From d346e03698a0f60037d595ac2d2331c9042b5d7d Mon Sep 17 00:00:00 2001 From: Giovanni Di Milia Date: Thu, 10 Jan 2019 14:52:20 -0500 Subject: [PATCH 01/14] feat(appflow): command to create deploy builds (#3815) --- packages/ionic/src/commands/deploy/build.ts | 198 ++++++++++++++++++ packages/ionic/src/commands/deploy/index.ts | 13 +- .../ionic/src/commands/deploy/manifest.ts | 2 + 3 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 packages/ionic/src/commands/deploy/build.ts diff --git a/packages/ionic/src/commands/deploy/build.ts b/packages/ionic/src/commands/deploy/build.ts new file mode 100644 index 0000000000..f875802354 --- /dev/null +++ b/packages/ionic/src/commands/deploy/build.ts @@ -0,0 +1,198 @@ +import { + CommandLineInputs, + CommandLineOptions, + LOGGER_LEVELS, + OptionGroup, +} from '@ionic/cli-framework'; +import { columnar } from '@ionic/cli-framework/utils/format'; +import { sleep } from '@ionic/cli-framework/utils/process'; +import chalk from 'chalk'; +import * as Debug from 'debug'; + +import { CommandMetadata } from '../../definitions'; +import { isSuperAgentError } from '../../guards'; +import { Command } from '../../lib/command'; +import { FatalException } from '../../lib/errors'; + +const debug = Debug('ionic:commands:deploy:build'); + +interface DeployBuild { + job_id: number; + id: string; + caller_id: number; + created: string; + finished: string; + state: string; + commit: any; + automation_id: number; + environment_id: number; + native_config_id: number; + automation_name: string; + environment_name: string; + job: any; + pending_channels: string[]; +} + +export class BuildCommand extends Command { + async getMetadata(): Promise { + const dashUrl = this.env.config.getDashUrl(); + + return { + name: 'build', + type: 'project', + summary: 'Create a deploy build on Appflow', + description: ` +This command creates a deploy build on Ionic Appflow. While the build is running, it prints the remote build log to the terminal. + +Customizing the build: +- The ${chalk.green('--environment')} and ${chalk.green('--channel')} options can be used to customize the groups of values exposed to the build. + +Apart from ${chalk.green('--commit')}, every option can be specified using the full name setup within the Appflow Dashboard${chalk.cyan('[1]')}. + +${chalk.cyan('[1]')}: ${chalk.bold(dashUrl)} +`, + exampleCommands: [ + '', + '--environment="My Custom Environment Name"', + '--commit=2345cd3305a1cf94de34e93b73a932f25baac77c', + '--channel="Master"', + '--channel="Master" --channel="My Custom Channel"', + ], + options: [ + { + name: 'environment', + summary: 'The group of environment variables exposed to your build', + type: String, + spec: { value: 'name' }, + }, + { + name: 'channel', + summary: 'The channel you want to auto deploy the build to. This can be repeated multiple times if multiple channels need to be specified.', + type: String, + spec: { value: 'name' }, + }, + { + name: 'commit', + summary: 'Commit (defaults to HEAD)', + type: String, + groups: [OptionGroup.Advanced], + spec: { value: 'sha1' }, + }, + ], + }; + } + + async run(inputs: CommandLineInputs, options: CommandLineOptions): Promise { + if (!this.project) { + throw new FatalException(`Cannot run ${chalk.green('ionic deploy build')} outside a project directory.`); + } + + const token = this.env.session.getUserToken(); + const appflowId = await this.project.requireAppflowId(); + + if (!options.commit) { + options.commit = (await this.env.shell.output('git', ['rev-parse', 'HEAD'], { cwd: this.project.directory })).trim(); + debug(`Commit hash: ${chalk.bold(options.commit)}`); + } + + let build = await this.createDeployBuild(appflowId, token, options); + const buildId = build.job_id; + + const details = columnar([ + ['Appflow ID', chalk.bold(appflowId)], + ['Build ID', chalk.bold(buildId.toString())], + ['Commit', chalk.bold(`${build.commit.sha.substring(0, 6)} ${build.commit.note}`)], + ['Environment', build.environment_name ? chalk.bold(build.environment_name) : chalk.dim('not set')], + ['Channels', build.pending_channels.length ? build.pending_channels.map(v => chalk.bold(`"${v}"`)).join(', ') : chalk.dim('not set')], + ], { vsep: ':' }); + + this.env.log.ok( + `Build created\n` + + details + '\n\n' + ); + + build = await this.tailBuildLog(appflowId, buildId, token); + if (build.state !== 'success') { + throw new Error('Build failed'); + } + + } + + async createDeployBuild(appflowId: string, token: string, options: CommandLineOptions): Promise { + const { req } = await this.env.client.make('POST', `/apps/${appflowId}/deploys/verbose_post`); + + let channels: string[] = []; + if (options.channel) { + if (typeof(options.channel) === 'string') { + channels.push(String(options.channel)); + } else if (typeof(options.channel) === 'object') { + channels = channels.concat(options.channel); + } + } + + req.set('Authorization', `Bearer ${token}`).send({ + commit_sha: options.commit, + environment_name: options.environment, + channel_names: channels ? channels : undefined, + }); + + try { + const res = await this.env.client.do(req); + return res.data as DeployBuild; + } catch (e) { + if (isSuperAgentError(e)) { + if (e.response.status === 401) { + this.env.log.error('Try logging out and back in again.'); + } + const apiErrorMessage = (e.response.body.error && e.response.body.error.message) ? e.response.body.error.message : 'Api Error'; + throw new FatalException(`Unable to create build: ` + apiErrorMessage); + } else { + throw e; + } + } + } + + async tailBuildLog(appflowId: string, buildId: number, token: string): Promise { + let build; + let start = 0; + const ws = this.env.log.createWriteStream(LOGGER_LEVELS.INFO, false); + + let isCreatedMessage = false; + while (!(build && (build.state === 'success' || build.state === 'failed'))) { + await sleep(5000); + build = await this.getDeployBuild(appflowId, buildId, token); + if (build && build.state === 'created' && !isCreatedMessage) { + ws.write(chalk.yellow('Concurrency limit reached: build will start as soon as other builds finish.')); + isCreatedMessage = true; + } + const trace = build.job.trace; + if (trace.length > start) { + ws.write(trace.substring(start)); + start = trace.length; + } + } + ws.end(); + + return build; + } + + async getDeployBuild(appflowId: string, buildId: number, token: string): Promise { + const { req } = await this.env.client.make('GET', `/apps/${appflowId}/deploys/${buildId}`); + req.set('Authorization', `Bearer ${token}`).send(); + + try { + const res = await this.env.client.do(req); + return res.data as DeployBuild; + } catch (e) { + if (isSuperAgentError(e)) { + if (e.response.status === 401) { + this.env.log.error('Try logging out and back in again.'); + } + const apiErrorMessage = (e.response.body.error && e.response.body.error.message) ? e.response.body.error.message : 'Api Error'; + throw new FatalException(`Unable to get build ${buildId}: ` + apiErrorMessage); + } else { + throw e; + } + } + } +} diff --git a/packages/ionic/src/commands/deploy/index.ts b/packages/ionic/src/commands/deploy/index.ts index 433b1cb4f1..2d5c4ebde6 100644 --- a/packages/ionic/src/commands/deploy/index.ts +++ b/packages/ionic/src/commands/deploy/index.ts @@ -1,4 +1,4 @@ -import { NamespaceGroup } from '@ionic/cli-framework'; +import chalk from 'chalk'; import { CommandMap, Namespace } from '../../lib/namespace'; @@ -6,14 +6,19 @@ export class DeployNamespace extends Namespace { async getMetadata() { return { name: 'deploy', - summary: 'Commands for working with Ionic Deploy', - groups: [NamespaceGroup.Hidden], - description: ` These commands make it easy to do all things deploy `, + summary: 'Appflow Deploy functionality', + description: ` +Interface to execute commands about deploy builds on Ionic Appflow. + +Appflow deploy documentation: +- Overview: ${chalk.bold('https://ionicframework.com/docs/appflow/deploy/')} +`, }; } async getCommands(): Promise { return new CommandMap([ + ['build', async () => { const { BuildCommand } = await import('./build'); return new BuildCommand(this); }], ['manifest', async () => { const { DeployManifestCommand } = await import('./manifest'); return new DeployManifestCommand(this); }], ]); } diff --git a/packages/ionic/src/commands/deploy/manifest.ts b/packages/ionic/src/commands/deploy/manifest.ts index c4327071ee..ca040bbeee 100644 --- a/packages/ionic/src/commands/deploy/manifest.ts +++ b/packages/ionic/src/commands/deploy/manifest.ts @@ -1,3 +1,4 @@ +import { CommandGroup } from '@ionic/cli-framework'; import { map } from '@ionic/cli-framework/utils/array'; import { readdirp, stat, writeFile } from '@ionic/utils-fs'; import chalk from 'chalk'; @@ -21,6 +22,7 @@ export class DeployManifestCommand extends Command { name: 'manifest', type: 'project', summary: 'Generates a manifest file for the deploy service from a built app directory', + groups: [CommandGroup.Hidden], }; } From d5681d609477970907c52d08877b3ef1b1d1b667 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Fri, 11 Jan 2019 16:17:13 -0500 Subject: [PATCH 02/14] build(deps): update diff requirement from ^3.4.0 to ^4.0.1 (#3814) Updates the requirements on [diff](https://github.com/kpdecker/jsdiff) to permit the latest version. - [Release notes](https://github.com/kpdecker/jsdiff/releases) - [Changelog](https://github.com/kpdecker/jsdiff/blob/master/release-notes.md) - [Commits](https://github.com/kpdecker/jsdiff/commits/v4.0.1) Signed-off-by: dependabot[bot] --- packages/ionic/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ionic/package.json b/packages/ionic/package.json index 2904573737..fa600d1499 100644 --- a/packages/ionic/package.json +++ b/packages/ionic/package.json @@ -54,7 +54,7 @@ "@ionic/utils-network": "0.0.6", "chalk": "^2.3.0", "debug": "^4.0.0", - "diff": "^3.4.0", + "diff": "^4.0.1", "elementtree": "^0.1.7", "leek": "0.0.24", "lodash": "^4.17.5", From 6050d7e6487b513cd15b3642be69cec8e4561579 Mon Sep 17 00:00:00 2001 From: Ionitron Date: Mon, 14 Jan 2019 15:01:39 +0000 Subject: [PATCH 03/14] chore(release): publish [skip ci] - cli-scripts@0.0.28 - ionic@4.8.0 --- packages/cli-scripts/CHANGELOG.md | 8 ++++++++ packages/cli-scripts/package.json | 4 ++-- packages/ionic/CHANGELOG.md | 11 +++++++++++ packages/ionic/package.json | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/cli-scripts/CHANGELOG.md b/packages/cli-scripts/CHANGELOG.md index 4495cca220..4dc8a78092 100644 --- a/packages/cli-scripts/CHANGELOG.md +++ b/packages/cli-scripts/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.0.28](https://github.com/ionic-team/ionic-cli/compare/cli-scripts@0.0.27...cli-scripts@0.0.28) (2019-01-14) + + + + +**Note:** Version bump only for package cli-scripts + ## [0.0.27](https://github.com/ionic-team/ionic-cli/compare/cli-scripts@0.0.26...cli-scripts@0.0.27) (2019-01-08) diff --git a/packages/cli-scripts/package.json b/packages/cli-scripts/package.json index 13e1e6e1b2..d35cae4eb5 100644 --- a/packages/cli-scripts/package.json +++ b/packages/cli-scripts/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "cli-scripts", - "version": "0.0.27", + "version": "0.0.28", "bin": { "ionic-cli-scripts": "./bin/ionic-cli-scripts" }, @@ -29,7 +29,7 @@ "ansi-styles": "^3.2.0", "chalk": "^2.3.0", "escape-string-regexp": "^1.0.5", - "ionic": "4.7.1", + "ionic": "4.8.0", "strip-ansi": "^5.0.0", "tslib": "^1.9.0" }, diff --git a/packages/ionic/CHANGELOG.md b/packages/ionic/CHANGELOG.md index 395c25bc33..801287ac82 100644 --- a/packages/ionic/CHANGELOG.md +++ b/packages/ionic/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [4.8.0](https://github.com/ionic-team/ionic-cli/compare/ionic@4.7.1...ionic@4.8.0) (2019-01-14) + + +### Features + +* **appflow:** command to create deploy builds ([#3815](https://github.com/ionic-team/ionic-cli/issues/3815)) ([d346e03](https://github.com/ionic-team/ionic-cli/commit/d346e03)) + + + + ## [4.7.1](https://github.com/ionic-team/ionic-cli/compare/ionic@4.7.0...ionic@4.7.1) (2019-01-08) diff --git a/packages/ionic/package.json b/packages/ionic/package.json index fa600d1499..1581762561 100644 --- a/packages/ionic/package.json +++ b/packages/ionic/package.json @@ -1,6 +1,6 @@ { "name": "ionic", - "version": "4.7.1", + "version": "4.8.0", "description": "A tool for creating and developing Ionic Framework mobile apps.", "homepage": "https://ionicframework.com", "author": "Ionic Team (https://ionicframework.com) ", From a50251fa7bcae23b66aa300128f027be1bdd05a0 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Tue, 15 Jan 2019 16:26:21 -0600 Subject: [PATCH 04/14] chore(devapp): update messaging --- packages/@ionic/discover/src/comm.ts | 7 +++---- packages/ionic/src/lib/serve.ts | 9 ++++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/@ionic/discover/src/comm.ts b/packages/@ionic/discover/src/comm.ts index 581727decc..a08c704434 100644 --- a/packages/@ionic/discover/src/comm.ts +++ b/packages/@ionic/discover/src/comm.ts @@ -9,18 +9,17 @@ const PREFIX = 'ION_CS'; export interface CommServerConnectionPayload { event: 'connect'; - email: string; - username: string; + device: string; } type Payload = CommServerConnectionPayload; -export interface ICommServerEventEmitter { +export interface CommServer { on(event: 'error', listener: (err: Error) => void): this; on(event: 'connect', listener: (data: CommServerConnectionPayload) => void): this; } -export class CommServer extends events.EventEmitter implements ICommServerEventEmitter { +export class CommServer extends events.EventEmitter { protected server?: WebSocket.Server; constructor( diff --git a/packages/ionic/src/lib/serve.ts b/packages/ionic/src/lib/serve.ts index 455448cf52..746f777502 100644 --- a/packages/ionic/src/lib/serve.ts +++ b/packages/ionic/src/lib/serve.ts @@ -150,6 +150,8 @@ export abstract class ServeRunner implements Runner `- ${chalk.bold(p)}`).join('\n')}\n\n` + `App may not function as expected in Ionic DevApp.` ); + + this.e.log.nl(); } } } @@ -264,13 +266,14 @@ export abstract class ServeRunner implements Runner { + comm.on('connect', async data => { + this.e.log.info(`DevApp connection established from ${chalk.bold(data.device)}`); + this.e.log.nl(); + if (!this.devAppConnectionMade) { this.devAppConnectionMade = true; await this.displayDevAppMessage(options); } - - this.e.log.info(`DevApp connection established from ${chalk.bold(data.email)}`); }); publisher.on('error', (err: Error) => { From 2c74d53861fbe3ca1adab3fc4ef3a66ab9819900 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Thu, 17 Jan 2019 14:54:19 -0600 Subject: [PATCH 05/14] feat(help): text/link footnotes for descriptions --- .../@ionic/cli-framework/src/definitions.ts | 14 ++++ packages/@ionic/cli-framework/src/guards.ts | 8 +- .../cli-framework/src/lib/__tests__/help.ts | 44 ++++++++-- .../src/lib/__tests__/options.ts | 1 + .../@ionic/cli-framework/src/lib/colors.ts | 7 ++ .../@ionic/cli-framework/src/lib/command.ts | 1 + packages/@ionic/cli-framework/src/lib/help.ts | 82 +++++++++++++++++-- 7 files changed, 145 insertions(+), 12 deletions(-) diff --git a/packages/@ionic/cli-framework/src/definitions.ts b/packages/@ionic/cli-framework/src/definitions.ts index 0250092621..04a87d716f 100644 --- a/packages/@ionic/cli-framework/src/definitions.ts +++ b/packages/@ionic/cli-framework/src/definitions.ts @@ -20,10 +20,24 @@ export interface CommandMetadataInput { private?: boolean; } +export interface TextFootnote { + id: string | number; + text: string; +} + +export interface LinkFootnote { + id: string | number; + url: string; + shortUrl?: string; +} + +export type Footnote = TextFootnote | LinkFootnote; + export interface Metadata { name: string; summary: string; description?: string; + footnotes?: Footnote[]; groups?: string[]; } diff --git a/packages/@ionic/cli-framework/src/guards.ts b/packages/@ionic/cli-framework/src/guards.ts index 2926ca497c..22b3a082ce 100644 --- a/packages/@ionic/cli-framework/src/guards.ts +++ b/packages/@ionic/cli-framework/src/guards.ts @@ -1,4 +1,4 @@ -import { CommandMetadata, CommandMetadataInput, CommandMetadataOption, HydratedCommandMetadata, ICommand, INamespace, PackageJson } from './definitions'; +import { CommandMetadata, CommandMetadataInput, CommandMetadataOption, HydratedCommandMetadata, ICommand, INamespace, LinkFootnote, PackageJson } from './definitions'; export function isNamespace, N extends INamespace, M extends CommandMetadata, I extends CommandMetadataInput, O extends CommandMetadataOption>(obj: any): obj is N { return obj && @@ -26,3 +26,9 @@ export function isHydratedCommandMetadata, N e export function isPackageJson(obj: any): obj is PackageJson { return obj && typeof obj.name === 'string'; } + +export function isLinkFootnote(obj: any): obj is LinkFootnote { + return obj && + (typeof obj.id === 'number' || typeof obj.id === 'string') && + typeof obj.url === 'string'; +} diff --git a/packages/@ionic/cli-framework/src/lib/__tests__/help.ts b/packages/@ionic/cli-framework/src/lib/__tests__/help.ts index 54ea8d4380..084d8d93a2 100644 --- a/packages/@ionic/cli-framework/src/lib/__tests__/help.ts +++ b/packages/@ionic/cli-framework/src/lib/__tests__/help.ts @@ -46,7 +46,13 @@ class FooNamespace extends Namespace { return { name: 'foo', summary: 'the foo namespace', - description: 'my description', + description: 'my description with this footnote[^here] and more text', + footnotes: [ + { + id: 'here', + text: 'A footnote is a thing', + }, + ], }; } @@ -79,7 +85,13 @@ class BarCommand extends Command { return { name: 'bar', summary: 'the bar command', - description: 'my description', + description: 'my description with a numeric link footnote[^1] and some more text', + footnotes: [ + { + id: 1, + url: 'https://ionicframework.com', + }, + ], inputs: [ { name: 'input1', summary: 'input1 summary' }, { name: 'input2', summary: 'input2 summary' }, @@ -124,7 +136,9 @@ describe('@ionic/cli-framework', () => { expect(stripAnsi(result)).toEqual(` my foo bar - the bar command - my description + my description with a numeric link footnote[1] and some more text + + [1]: https://ionicframework.com Usage: @@ -185,7 +199,9 @@ describe('@ionic/cli-framework', () => { expect(stripAnsi(result)).toEqual(` my foo - the foo namespace - my description + my description with this footnote[1] and more text + + [1]: A footnote is a thing Usage: @@ -217,13 +233,20 @@ describe('@ionic/cli-framework', () => { "my" ], "summary": "the bar command", - "description": "my description", + "description": "my description with a numeric link footnote[^1] and some more text", "groups": [], "exampleCommands": [ "my bar ", "my bar input1 input2", "my bar --opt1 --opt2" ], + "footnotes": [ + { + "type": "link", + "id": 1, + "url": "https://ionicframework.com" + } + ], "aliases": [], "inputs": [ { @@ -281,13 +304,20 @@ describe('@ionic/cli-framework', () => { "foo" ], "summary": "the bar command", - "description": "my description", + "description": "my description with a numeric link footnote[^1] and some more text", "groups": [], "exampleCommands": [ "my foo bar ", "my foo bar input1 input2", "my foo bar --opt1 --opt2" ], + "footnotes": [ + { + "type": "link", + "id": 1, + "url": "https://ionicframework.com" + } + ], "aliases": [ "my foo b" ], @@ -350,6 +380,7 @@ describe('@ionic/cli-framework', () => { "description": "", "groups": [], "exampleCommands": [], + "footnotes": [], "aliases": [ "my foo b1", "my foo b2" @@ -368,6 +399,7 @@ describe('@ionic/cli-framework', () => { "beta" ], "exampleCommands": [], + "footnotes": [], "aliases": [], "inputs": [], "options": [] diff --git a/packages/@ionic/cli-framework/src/lib/__tests__/options.ts b/packages/@ionic/cli-framework/src/lib/__tests__/options.ts index ead5ba8a06..15f8c55bac 100644 --- a/packages/@ionic/cli-framework/src/lib/__tests__/options.ts +++ b/packages/@ionic/cli-framework/src/lib/__tests__/options.ts @@ -74,6 +74,7 @@ describe('@ionic/cli-framework', () => { name: 'bar', summary: '', description: '', + footnotes: [], exampleCommands: [], groups: [], inputs: [ diff --git a/packages/@ionic/cli-framework/src/lib/colors.ts b/packages/@ionic/cli-framework/src/lib/colors.ts index 97f4b394dc..ad651b5bcd 100644 --- a/packages/@ionic/cli-framework/src/lib/colors.ts +++ b/packages/@ionic/cli-framework/src/lib/colors.ts @@ -32,6 +32,11 @@ export interface Colors { */ failure: ColorFunction; + /** + * Used to mark text as ancillary or supportive. + */ + ancillary: ColorFunction; + log: LoggerColors; } @@ -41,6 +46,7 @@ export const DEFAULT_COLORS: Colors = Object.freeze({ input: chalk.green, success: chalk.green, failure: chalk.red, + ancillary: chalk.cyan, log: Object.freeze({ DEBUG: chalk.magenta, INFO: chalk.white, @@ -55,6 +61,7 @@ export const NO_COLORS: Colors = Object.freeze({ input: lodash.identity, success: lodash.identity, failure: lodash.identity, + ancillary: lodash.identity, log: Object.freeze({ DEBUG: lodash.identity, INFO: lodash.identity, diff --git a/packages/@ionic/cli-framework/src/lib/command.ts b/packages/@ionic/cli-framework/src/lib/command.ts index 29fde816fd..8f9235f201 100644 --- a/packages/@ionic/cli-framework/src/lib/command.ts +++ b/packages/@ionic/cli-framework/src/lib/command.ts @@ -229,6 +229,7 @@ export abstract class BaseNamespace, N extends aliases, ...nsmeta, description: nsmeta.description ? nsmeta.description : '', + footnotes: nsmeta.footnotes ? nsmeta.footnotes : [], groups: nsmeta.groups ? nsmeta.groups : [], }; grouped.set(nsmeta.name, entry); diff --git a/packages/@ionic/cli-framework/src/lib/help.ts b/packages/@ionic/cli-framework/src/lib/help.ts index a67ee9f8d8..23dde933ae 100644 --- a/packages/@ionic/cli-framework/src/lib/help.ts +++ b/packages/@ionic/cli-framework/src/lib/help.ts @@ -1,8 +1,9 @@ import chalk from 'chalk'; +import * as Debug from 'debug'; import * as lodash from 'lodash'; -import { CommandMetadata, CommandMetadataInput, CommandMetadataOption, HydratedCommandMetadata, HydratedNamespaceMetadata, ICommand, INamespace, NamespaceLocateResult, NamespaceMetadata } from '../definitions'; -import { isHydratedCommandMetadata } from '../guards'; +import { CommandMetadata, CommandMetadataInput, CommandMetadataOption, Footnote, HydratedCommandMetadata, HydratedNamespaceMetadata, ICommand, INamespace, LinkFootnote, NamespaceLocateResult, NamespaceMetadata } from '../definitions'; +import { isHydratedCommandMetadata, isLinkFootnote } from '../guards'; import { filter, map } from '../utils/array'; import { generateFillSpaceStringList, stringWidth, wordWrap } from '../utils/format'; @@ -10,6 +11,8 @@ import { ColorFunction, Colors, DEFAULT_COLORS } from './colors'; import { formatOptionName, hydrateOptionSpec } from './options'; import { validators } from './validators'; +const debug = Debug('ionic:cli-framework:lib:help'); + const DEFAULT_DOTS_WIDTH = 32; export enum CommandGroup { @@ -63,6 +66,49 @@ function formatGroupDecorations(decorations: Decoration[], return prepends.length ? prepends.join(' ') + ' ' : ''; } +function formatLinkFootnote(footnote: LinkFootnote, colors = DEFAULT_COLORS): string { + const { strong } = colors; + + return strong(footnote.shortUrl ? footnote.shortUrl : footnote.url); +} + +function formatFootnote(index: number, footnote: Footnote, colors = DEFAULT_COLORS): string { + const { ancillary } = colors; + const prefix = ancillary(`[${index}]`); + + const output = isLinkFootnote(footnote) ? formatLinkFootnote(footnote, colors) : footnote.text; + + return `${prefix}: ${output}`; +} + +function formatFootnotes(text: string, footnotes?: ReadonlyArray, colors = DEFAULT_COLORS): string { + if (!footnotes) { + return text; + } + + const { ancillary } = colors; + const discoveredFootnotes: Footnote[] = []; + const output = text.replace(/\[\^([A-z0-9-]+)\]/g, (match, p1) => { + const m = Number.parseInt(p1, 10); + const id = !Number.isNaN(m) ? m : p1; + const foundFootnote = footnotes.find(footnote => footnote.id === id); + + if (foundFootnote) { + const len = discoveredFootnotes.push(foundFootnote); + return ancillary(`[${len}]`); + } else { + debug('No footnote found by ID: %O', id); + return ''; + } + }); + + return output + ( + discoveredFootnotes.length > 0 ? + `\n\n${discoveredFootnotes.map((footnote, i) => formatFootnote(i + 1, footnote, colors)).join('\n')}` : + '' + ); +} + export async function isOptionVisible(opt: O): Promise { return !opt.groups || !opt.groups.includes(OptionGroup.Hidden); } @@ -165,7 +211,9 @@ export class NamespaceStringHelpFormatter, N e return ''; } - return wordWrap(metadata.description.trim(), { indentation: 4 }); + const text = formatFootnotes(metadata.description.trim(), metadata.footnotes, this.colors); + + return wordWrap(text, { indentation: 4 }); } async getGlobalOptions(): Promise { @@ -432,7 +480,9 @@ export class CommandStringHelpFormatter, N ext return ''; } - return wordWrap(metadata.description.trim(), { indentation: 4 }); + const text = formatFootnotes(metadata.description.trim(), metadata.footnotes, this.colors); + + return wordWrap(text, { indentation: 4 }); } async formatInlineInput(input: I): Promise { @@ -665,11 +715,27 @@ export interface CommandHelpSchemaOption { }; } +export interface CommandHelpSchemaFootnoteText { + readonly type: 'text'; + readonly id: string | number; + readonly text: string; +} + +export interface CommandHelpSchemaFootnoteLink { + readonly type: 'link'; + readonly id: string | number; + readonly url: string; + readonly shortUrl?: string; +} + +export type CommandHelpSchemaFootnote = CommandHelpSchemaFootnoteText | CommandHelpSchemaFootnoteLink; + export interface CommandHelpSchema { readonly name: string; readonly namespace: ReadonlyArray; readonly summary: string; readonly description: string; + readonly footnotes: ReadonlyArray; readonly groups: ReadonlyArray; readonly exampleCommands: ReadonlyArray; readonly aliases: ReadonlyArray; @@ -717,19 +783,24 @@ export class CommandSchemaHelpFormatter, N ext return { name, type, summary, default: option.default, groups, aliases, spec }; } + formatFootnote(footnote: Footnote): CommandHelpSchemaFootnote { + return isLinkFootnote(footnote) ? ({ type: 'link', ...footnote }) : ({ type: 'text', ...footnote }); + } + async formatCommand(cmd: M | HydratedCommandMetadata): Promise { const commandPath = this.location.path.map(([p]) => p); const namespacePath = lodash.initial(commandPath); const name = commandPath.join(' '); const summary = cmd.summary ? cmd.summary.trim() : ''; const description = cmd.description ? cmd.description.trim() : ''; + const footnotes = cmd.footnotes ? cmd.footnotes.map(footnote => this.formatFootnote(footnote)) : []; const groups = cmd.groups ? cmd.groups : []; const exampleCommands = cmd.exampleCommands ? cmd.exampleCommands.map(c => `${name} ${c}`) : []; const aliases = isHydratedCommandMetadata(cmd) ? cmd.aliases : []; const inputs = cmd.inputs ? await this.formatInputs(cmd.inputs) : []; const options = cmd.options ? await this.formatOptions(cmd.options) : []; - return { name, namespace: namespacePath, summary, description, groups, exampleCommands, aliases, inputs, options }; + return { name, namespace: namespacePath, summary, description, footnotes, groups, exampleCommands, aliases, inputs, options }; } } @@ -738,6 +809,7 @@ export function createCommandMetadataFromSchema(schema: CommandHelpSchema): Requ name: schema.name, summary: schema.summary, description: schema.description, + footnotes: [...schema.footnotes], groups: [...schema.groups], exampleCommands: [...schema.exampleCommands], inputs: [...schema.inputs], From 1059e8307dcd59068daa2ea622606b1428204f6a Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Thu, 17 Jan 2019 15:33:51 -0600 Subject: [PATCH 06/14] chore: use footnotes and short URLs --- packages/ionic/src/commands/build.ts | 4 ++++ .../ionic/src/commands/capacitor/index.ts | 2 +- packages/ionic/src/commands/capacitor/run.ts | 18 +++++++++++++---- packages/ionic/src/commands/cordova/build.ts | 20 ++++++++++++++----- .../ionic/src/commands/cordova/prepare.ts | 6 +++++- packages/ionic/src/commands/cordova/run.ts | 18 +++++++++++++---- packages/ionic/src/commands/deploy/build.ts | 10 +++++++--- packages/ionic/src/commands/deploy/index.ts | 2 +- packages/ionic/src/commands/docs.ts | 2 +- packages/ionic/src/commands/generate.ts | 5 ++++- packages/ionic/src/commands/git/remote.ts | 12 ++++++++--- packages/ionic/src/commands/link.ts | 20 +++++++++++++------ packages/ionic/src/commands/login.ts | 10 +++++++--- packages/ionic/src/commands/package/build.ts | 10 +++++++--- packages/ionic/src/commands/package/index.ts | 2 +- packages/ionic/src/commands/serve.ts | 5 ++++- packages/ionic/src/commands/signup.ts | 12 +++++++---- packages/ionic/src/commands/ssh/index.ts | 12 ++++++++--- packages/ionic/src/commands/ssh/setup.ts | 19 +++++++++++++----- packages/ionic/src/commands/ssh/use.ts | 10 +++++++--- packages/ionic/src/commands/start.ts | 9 +++------ packages/ionic/src/index.ts | 2 +- .../ionic/src/lib/doctor/ailments/index.ts | 2 +- .../ionic/src/lib/project/angular/build.ts | 11 +++++++--- .../ionic/src/lib/project/angular/serve.ts | 18 ++++++++++++----- packages/ionic/src/lib/project/index.ts | 2 +- .../src/lib/project/ionic-angular/build.ts | 12 ++++++++--- .../ionic/src/lib/project/ionic1/index.ts | 2 +- packages/ionic/src/lib/start.ts | 2 +- 29 files changed, 184 insertions(+), 75 deletions(-) diff --git a/packages/ionic/src/commands/build.ts b/packages/ionic/src/commands/build.ts index c24b0c579c..4a2781f644 100644 --- a/packages/ionic/src/commands/build.ts +++ b/packages/ionic/src/commands/build.ts @@ -1,3 +1,4 @@ +import { Footnote } from '@ionic/cli-framework'; import chalk from 'chalk'; import { CommandInstanceInfo, CommandLineInputs, CommandLineOptions, CommandMetadata, CommandMetadataOption, CommandPreRun } from '../definitions'; @@ -8,6 +9,7 @@ import { FatalException } from '../lib/errors'; export class BuildCommand extends Command implements CommandPreRun { async getMetadata(): Promise { const options: CommandMetadataOption[] = []; + const footnotes: Footnote[] = []; const exampleCommands = ['']; let description = `${chalk.green('ionic build')} will perform an Ionic build, which compiles web assets and prepares them for deployment.`; let groups: string[] = []; @@ -19,6 +21,7 @@ export class BuildCommand extends Command implements CommandPreRun { groups = libmetadata.groups || []; options.push(...libmetadata.options || []); description += libmetadata.description ? `\n\n${libmetadata.description.trim()}` : ''; + footnotes.push(...libmetadata.footnotes || []); exampleCommands.push(...libmetadata.exampleCommands || []); } @@ -29,6 +32,7 @@ export class BuildCommand extends Command implements CommandPreRun { type: 'project', summary: 'Build web assets and prepare your app for any platform targets', description, + footnotes, groups, exampleCommands, options, diff --git a/packages/ionic/src/commands/capacitor/index.ts b/packages/ionic/src/commands/capacitor/index.ts index 68faf54104..ad8767928f 100644 --- a/packages/ionic/src/commands/capacitor/index.ts +++ b/packages/ionic/src/commands/capacitor/index.ts @@ -12,7 +12,7 @@ export class CapacitorNamespace extends Namespace { These commands integrate with Capacitor, Ionic's new native layer project which provides an alternative to Cordova for native functionality in your app. Learn more about Capacitor: -- Main documentation: ${chalk.bold('https://capacitor.ionicframework.com/')} +- Main documentation: ${chalk.bold('https://ion.link/capacitor')} `, groups: [NamespaceGroup.Beta], }; diff --git a/packages/ionic/src/commands/capacitor/run.ts b/packages/ionic/src/commands/capacitor/run.ts index 61338606c8..8b62e80c5a 100644 --- a/packages/ionic/src/commands/capacitor/run.ts +++ b/packages/ionic/src/commands/capacitor/run.ts @@ -1,4 +1,4 @@ -import { CommandGroup, validators } from '@ionic/cli-framework'; +import { CommandGroup, Footnote, validators } from '@ionic/cli-framework'; import { onBeforeExit, sleepForever } from '@ionic/cli-framework/utils/process'; import chalk from 'chalk'; import * as path from 'path'; @@ -22,6 +22,7 @@ export class RunCommand extends CapacitorCommand implements CommandPreRun { 'ios --livereload', 'ios --livereload-url=http://localhost:8100', ].sort(); + const options: CommandMetadataOption[] = [ // Build Options { @@ -44,6 +45,14 @@ export class RunCommand extends CapacitorCommand implements CommandPreRun { }, ]; + const footnotes: Footnote[] = [ + { + id: 'remote-debugging-docs', + url: 'https://ionicframework.com/docs/developer-resources/developer-tips', + shortUrl: 'https://ion.link/remote-debugging-docs', + }, + ]; + const serveRunner = this.project && await this.project.getServeRunner(); const buildRunner = this.project && await this.project.getBuildRunner(); @@ -51,6 +60,7 @@ export class RunCommand extends CapacitorCommand implements CommandPreRun { const libmetadata = await buildRunner.getCommandMetadata(); groups = libmetadata.groups || []; options.push(...libmetadata.options || []); + footnotes.push(...libmetadata.footnotes || []); } if (serveRunner) { @@ -58,6 +68,7 @@ export class RunCommand extends CapacitorCommand implements CommandPreRun { const existingOpts = options.map(o => o.name); groups = libmetadata.groups || []; options.push(...(libmetadata.options || []).filter(o => !existingOpts.includes(o.name)).map(o => ({ ...o, hint: `${o.hint ? `${o.hint} ` : ''}${chalk.dim('(--livereload)')}` }))); + footnotes.push(...libmetadata.footnotes || []); } return { @@ -72,10 +83,9 @@ ${chalk.green('ionic capacitor run')} will do the following: Once the web assets and configuration are copied into your native project, the app can run on devices and emulators/simulators using the native IDE. Unfortunately, programmatically building and launching the native project is not yet supported. -For Android and iOS, you can setup Remote Debugging on your device with browser development tools using these docs${chalk.cyan('[1]')}. - -${chalk.cyan('[1]')}: ${chalk.bold('https://ionicframework.com/docs/developer-resources/developer-tips/')} +For Android and iOS, you can setup Remote Debugging on your device with browser development tools using these docs[^remote-debugging-docs]. `, + footnotes, exampleCommands, inputs: [ { diff --git a/packages/ionic/src/commands/cordova/build.ts b/packages/ionic/src/commands/cordova/build.ts index a2683909f6..95ef9e0925 100644 --- a/packages/ionic/src/commands/cordova/build.ts +++ b/packages/ionic/src/commands/cordova/build.ts @@ -1,4 +1,4 @@ -import { CommandMetadataOption, validators } from '@ionic/cli-framework'; +import { CommandMetadataOption, Footnote, validators } from '@ionic/cli-framework'; import chalk from 'chalk'; import { CommandInstanceInfo, CommandLineInputs, CommandLineOptions, CommandMetadata, CommandPreRun } from '../../definitions'; @@ -22,11 +22,23 @@ export class BuildCommand extends CordovaCommand implements CommandPreRun { ...COMMON_CORDOVA_BUILD_COMMAND_OPTIONS, ]; + const footnotes: Footnote[] = [ + { + id: 'cordova-android-using-flags', + url: 'https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html#using-flags', + }, + { + id: 'cordova-ios-using-flags', + url: 'https://cordova.apache.org/docs/en/latest/guide/platforms/ios/index.html#using-flags', + }, + ]; + const runner = this.project && await this.project.getBuildRunner(); if (runner) { const libmetadata = await runner.getCommandMetadata(); options.push(...libmetadata.options || []); + footnotes.push(...libmetadata.footnotes || []); } return { @@ -38,11 +50,9 @@ Like running ${chalk.green('cordova build')} directly, but also builds web asset To pass additional options to the Cordova CLI, use the ${chalk.green('--')} separator after the Ionic CLI arguments. -The Cordova CLI requires a separator for platform-specific arguments for Android builds${chalk.cyan('[1]')}, so an additional separator is required for the Ionic CLI, but it is not required for iOS builds${chalk.cyan('[2]')}. See the example commands for usage with separators. To avoid using flags, consider using ${chalk.green('--buildConfig')} with a ${chalk.bold('build.json')} file. - -${chalk.cyan('[1]')}: ${chalk.bold('https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html#using-flags')} -${chalk.cyan('[2]')}: ${chalk.bold('https://cordova.apache.org/docs/en/latest/guide/platforms/ios/index.html#using-flags')} +The Cordova CLI requires a separator for platform-specific arguments for Android builds[^cordova-android-using-flags], so an additional separator is required for the Ionic CLI, but it is not required for iOS builds[^cordova-ios-using-flags]. See the example commands for usage with separators. To avoid using flags, consider using ${chalk.green('--buildConfig')} with a ${chalk.bold('build.json')} file. `, + footnotes, exampleCommands, inputs: [ { diff --git a/packages/ionic/src/commands/cordova/prepare.ts b/packages/ionic/src/commands/cordova/prepare.ts index 32e0000569..466c8cbfc4 100644 --- a/packages/ionic/src/commands/cordova/prepare.ts +++ b/packages/ionic/src/commands/cordova/prepare.ts @@ -1,4 +1,4 @@ -import { CommandMetadataOption } from '@ionic/cli-framework'; +import { CommandMetadataOption, Footnote } from '@ionic/cli-framework'; import chalk from 'chalk'; import { CommandInstanceInfo, CommandLineInputs, CommandLineOptions, CommandMetadata, CommandPreRun } from '../../definitions'; @@ -19,11 +19,14 @@ export class PrepareCommand extends CordovaCommand implements CommandPreRun { }, ]; + const footnotes: Footnote[] = []; + const runner = this.project && await this.project.getBuildRunner(); if (runner) { const libmetadata = await runner.getCommandMetadata(); options.push(...libmetadata.options || []); + footnotes.push(...libmetadata.footnotes || []); } return { @@ -41,6 +44,7 @@ ${chalk.green('ionic cordova prepare')} will do the following: You may wish to use ${chalk.green('ionic cordova prepare')} if you run your project with Android Studio or Xcode. `, + footnotes, exampleCommands: ['', 'ios', 'android'], inputs: [ { diff --git a/packages/ionic/src/commands/cordova/run.ts b/packages/ionic/src/commands/cordova/run.ts index 861560fbc5..94af079b31 100644 --- a/packages/ionic/src/commands/cordova/run.ts +++ b/packages/ionic/src/commands/cordova/run.ts @@ -1,4 +1,4 @@ -import { ERROR_SHELL_COMMAND_NOT_FOUND, LOGGER_LEVELS, OptionGroup, ShellCommandError, createPrefixedFormatter } from '@ionic/cli-framework'; +import { ERROR_SHELL_COMMAND_NOT_FOUND, Footnote, LOGGER_LEVELS, OptionGroup, ShellCommandError, createPrefixedFormatter } from '@ionic/cli-framework'; import { onBeforeExit, processExit, sleepForever } from '@ionic/cli-framework/utils/process'; import chalk from 'chalk'; import * as path from 'path'; @@ -98,6 +98,7 @@ export class RunCommand extends CordovaCommand implements CommandPreRun { 'ios --livereload', 'ios --livereload-url=http://localhost:8100', ].sort(); + const options: CommandMetadataOption[] = [ { name: 'list', @@ -127,6 +128,14 @@ export class RunCommand extends CordovaCommand implements CommandPreRun { }, ]; + const footnotes: Footnote[] = [ + { + id: 'remote-debugging-docs', + url: 'https://ionicframework.com/docs/developer-resources/developer-tips', + shortUrl: 'https://ion.link/remote-debugging-docs', + }, + ]; + const serveRunner = this.project && await this.project.getServeRunner(); const buildRunner = this.project && await this.project.getBuildRunner(); @@ -134,6 +143,7 @@ export class RunCommand extends CordovaCommand implements CommandPreRun { const libmetadata = await buildRunner.getCommandMetadata(); groups = libmetadata.groups || []; options.push(...libmetadata.options || []); + footnotes.push(...libmetadata.footnotes || []); } if (serveRunner) { @@ -141,6 +151,7 @@ export class RunCommand extends CordovaCommand implements CommandPreRun { const existingOpts = options.map(o => o.name); groups = libmetadata.groups || []; options.push(...(libmetadata.options || []).filter(o => !existingOpts.includes(o.name)).map(o => ({ ...o, hint: `${o.hint ? `${o.hint} ` : ''}${chalk.dim('(--livereload)')}` }))); + footnotes.push(...libmetadata.footnotes || []); } // Cordova Options @@ -156,12 +167,11 @@ export class RunCommand extends CordovaCommand implements CommandPreRun { description: ` Like running ${chalk.green('cordova run')} or ${chalk.green('cordova emulate')} directly, but performs ${chalk.green('ionic build')} before deploying to the device or emulator. Optionally specify the ${chalk.green('--livereload')} option to use the dev server from ${chalk.green('ionic serve')} for livereload functionality. -For Android and iOS, you can setup Remote Debugging on your device with browser development tools using these docs${chalk.cyan('[1]')}. +For Android and iOS, you can setup Remote Debugging on your device with browser development tools using these docs[^remote-debugging-docs]. Just like with ${chalk.green('ionic cordova build')}, you can pass additional options to the Cordova CLI using the ${chalk.green('--')} separator. To pass additional options to the dev server, consider using ${chalk.green('ionic serve')} and the ${chalk.green('--livereload-url')} option. - -${chalk.cyan('[1]')}: ${chalk.bold('https://ionicframework.com/docs/developer-resources/developer-tips/')} `, + footnotes, exampleCommands, inputs: [ { diff --git a/packages/ionic/src/commands/deploy/build.ts b/packages/ionic/src/commands/deploy/build.ts index f875802354..7ddde54073 100644 --- a/packages/ionic/src/commands/deploy/build.ts +++ b/packages/ionic/src/commands/deploy/build.ts @@ -47,10 +47,14 @@ This command creates a deploy build on Ionic Appflow. While the build is running Customizing the build: - The ${chalk.green('--environment')} and ${chalk.green('--channel')} options can be used to customize the groups of values exposed to the build. -Apart from ${chalk.green('--commit')}, every option can be specified using the full name setup within the Appflow Dashboard${chalk.cyan('[1]')}. - -${chalk.cyan('[1]')}: ${chalk.bold(dashUrl)} +Apart from ${chalk.green('--commit')}, every option can be specified using the full name setup within the Appflow Dashboard[^dashboard]. `, + footnotes: [ + { + id: 'dashboard', + url: dashUrl, + }, + ], exampleCommands: [ '', '--environment="My Custom Environment Name"', diff --git a/packages/ionic/src/commands/deploy/index.ts b/packages/ionic/src/commands/deploy/index.ts index 2d5c4ebde6..a6e63e3ece 100644 --- a/packages/ionic/src/commands/deploy/index.ts +++ b/packages/ionic/src/commands/deploy/index.ts @@ -11,7 +11,7 @@ export class DeployNamespace extends Namespace { Interface to execute commands about deploy builds on Ionic Appflow. Appflow deploy documentation: -- Overview: ${chalk.bold('https://ionicframework.com/docs/appflow/deploy/')} +- Overview: ${chalk.bold('https://ion.link/appflow-deploy-docs')} `, }; } diff --git a/packages/ionic/src/commands/docs.ts b/packages/ionic/src/commands/docs.ts index 1b3c61fa96..95ade27fdf 100644 --- a/packages/ionic/src/commands/docs.ts +++ b/packages/ionic/src/commands/docs.ts @@ -28,7 +28,7 @@ export class DocsCommand extends Command { async run(inputs: CommandLineInputs, options: CommandLineOptions): Promise { const browser = options['browser'] ? String(options['browser']) : undefined; - const homepage = 'https://ionicframework.com/docs'; + const homepage = 'https://ion.link/docs'; const url = this.project ? await this.project.getDocsUrl() : homepage; try { diff --git a/packages/ionic/src/commands/generate.ts b/packages/ionic/src/commands/generate.ts index b14341c16d..022deab81e 100644 --- a/packages/ionic/src/commands/generate.ts +++ b/packages/ionic/src/commands/generate.ts @@ -1,4 +1,4 @@ -import { CommandGroup } from '@ionic/cli-framework'; +import { CommandGroup, Footnote } from '@ionic/cli-framework'; import chalk from 'chalk'; import { CommandLineInputs, CommandLineOptions, CommandMetadata, CommandMetadataInput, CommandMetadataOption, CommandPreRun } from '../definitions'; @@ -11,6 +11,7 @@ export class GenerateCommand extends Command implements CommandPreRun { const inputs: CommandMetadataInput[] = []; const options: CommandMetadataOption[] = []; const exampleCommands = ['']; + const footnotes: Footnote[] = []; let groups: string[] = [CommandGroup.Hidden]; @@ -26,6 +27,7 @@ export class GenerateCommand extends Command implements CommandPreRun { inputs.push(...libmetadata.inputs || []); options.push(...libmetadata.options || []); description = (libmetadata.description || '').trim(); + footnotes.push(...libmetadata.footnotes || []); exampleCommands.push(...libmetadata.exampleCommands || []); } @@ -34,6 +36,7 @@ export class GenerateCommand extends Command implements CommandPreRun { type: 'project', summary: 'Automatically create framework features', description, + footnotes, inputs, options, groups, diff --git a/packages/ionic/src/commands/git/remote.ts b/packages/ionic/src/commands/git/remote.ts index 8a7171ea65..f551d18fb4 100644 --- a/packages/ionic/src/commands/git/remote.ts +++ b/packages/ionic/src/commands/git/remote.ts @@ -6,6 +6,8 @@ import { FatalException } from '../../lib/errors'; export class GitRemoteCommand extends Command { async getMetadata(): Promise { + const dashUrl = this.env.config.getDashUrl(); + return { name: 'remote', type: 'project', @@ -13,10 +15,14 @@ export class GitRemoteCommand extends Command { description: ` This command is used by ${chalk.green('ionic link')} when Ionic Appflow is used as the git host. -${chalk.green('ionic git remote')} will check the local repository for whether or not the git remote is properly set up. This command operates on the ${chalk.bold('ionic')} remote. For advanced configuration, see ${chalk.bold('Settings')} => ${chalk.bold('Git')} in the app settings of the Dashboard${chalk.cyan('[1]')}. - -${chalk.cyan('[1]')}: ${chalk.bold('https://dashboard.ionicframework.com')} +${chalk.green('ionic git remote')} will check the local repository for whether or not the git remote is properly set up. This command operates on the ${chalk.bold('ionic')} remote. For advanced configuration, see ${chalk.bold('Settings')} => ${chalk.bold('Git')} in the app settings of the Dashboard[^dashboard]. `, + footnotes: [ + { + id: 'dashboard', + url: dashUrl, + }, + ], }; } diff --git a/packages/ionic/src/commands/link.ts b/packages/ionic/src/commands/link.ts index 8aa6cfec3b..0c789fc994 100644 --- a/packages/ionic/src/commands/link.ts +++ b/packages/ionic/src/commands/link.ts @@ -38,15 +38,23 @@ Link apps on Ionic Appflow to local Ionic projects with this command. If the ${chalk.green('id')} argument is excluded, this command will prompt you to select an app from Ionic Appflow. -Ionic Appflow uses a git-based workflow to manage app updates. During the linking process, select ${chalk.bold('GitHub')} (recommended) or ${chalk.bold('Ionic Appflow')} as a git host. See our documentation${chalk.cyan('[1]')} for more information. +Ionic Appflow uses a git-based workflow to manage app updates. During the linking process, select ${chalk.bold('GitHub')} (recommended) or ${chalk.bold('Ionic Appflow')} as a git host. See our documentation[^appflow-git-basics] for more information. Ultimately, this command sets the ${chalk.bold('id')} property in ${chalk.bold(prettyPath(projectFile))}, which marks this app as linked. -If you are having issues linking, please get in touch with our Support${chalk.cyan('[2]')}. - -${chalk.cyan('[1]')}: ${chalk.bold('https://ionicframework.com/docs/appflow/basics/git')} -${chalk.cyan('[2]')}: ${chalk.bold('https://ionicframework.com/support/request')} +If you are having issues linking, please get in touch with our Support[^support-request]. `, + footnotes: [ + { + id: 'appflow-git-basics', + url: 'https://ionicframework.com/docs/appflow/basics/git', + shortUrl: 'https://ion.link/appflow-git-basics', + }, + { + id: 'support-request', + url: 'https://ion.link/support-request', + }, + ], exampleCommands: ['', 'a1b2c3d4'], inputs: [ { @@ -263,7 +271,7 @@ ${chalk.cyan('[2]')}: ${chalk.bold('https://ionicframework.com/support/request') this.env.log.info( `Ionic Appflow uses a git-based workflow to manage app updates.\n` + `You will be prompted to set up the git host and repository for this new app. See the docs${chalk.cyan('[1]')} for more information.\n\n` + - `${chalk.cyan('[1]')}: ${chalk.bold('https://ionicframework.com/docs/appflow/basics/git/')}` + `${chalk.cyan('[1]')}: ${chalk.bold('https://ion.link/appflow-git-basics')}` ); this.env.log.nl(); diff --git a/packages/ionic/src/commands/login.ts b/packages/ionic/src/commands/login.ts index f65cfcc531..9e19c50158 100644 --- a/packages/ionic/src/commands/login.ts +++ b/packages/ionic/src/commands/login.ts @@ -21,10 +21,14 @@ You can also use ${chalk.green('IONIC_EMAIL')} and ${chalk.green('IONIC_PASSWORD If you need to create an Ionic Appflow account, use ${chalk.green('ionic signup')}. -If you are having issues logging in, please get in touch with our Support${chalk.cyan('[1]')}. - -${chalk.cyan('[1]')}: ${chalk.bold('https://ionicframework.com/support/request')} +If you are having issues logging in, please get in touch with our Support[^support-request]. `, + footnotes: [ + { + id: 'support-request', + url: 'https://ion.link/support-request', + }, + ], exampleCommands: ['', 'john@example.com', 'hello@example.com secret'], inputs: [ { diff --git a/packages/ionic/src/commands/package/build.ts b/packages/ionic/src/commands/package/build.ts index eda2534158..e4c2600b6e 100644 --- a/packages/ionic/src/commands/package/build.ts +++ b/packages/ionic/src/commands/package/build.ts @@ -64,16 +64,20 @@ export class BuildCommand extends Command { description: ` This command creates a package build on Ionic Appflow. While the build is running, it prints the remote build log to the terminal. If the build is successful, it downloads the created app package file in the current directory. -Apart from ${chalk.green('--commit')}, every option can be specified using the full name setup within the Appflow Dashboard${chalk.cyan('[1]')}. +Apart from ${chalk.green('--commit')}, every option can be specified using the full name setup within the Appflow Dashboard[^dashboard]. The ${chalk.green('--security-profile')} option is mandatory for any iOS build but not for Android debug builds. Customizing the build: - The ${chalk.green('--environment')} and ${chalk.green('--native-config')} options can be used to customize the groups of values exposed to the build. - Override the preferred platform with ${chalk.green('--target-platform')}. This is useful for building older iOS apps. - -${chalk.cyan('[1]')}: ${chalk.bold(dashUrl)} `, + footnotes: [ + { + id: 'dashboard', + url: dashUrl, + }, + ], exampleCommands: [ 'android debug', 'ios development --security-profile="iOS Security Profile Name"', diff --git a/packages/ionic/src/commands/package/index.ts b/packages/ionic/src/commands/package/index.ts index edff0fff1c..2b413a5f29 100644 --- a/packages/ionic/src/commands/package/index.ts +++ b/packages/ionic/src/commands/package/index.ts @@ -11,7 +11,7 @@ export class PackageNamespace extends Namespace { Interface to execute commands about package builds on Ionic Appflow. Appflow package documentation: -- Overview: ${chalk.bold('https://ionicframework.com/docs/appflow/package/')} +- Overview: ${chalk.bold('https://ion.link/appflow-package-docs')} `, }; } diff --git a/packages/ionic/src/commands/serve.ts b/packages/ionic/src/commands/serve.ts index daff4e0099..e9fee22743 100644 --- a/packages/ionic/src/commands/serve.ts +++ b/packages/ionic/src/commands/serve.ts @@ -1,4 +1,4 @@ -import { CommandGroup, OptionGroup } from '@ionic/cli-framework'; +import { CommandGroup, Footnote, OptionGroup } from '@ionic/cli-framework'; import { sleepForever } from '@ionic/cli-framework/utils/process'; import chalk from 'chalk'; import * as lodash from 'lodash'; @@ -69,6 +69,7 @@ export class ServeCommand extends Command implements CommandPreRun { ]; const exampleCommands = ['', '--local', '--lab']; + const footnotes: Footnote[] = []; let description = ` Easily spin up a development server which launches in your browser. It watches for changes in your source files and automatically reloads with the updated build. @@ -84,6 +85,7 @@ Try the ${chalk.green('--lab')} option to see multiple platforms at once.`; groups = libmetadata.groups || []; options.push(...libmetadata.options || []); description += `\n\n${(libmetadata.description || '').trim()}`; + footnotes.push(...libmetadata.footnotes || []); exampleCommands.push(...libmetadata.exampleCommands || []); } @@ -92,6 +94,7 @@ Try the ${chalk.green('--lab')} option to see multiple platforms at once.`; type: 'project', summary: 'Start a local dev server for app dev/testing', description, + footnotes, groups, exampleCommands, options, diff --git a/packages/ionic/src/commands/signup.ts b/packages/ionic/src/commands/signup.ts index 19abd16d60..66ad962ddf 100644 --- a/packages/ionic/src/commands/signup.ts +++ b/packages/ionic/src/commands/signup.ts @@ -11,12 +11,16 @@ export class SignupCommand extends Command { type: 'global', summary: 'Create an account for Ionic Appflow', description: ` -Learn more about Ionic Appflow: ${chalk.bold('https://ionicframework.com/appflow')} +Learn more about Ionic Appflow: ${chalk.bold('https://ion.link/appflow')} -If you are having issues signing up, please get in touch with our Support${chalk.cyan('[1]')}. - -${chalk.cyan('[1]')}: ${chalk.bold('https://ionicframework.com/support/request')} +If you are having issues signing up, please get in touch with our Support[^support-request]. `, + footnotes: [ + { + id: 'support-request', + url: 'https://ion.link/support-request', + }, + ], }; } diff --git a/packages/ionic/src/commands/ssh/index.ts b/packages/ionic/src/commands/ssh/index.ts index 6afe958d43..585ed6c8c1 100644 --- a/packages/ionic/src/commands/ssh/index.ts +++ b/packages/ionic/src/commands/ssh/index.ts @@ -4,16 +4,22 @@ import { CommandMap, Namespace } from '../../lib/namespace'; export class SSHNamespace extends Namespace { async getMetadata() { + const dashUrl = this.env.config.getDashUrl(); + return { name: 'ssh', summary: 'Commands for configuring SSH keys', description: ` -These commands help automate your SSH configuration for Ionic Appflow. As an alternative, SSH configuration can be done entirely manually by visiting your Personal Settings${chalk.cyan('[1]')}. +These commands help automate your SSH configuration for Ionic Appflow. As an alternative, SSH configuration can be done entirely manually by visiting your Personal Settings[^dashboard-settings-ssh-keys]. To begin, run ${chalk.green('ionic ssh setup')}, which lets you use existing keys or generate new ones just for Ionic. - -${chalk.cyan('[1]')}: ${chalk.bold('https://dashboard.ionicframework.com/settings/ssh-keys')} `, + footnotes: [ + { + id: 'dashboard-settings-ssh-keys', + url: `${dashUrl}/settings/ssh-keys`, + }, + ], }; } diff --git a/packages/ionic/src/commands/ssh/setup.ts b/packages/ionic/src/commands/ssh/setup.ts index 42da3a760a..26105f116b 100644 --- a/packages/ionic/src/commands/ssh/setup.ts +++ b/packages/ionic/src/commands/ssh/setup.ts @@ -10,18 +10,27 @@ import { SSHBaseCommand } from './base'; export class SSHSetupCommand extends SSHBaseCommand { async getMetadata(): Promise { + const dashUrl = this.env.config.getDashUrl(); + return { name: 'setup', type: 'global', summary: 'Setup your Ionic Appflow SSH keys automatically', description: ` -This command offers a setup wizard for Ionic Appflow SSH keys using a series of prompts. For more control, see the commands available for managing SSH keys with the ${chalk.green('ionic ssh --help')} command. For an entirely manual approach, see ${chalk.bold('Personal Settings')} => ${chalk.bold('SSH Keys')} in the Dashboard${chalk.cyan('[1]')}. - -If you are having issues setting up SSH keys, please get in touch with our Support${chalk.cyan('[2]')}. +This command offers a setup wizard for Ionic Appflow SSH keys using a series of prompts. For more control, see the commands available for managing SSH keys with the ${chalk.green('ionic ssh --help')} command. For an entirely manual approach, see ${chalk.bold('Personal Settings')} => ${chalk.bold('SSH Keys')} in the Dashboard[^dashboard-settings-ssh-keys]. -${chalk.cyan('[1]')}: ${chalk.bold('https://dashboard.ionicframework.com/settings/ssh-keys')} -${chalk.cyan('[2]')}: ${chalk.bold('https://ionicframework.com/support/request')} +If you are having issues setting up SSH keys, please get in touch with our Support[^support-request]. `, + footnotes: [ + { + id: 'dashboard-settings-ssh-keys', + url: `${dashUrl}/settings/ssh-keys`, + }, + { + id: 'support-request', + url: 'https://ion.link/support-request', + }, + ], }; } diff --git a/packages/ionic/src/commands/ssh/use.ts b/packages/ionic/src/commands/ssh/use.ts index d16f628708..fc3233bc9f 100644 --- a/packages/ionic/src/commands/ssh/use.ts +++ b/packages/ionic/src/commands/ssh/use.ts @@ -15,12 +15,16 @@ export class SSHUseCommand extends SSHBaseCommand { type: 'global', summary: 'Set your active Ionic SSH key', description: ` -This command modifies the SSH configuration file (${chalk.bold('~/.ssh/config')}) to set an active private key for the ${chalk.bold('git.ionicjs.com')} host. Read more about SSH configuration by running the ${chalk.green('man ssh_config')} command or by visiting online man pages${chalk.cyan('[1]')}. +This command modifies the SSH configuration file (${chalk.bold('~/.ssh/config')}) to set an active private key for the ${chalk.bold('git.ionicjs.com')} host. Read more about SSH configuration by running the ${chalk.green('man ssh_config')} command or by visiting online man pages[^ssh-config-docs]. Before making changes, ${chalk.green('ionic ssh use')} will print a diff and ask for permission to write the file. - -${chalk.cyan('[1]')}: ${chalk.bold('https://linux.die.net/man/5/ssh_config')} `, + footnotes: [ + { + id: 'ssh-config-docs', + url: 'https://linux.die.net/man/5/ssh_config', + }, + ], inputs: [ { name: 'key-path', diff --git a/packages/ionic/src/commands/start.ts b/packages/ionic/src/commands/start.ts index a9afb72443..f8a787ccd3 100644 --- a/packages/ionic/src/commands/start.ts +++ b/packages/ionic/src/commands/start.ts @@ -56,9 +56,7 @@ Running ${chalk.green('ionic start')} without any arguments will prompt you for The first argument is your app's ${chalk.green('name')}. Don't worry--you can always change this later. The ${chalk.green('--project-id')} is generated from ${chalk.green('name')} unless explicitly specified. -The second argument is the ${chalk.green('template')} from which to generate your app. You can list all templates with the ${chalk.green('--list')} option. For more information on starter templates, see the CLI documentation${chalk.cyan('[1]')}. You can also specify a git repository URL for ${chalk.green('template')}, in which case the existing project will be cloned. - -${chalk.cyan('[1]')}: ${chalk.bold('https://ionicframework.com/docs/cli/starters.html')} +The second argument is the ${chalk.green('template')} from which to generate your app. You can list all templates with the ${chalk.green('--list')} option. You can also specify a git repository URL for ${chalk.green('template')}, in which case the existing project will be cloned. `, exampleCommands: [ '', @@ -691,13 +689,12 @@ ${chalk.cyan('[1]')}: ${chalk.bold('https://ionicframework.com/docs/cli/starters const steps = [ `Go to your ${cloned ? 'cloned' : 'newly created'} project: ${chalk.green(`cd ${prettyPath(projectDir)}`)}`, `Run ${chalk.green('ionic serve')} within the app directory to see your app`, - `Build features and components: ${chalk.bold('https://beta.ionicframework.com/docs/building/scaffolding')}`, + `Build features and components: ${chalk.bold('https://ion.link/scaffolding-docs')}`, `Get Ionic DevApp for easy device testing: ${chalk.bold('https://bit.ly/ionic-dev-app')}`, ]; if (linkConfirmed) { - steps.push(`Finish setting up Ionic Appflow Error Monitoring: ${chalk.bold('https://ionicframework.com/docs/appflow/monitoring/#getting-started')}`); - steps.push(`Finally, push your code to Ionic Appflow to perform real-time updates, and more: ${chalk.green('git push ionic master')}`); + steps.push(`Push your code to Ionic Appflow to perform real-time updates, and more: ${chalk.green('git push ionic master')}`); } this.env.log.info(`${chalk.bold('Next Steps')}:\n${steps.map(s => `- ${s}`).join('\n')}`); diff --git a/packages/ionic/src/index.ts b/packages/ionic/src/index.ts index bd85ec3747..5ee329cd78 100644 --- a/packages/ionic/src/index.ts +++ b/packages/ionic/src/index.ts @@ -149,7 +149,7 @@ export async function run(pargv: string[]): Promise { } else if (err.code && err.code === 'ENOTFOUND' || err.code === 'ECONNREFUSED') { ienv.log.error( `Network connectivity error occurred, are you offline?\n` + - `If you are behind a firewall and need to configure proxy settings, see: ${chalk.bold('https://ionicframework.com/docs/cli/configuring.html#using-a-proxy')}\n\n` + + `If you are behind a firewall and need to configure proxy settings, see: ${chalk.bold('https://ion.link/cli-proxy-docs')}\n\n` + chalk.red(String(err.stack ? err.stack : err)) ); } else if (isExitCodeException(err)) { diff --git a/packages/ionic/src/lib/doctor/ailments/index.ts b/packages/ionic/src/lib/doctor/ailments/index.ts index cbe649a2bd..621bed1dc6 100644 --- a/packages/ionic/src/lib/doctor/ailments/index.ts +++ b/packages/ionic/src/lib/doctor/ailments/index.ts @@ -194,7 +194,7 @@ export class IonicNativeOldVersionInstalled extends Ailment { return [ { message: `Run ${chalk.green(args.join(' '))}` }, - { message: `Refer to ${chalk.bold('https://ionicframework.com/docs/native')} for installation & usage instructions` }, + { message: `Refer to ${chalk.bold('https://ion.link/native-docs')} for installation & usage instructions` }, ]; } } diff --git a/packages/ionic/src/lib/project/angular/build.ts b/packages/ionic/src/lib/project/angular/build.ts index 5b8b608f1f..a118e7c0f9 100644 --- a/packages/ionic/src/lib/project/angular/build.ts +++ b/packages/ionic/src/lib/project/angular/build.ts @@ -31,9 +31,14 @@ export class AngularBuildRunner extends BuildRunner { return { groups: [CommandGroup.Beta], description: ` -${chalk.green('ionic build')} uses the Angular CLI. Use ${chalk.green('ng build --help')} to list all Angular CLI options for building your app. See the ${chalk.green('ng build')} docs${chalk.cyan('[1]')} for explanations. Options not listed below are considered advanced and can be passed to the ${chalk.green('ng')} CLI using the ${chalk.green('--')} separator after the Ionic CLI arguments. See the examples. - -${chalk.cyan('[1]')}: ${chalk.bold('https://angular.io/cli/build')}`, +${chalk.green('ionic build')} uses the Angular CLI. Use ${chalk.green('ng build --help')} to list all Angular CLI options for building your app. See the ${chalk.green('ng build')} docs[^ng-build-docs] for explanations. Options not listed below are considered advanced and can be passed to the ${chalk.green('ng')} CLI using the ${chalk.green('--')} separator after the Ionic CLI arguments. See the examples. +`, + footnotes: [ + { + id: 'ng-build-docs', + url: 'https://angular.io/cli/build', + }, + ], options: [ { name: 'prod', diff --git a/packages/ionic/src/lib/project/angular/serve.ts b/packages/ionic/src/lib/project/angular/serve.ts index 6f0fc04702..694c323dfc 100644 --- a/packages/ionic/src/lib/project/angular/serve.ts +++ b/packages/ionic/src/lib/project/angular/serve.ts @@ -21,12 +21,20 @@ export class AngularServeRunner extends ServeRunner { return { groups: [CommandGroup.Beta], description: ` -${chalk.green('ionic serve')} uses the Angular CLI. Use ${chalk.green('ng serve --help')} to list all Angular CLI options for serving your app. See the ${chalk.green('ng serve')} docs${chalk.cyan('[1]')} for explanations. Options not listed below are considered advanced and can be passed to the Angular CLI using the ${chalk.green('--')} separator after the Ionic CLI arguments. See the examples. +${chalk.green('ionic serve')} uses the Angular CLI. Use ${chalk.green('ng serve --help')} to list all Angular CLI options for serving your app. See the ${chalk.green('ng serve')} docs[^ng-serve-docs] for explanations. Options not listed below are considered advanced and can be passed to the Angular CLI using the ${chalk.green('--')} separator after the Ionic CLI arguments. See the examples. -The dev server can use HTTPS via the ${chalk.green('--ssl')} option ${chalk.bold.red('(experimental)')}. There are several known issues with HTTPS. See issue #3305${chalk.cyan('[2]')}. - -${chalk.cyan('[1]')}: ${chalk.bold('https://angular.io/cli/serve')} -${chalk.cyan('[2]')}: ${chalk.bold('https://github.com/ionic-team/ionic-cli/issues/3305')}`, +The dev server can use HTTPS via the ${chalk.green('--ssl')} option ${chalk.bold.red('(experimental)')}. There are several known issues with HTTPS. See issue #3305[^issue-3305]. +`, + footnotes: [ + { + id: 'ng-serve-docs', + url: 'https://angular.io/cli/serve', + }, + { + id: 'issue-3305', + url: 'https://github.com/ionic-team/ionic-cli/issues/3305', + }, + ], options: [ { name: 'ssl', diff --git a/packages/ionic/src/lib/project/index.ts b/packages/ionic/src/lib/project/index.ts index bf24f94f3c..ebfff7cdd0 100644 --- a/packages/ionic/src/lib/project/index.ts +++ b/packages/ionic/src/lib/project/index.ts @@ -509,7 +509,7 @@ export abstract class Project implements IProject { } async getDocsUrl(): Promise { - return 'https://ionicframework.com/docs'; + return 'https://ion.link/docs'; } async getSourceDir(): Promise { diff --git a/packages/ionic/src/lib/project/ionic-angular/build.ts b/packages/ionic/src/lib/project/ionic-angular/build.ts index 73afcc51dd..068f746b26 100644 --- a/packages/ionic/src/lib/project/ionic-angular/build.ts +++ b/packages/ionic/src/lib/project/ionic-angular/build.ts @@ -24,9 +24,15 @@ export class IonicAngularBuildRunner extends BuildRunner> { return { - description: `${chalk.green('ionic build')} uses ${chalk.bold('@ionic/app-scripts')}. See the project's ${chalk.bold('README.md')}${chalk.cyan('[1]')} for documentation. Options not listed below are considered advanced and can be passed to the ${chalk.green('ionic-app-scripts')} CLI using the ${chalk.green('--')} separator after the Ionic CLI arguments. See the examples. - -${chalk.cyan('[1]')}: ${chalk.bold('https://github.com/ionic-team/ionic-app-scripts/blob/master/README.md')}`, + description: ` +${chalk.green('ionic build')} uses ${chalk.bold('@ionic/app-scripts')}. See the project's ${chalk.bold('README.md')}[^app-scripts-readme] for documentation. Options not listed below are considered advanced and can be passed to the ${chalk.green('ionic-app-scripts')} CLI using the ${chalk.green('--')} separator after the Ionic CLI arguments. See the examples. + `, + footnotes: [ + { + id: 'app-scripts-readme', + url: 'https://github.com/ionic-team/ionic-app-scripts/blob/master/README.md', + }, + ], options: [ { name: 'source-map', diff --git a/packages/ionic/src/lib/project/ionic1/index.ts b/packages/ionic/src/lib/project/ionic1/index.ts index d7f57f483f..66fb689c52 100644 --- a/packages/ionic/src/lib/project/ionic1/index.ts +++ b/packages/ionic/src/lib/project/ionic1/index.ts @@ -77,7 +77,7 @@ export class Ionic1Project extends Project { } async getDocsUrl(): Promise { - return 'https://ionicframework.com/docs/v1/'; + return 'https://ion.link/v1-docs'; } // this method search not only package.json diff --git a/packages/ionic/src/lib/start.ts b/packages/ionic/src/lib/start.ts index 15c808c1fe..4c2e7de647 100644 --- a/packages/ionic/src/lib/start.ts +++ b/packages/ionic/src/lib/start.ts @@ -48,7 +48,7 @@ export async function getIonicProText() { ${chalk.bold(`${emoji('🔥', '*')} IONIC APPFLOW ${emoji('🔥', '*')}`)}\n Supercharge your Ionic development with the ${chalk.bold('Ionic Appflow')} SDK\n - ${emoji('📲', '')} Push remote updates and skip the app store queue\n - Learn more about Ionic Appflow: ${chalk.bold('https://ionicframework.com/appflow')} + Learn more about Ionic Appflow: ${chalk.bold('https://ion.link/appflow')} `; return `${msg}\n${advertisementSeparator.repeat(60)}\n\n`; From 9cfe7f3a503b0c723d5cdc00e99d8475b094f954 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Thu, 17 Jan 2019 16:02:15 -0600 Subject: [PATCH 07/14] chore(docs): generate markdown from link footnotes --- packages/cli-scripts/src/docs/index.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/cli-scripts/src/docs/index.ts b/packages/cli-scripts/src/docs/index.ts index d6a10b3fed..189c5770ca 100644 --- a/packages/cli-scripts/src/docs/index.ts +++ b/packages/cli-scripts/src/docs/index.ts @@ -1,4 +1,4 @@ -import { Command, CommandHelpSchemaInput, CommandHelpSchemaOption, CommandLineInputs, CommandLineOptions, CommandMetadata } from '@ionic/cli-framework'; +import { Command, CommandHelpSchemaFootnote, CommandHelpSchemaInput, CommandHelpSchemaOption, CommandLineInputs, CommandLineOptions, CommandMetadata } from '@ionic/cli-framework'; import { strcmp } from '@ionic/cli-framework/utils/string'; import { mkdirp, remove, writeFile } from '@ionic/utils-fs'; import chalk from 'chalk'; @@ -51,12 +51,27 @@ export class DocsCommand extends Command { return { ...command, summary: stripAnsi(links2md(ansi2md(command.summary))).trim(), - description: stripAnsi(links2md(ansi2md(command.description))).trim(), + description: await this.formatFootnotes(stripAnsi(links2md(ansi2md(command.description))).trim(), command.footnotes), + footnotes: command.footnotes.filter(footnote => footnote.type !== 'link'), // we format link footnotes in `formatFootnotes()` inputs: await Promise.all(command.inputs.map(input => this.extractInput(input))), options: await Promise.all(command.options.map(opt => this.extractOption(opt))), }; } + private async formatFootnotes(description: string, footnotes: ReadonlyArray): Promise { + return description.replace(/(\S+)\[\^([A-z0-9-]+)\]/g, (match, p1, p2) => { + const m = Number.parseInt(p2, 10); + const id = !Number.isNaN(m) ? m : p2; + const foundFootnote = footnotes.find(footnote => footnote.id === id); + + if (!foundFootnote) { + throw new Error('Bad footnote.'); + } + + return foundFootnote.type === 'link' ? `[${p1}](${foundFootnote.url})` : match; // TODO: handle text footnotes + }); + } + private async extractInput(input: CommandHelpSchemaInput): Promise { return { ...input, From 9898fa839935cbc6a35b255fc3838a43b76bf5e6 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Fri, 18 Jan 2019 16:26:29 -0600 Subject: [PATCH 08/14] feat(custom): run ionic:build/ionic:serve scripts resolves https://github.com/ionic-team/ionic-cli/issues/3819 --- packages/ionic/src/definitions.ts | 4 + packages/ionic/src/lib/build.ts | 59 ++++++++++++++- .../ionic/src/lib/project/custom/build.ts | 37 ++++++++++ .../ionic/src/lib/project/custom/index.ts | 21 +++--- .../ionic/src/lib/project/custom/serve.ts | 47 ++++++++++++ packages/ionic/src/lib/serve.ts | 74 +++++++++++++++++-- 6 files changed, 223 insertions(+), 19 deletions(-) create mode 100644 packages/ionic/src/lib/project/custom/build.ts create mode 100644 packages/ionic/src/lib/project/custom/serve.ts diff --git a/packages/ionic/src/definitions.ts b/packages/ionic/src/definitions.ts index 8694f56ee5..cf44940222 100644 --- a/packages/ionic/src/definitions.ts +++ b/packages/ionic/src/definitions.ts @@ -527,6 +527,8 @@ export interface IonicAngularBuildOptions extends BuildOptions<'ionic-angular'> export interface Ionic1BuildOptions extends BuildOptions<'ionic1'> {} +export interface CustomBuildOptions extends BuildOptions<'custom'> {} + export interface GenerateOptions { type: string; name: string; @@ -585,6 +587,8 @@ export interface Ionic1ServeOptions extends ServeOptions { notificationPort: number; } +export interface CustomServeOptions extends ServeOptions {} + export interface LabServeDetails { projectType: ProjectType; address: string; diff --git a/packages/ionic/src/lib/build.ts b/packages/ionic/src/lib/build.ts index 0185f9ca58..418320fb6f 100644 --- a/packages/ionic/src/lib/build.ts +++ b/packages/ionic/src/lib/build.ts @@ -2,7 +2,7 @@ import { BaseError, ERROR_SHELL_COMMAND_NOT_FOUND, OptionGroup, PromptModule, Sh import chalk from 'chalk'; import * as Debug from 'debug'; -import { BaseBuildOptions, BuildOptions, CommandLineInputs, CommandLineOptions, CommandMetadata, CommandMetadataOption, IConfig, ILogger, IProject, IShell, Runner } from '../definitions'; +import { BaseBuildOptions, BuildOptions, CommandLineInputs, CommandLineOptions, CommandMetadata, CommandMetadataOption, IConfig, ILogger, IProject, IShell, NpmClient, Runner } from '../definitions'; import { BuildCLIProgramNotFoundException, FatalException, RunnerException } from './errors'; import { Hook } from './hooks'; @@ -40,6 +40,10 @@ export abstract class BuildRunner> implements Runner abstract createOptionsFromCommandLine(inputs: CommandLineInputs, options: CommandLineOptions): T; abstract buildProject(options: T): Promise; + getPkgManagerBuildCLI(): PkgManagerBuildCLI { + return this.e.config.get('npmClient') === 'npm' ? new NpmBuildCLI(this.e) : new YarnBuildCLI(this.e); + } + createBaseOptionsFromCommandLine(inputs: CommandLineInputs, options: CommandLineOptions): BaseBuildOptions { const separatedArgs = options['--']; const platform = options['platform'] ? String(options['platform']) : undefined; @@ -123,6 +127,11 @@ export abstract class BuildCLI { */ abstract readonly script?: string; + /** + * If true, the Build CLI will not prompt to be installed. + */ + readonly global: boolean = false; + private _resolvedProgram?: string; constructor(protected readonly e: BuildRunnerDeps) {} @@ -140,6 +149,16 @@ export abstract class BuildCLI { */ protected abstract buildArgs(options: T): Promise; + async resolveScript(): Promise { + if (typeof this.script === 'undefined') { + return; + } + + const pkg = await this.e.project.requirePackageJson(); + + return pkg.scripts && pkg.scripts[this.script]; + } + async build(options: T): Promise { this._resolvedProgram = await this.resolveProgram(); @@ -154,6 +173,11 @@ export abstract class BuildCLI { throw e; } + if (this.global) { + this.e.log.nl(); + throw new FatalException(`${chalk.green(this.pkg)} is required for this command to work properly.`); + } + this.e.log.nl(); this.e.log.info( `Looks like ${chalk.green(this.pkg)} isn't installed in this project.\n` + @@ -189,9 +213,7 @@ export abstract class BuildCLI { if (typeof this.script !== 'undefined') { debug(`Looking for ${chalk.cyan(this.script)} npm script.`); - const pkg = await this.e.project.requirePackageJson(); - - if (pkg.scripts && pkg.scripts[this.script]) { + if (await this.resolveScript()) { debug(`Using ${chalk.cyan(this.script)} npm script.`); return this.e.config.get('npmClient'); } @@ -223,6 +245,35 @@ export abstract class BuildCLI { } } +abstract class PkgManagerBuildCLI extends BuildCLI { + readonly abstract program: NpmClient; + readonly global = true; + readonly script = BUILD_SCRIPT; + + protected async resolveProgram(): Promise { + return this.program; + } + + protected async buildArgs(options: BaseBuildOptions): Promise { + const { pkgManagerArgs } = await import('./utils/npm'); + const [ , ...pkgArgs ] = await pkgManagerArgs(this.program, { command: 'run', script: this.script, scriptArgs: [...options['--'] || []] }); + + return pkgArgs; + } +} + +export class NpmBuildCLI extends PkgManagerBuildCLI { + readonly name = 'npm CLI'; + readonly pkg = 'npm'; + readonly program = 'npm'; +} + +export class YarnBuildCLI extends PkgManagerBuildCLI { + readonly name = 'Yarn'; + readonly pkg = 'yarn'; + readonly program = 'yarn'; +} + class BuildBeforeHook extends Hook { readonly name = 'build:before'; } diff --git a/packages/ionic/src/lib/project/custom/build.ts b/packages/ionic/src/lib/project/custom/build.ts new file mode 100644 index 0000000000..ce0b0f92ee --- /dev/null +++ b/packages/ionic/src/lib/project/custom/build.ts @@ -0,0 +1,37 @@ +import chalk from 'chalk'; + +import { CommandLineInputs, CommandLineOptions, CommandMetadata, CustomBuildOptions } from '../../../definitions'; +import { BuildRunner, BuildRunnerDeps } from '../../build'; +import { RunnerException } from '../../errors'; + +export class CustomBuildRunner extends BuildRunner { + constructor(protected readonly e: BuildRunnerDeps) { + super(); + } + + async getCommandMetadata(): Promise> { + return {}; + } + + createOptionsFromCommandLine(inputs: CommandLineInputs, options: CommandLineOptions): CustomBuildOptions { + const baseOptions = super.createBaseOptionsFromCommandLine(inputs, options); + + return { + ...baseOptions, + type: 'custom', + }; + } + + async buildProject(options: CustomBuildOptions): Promise { + const cli = this.getPkgManagerBuildCLI(); + + if (!await cli.resolveScript()) { + throw new RunnerException( + `Cannot perform build.\n` + + `Since you're using the ${chalk.bold('custom')} project type, you must provide the ${chalk.green(cli.script)} npm script so the Ionic CLI can build your project.` + ); + } + + await cli.build(options); + } +} diff --git a/packages/ionic/src/lib/project/custom/index.ts b/packages/ionic/src/lib/project/custom/index.ts index 1886d1d11a..76929b19ad 100644 --- a/packages/ionic/src/lib/project/custom/index.ts +++ b/packages/ionic/src/lib/project/custom/index.ts @@ -3,6 +3,9 @@ import chalk from 'chalk'; import { Project } from '../'; import { RunnerNotFoundException } from '../../errors'; +import * as ζbuild from './build'; +import * as ζserve from './serve'; + export class CustomProject extends Project { readonly type: 'custom' = 'custom'; @@ -13,18 +16,16 @@ export class CustomProject extends Project { return false; } - async requireBuildRunner(): Promise { - throw new RunnerNotFoundException( - `Cannot perform build for custom projects.\n` + - `Since you're using the ${chalk.bold('custom')} project type, this command won't work. The Ionic CLI doesn't know how to build custom projects.` - ); + async requireBuildRunner(): Promise<ζbuild.CustomBuildRunner> { + const { CustomBuildRunner } = await import('./build'); + const deps = { ...this.e, project: this }; + return new CustomBuildRunner(deps); } - async requireServeRunner(): Promise { - throw new RunnerNotFoundException( - `Cannot perform serve for custom projects.\n` + - `Since you're using the ${chalk.bold('custom')} project type, this command won't work. The Ionic CLI doesn't know how to serve custom projects.` - ); + async requireServeRunner(): Promise<ζserve.CustomServeRunner> { + const { CustomServeRunner } = await import('./serve'); + const deps = { ...this.e, project: this }; + return new CustomServeRunner(deps); } async requireGenerateRunner(): Promise { diff --git a/packages/ionic/src/lib/project/custom/serve.ts b/packages/ionic/src/lib/project/custom/serve.ts new file mode 100644 index 0000000000..9a38d6991a --- /dev/null +++ b/packages/ionic/src/lib/project/custom/serve.ts @@ -0,0 +1,47 @@ +import { findClosestOpenPort } from '@ionic/utils-network'; +import chalk from 'chalk'; + +import { CommandMetadata, CustomServeOptions, ServeDetails } from '../../../definitions'; +import { RunnerException } from '../../errors'; +import { BIND_ALL_ADDRESS, LOCAL_ADDRESSES, ServeRunner, ServeRunnerDeps } from '../../serve'; + +export class CustomServeRunner extends ServeRunner { + constructor(protected readonly e: ServeRunnerDeps) { + super(); + } + + async getCommandMetadata(): Promise> { + return {}; + } + + modifyOpenURL(url: string, options: CustomServeOptions): string { + return url; + } + + async serveProject(options: CustomServeOptions): Promise { + const cli = this.getPkgManagerServeCLI(); + + if (!await cli.resolveScript()) { + throw new RunnerException( + `Cannot perform serve.\n` + + `Since you're using the ${chalk.bold('custom')} project type, you must provide the ${chalk.green(cli.script)} npm script so the Ionic CLI can serve your project.` + ); + } + + const [ externalIP, availableInterfaces ] = await this.selectExternalIP(options); + + const port = options.port = await findClosestOpenPort(options.port); + + await cli.serve(options); + + return { + custom: true, + protocol: 'http', + localAddress: 'localhost', + externalAddress: externalIP, + externalNetworkInterfaces: availableInterfaces, + port, + externallyAccessible: ![BIND_ALL_ADDRESS, ...LOCAL_ADDRESSES].includes(externalIP), + }; + } +} diff --git a/packages/ionic/src/lib/serve.ts b/packages/ionic/src/lib/serve.ts index 746f777502..da579b894c 100644 --- a/packages/ionic/src/lib/serve.ts +++ b/packages/ionic/src/lib/serve.ts @@ -1,4 +1,4 @@ -import { BaseError, LOGGER_LEVELS, OptionGroup, PromptModule, createPrefixedFormatter } from '@ionic/cli-framework'; +import { BaseError, LOGGER_LEVELS, OptionGroup, ParsedArgs, PromptModule, createPrefixedFormatter, unparseArgs } from '@ionic/cli-framework'; import { killProcessTree, onBeforeExit, processExit } from '@ionic/cli-framework/utils/process'; import { str2num } from '@ionic/cli-framework/utils/string'; import { readJson } from '@ionic/utils-fs'; @@ -14,7 +14,7 @@ import * as stream from 'stream'; import * as through2 from 'through2'; import { ASSETS_DIRECTORY } from '../constants'; -import { CommandLineInputs, CommandLineOptions, CommandMetadata, CommandMetadataOption, DevAppDetails, IConfig, ILogger, IProject, IShell, IonicEnvironmentFlags, LabServeDetails, Runner, ServeDetails, ServeOptions } from '../definitions'; +import { CommandLineInputs, CommandLineOptions, CommandMetadata, CommandMetadataOption, DevAppDetails, IConfig, ILogger, IProject, IShell, IonicEnvironmentFlags, LabServeDetails, NpmClient, Runner, ServeDetails, ServeOptions } from '../definitions'; import { isCordovaPackageJson } from '../guards'; import { FatalException, RunnerException, ServeCLIProgramNotFoundException } from './errors'; @@ -89,6 +89,10 @@ export abstract class ServeRunner implements Runner; abstract modifyOpenURL(url: string, options: T): string; + getPkgManagerServeCLI(): PkgManagerServeCLI { + return this.e.config.get('npmClient') === 'npm' ? new NpmServeCLI(this.e) : new YarnServeCLI(this.e); + } + createOptionsFromCommandLine(inputs: CommandLineInputs, options: CommandLineOptions): ServeOptions { const separatedArgs = options['--']; @@ -425,6 +429,11 @@ export abstract class ServeCLI extends EventEmitter { */ abstract readonly script?: string; + /** + * If true, the Serve CLI will not prompt to be installed. + */ + readonly global: boolean = false; + private _resolvedProgram?: string; constructor(protected readonly e: ServeRunnerDeps) { @@ -469,6 +478,16 @@ export abstract class ServeCLI extends EventEmitter { return true; } + async resolveScript(): Promise { + if (typeof this.script === 'undefined') { + return; + } + + const pkg = await this.e.project.requirePackageJson(); + + return pkg.scripts && pkg.scripts[this.script]; + } + async serve(options: T): Promise { this._resolvedProgram = await this.resolveProgram(); @@ -491,6 +510,11 @@ export abstract class ServeCLI extends EventEmitter { throw e; } + if (this.global) { + this.e.log.nl(); + throw new FatalException(`${chalk.green(this.pkg)} is required for this command to work properly.`); + } + this.e.log.nl(); this.e.log.info( `Looks like ${chalk.green(this.pkg)} isn't installed in this project.\n` + @@ -570,9 +594,7 @@ export abstract class ServeCLI extends EventEmitter { if (typeof this.script !== 'undefined') { debug(`Looking for ${chalk.cyan(this.script)} npm script.`); - const pkg = await this.e.project.requirePackageJson(); - - if (pkg.scripts && pkg.scripts[this.script]) { + if (await this.resolveScript()) { debug(`Using ${chalk.cyan(this.script)} npm script.`); return this.e.config.get('npmClient'); } @@ -616,6 +638,48 @@ export abstract class ServeCLI extends EventEmitter { } } +abstract class PkgManagerServeCLI extends ServeCLI { + readonly abstract program: NpmClient; + readonly global = true; + readonly script = SERVE_SCRIPT; + + protected async resolveProgram(): Promise { + return this.program; + } + + protected async buildArgs(options: ServeOptions): Promise { + const { pkgManagerArgs } = await import('./utils/npm'); + + // The Ionic CLI decides the host/port of the dev server, so --host and + // --port are provided to the downstream npm script as a best-effort + // attempt. + const args: ParsedArgs = { + _: [], + host: options.address, + port: options.port.toString(), + }; + + const scriptArgs = [...unparseArgs(args), ...options['--'] || []]; + const [ , ...pkgArgs ] = await pkgManagerArgs(this.program, { command: 'run', script: this.script, scriptArgs }); + + return pkgArgs; + } +} + +export class NpmServeCLI extends PkgManagerServeCLI { + readonly name = 'npm CLI'; + readonly pkg = 'npm'; + readonly program = 'npm'; + readonly prefix = 'npm'; +} + +export class YarnServeCLI extends PkgManagerServeCLI { + readonly name = 'Yarn'; + readonly pkg = 'yarn'; + readonly program = 'yarn'; + readonly prefix = 'yarn'; +} + interface IonicLabServeCLIOptions extends Readonly { readonly serveDetails: Readonly; } From db289d73d803a13bebaa3406c8245ac6d5ec9ce6 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Tue, 22 Jan 2019 10:26:25 -0600 Subject: [PATCH 09/14] refactor(cordova): reuse cordova compile options --- packages/ionic/src/commands/cordova/base.ts | 25 +++++++--- packages/ionic/src/commands/cordova/build.ts | 4 +- .../ionic/src/commands/cordova/compile.ts | 4 +- packages/ionic/src/commands/cordova/run.ts | 47 +------------------ 4 files changed, 23 insertions(+), 57 deletions(-) diff --git a/packages/ionic/src/commands/cordova/base.ts b/packages/ionic/src/commands/cordova/base.ts index 206ad6a422..6bf69ad006 100644 --- a/packages/ionic/src/commands/cordova/base.ts +++ b/packages/ionic/src/commands/cordova/base.ts @@ -9,33 +9,33 @@ import { Command } from '../../lib/command'; import { FatalException } from '../../lib/errors'; import { runCommand } from '../../lib/executor'; -export const COMMON_CORDOVA_BUILD_COMMAND_OPTIONS: CommandMetadataOption[] = [ +export const CORDOVA_COMPILE_OPTIONS: CommandMetadataOption[] = [ { name: 'debug', - summary: 'Create a debug build', + summary: 'Mark as a debug build', type: Boolean, groups: ['cordova'], hint: chalk.dim('[cordova]'), }, { name: 'release', - summary: 'Create a release build', + summary: 'Mark as a release build', type: Boolean, groups: ['cordova'], hint: chalk.dim('[cordova]'), }, { name: 'device', - summary: 'Create a build for a device', + summary: 'Deploy build to a device', type: Boolean, - groups: ['cordova'], + groups: ['cordova', 'native-run'], hint: chalk.dim('[cordova]'), }, { name: 'emulator', - summary: 'Create a build for an emulator', + summary: 'Deploy build to an emulator', type: Boolean, - groups: ['cordova'], + groups: ['cordova', 'native-run'], hint: chalk.dim('[cordova]'), }, { @@ -47,6 +47,17 @@ export const COMMON_CORDOVA_BUILD_COMMAND_OPTIONS: CommandMetadataOption[] = [ }, ]; +export const CORDOVA_RUN_OPTIONS: ReadonlyArray = [ + ...CORDOVA_COMPILE_OPTIONS, + { + name: 'target', + summary: `Deploy build to a device (use ${chalk.green('--list')} to see all)`, + type: String, + groups: [OptionGroup.Advanced, 'cordova', 'native-run'], + hint: chalk.dim('[cordova]'), + }, +]; + export const CORDOVA_BUILD_EXAMPLE_COMMANDS = [ 'ios', 'ios --prod --release', diff --git a/packages/ionic/src/commands/cordova/build.ts b/packages/ionic/src/commands/cordova/build.ts index 95ef9e0925..4a7c8ee39d 100644 --- a/packages/ionic/src/commands/cordova/build.ts +++ b/packages/ionic/src/commands/cordova/build.ts @@ -6,7 +6,7 @@ import { build } from '../../lib/build'; import { FatalException } from '../../lib/errors'; import { filterArgumentsForCordova, generateOptionsForCordovaBuild } from '../../lib/integrations/cordova/utils'; -import { COMMON_CORDOVA_BUILD_COMMAND_OPTIONS, CORDOVA_BUILD_EXAMPLE_COMMANDS, CordovaCommand } from './base'; +import { CORDOVA_BUILD_EXAMPLE_COMMANDS, CORDOVA_COMPILE_OPTIONS, CordovaCommand } from './base'; export class BuildCommand extends CordovaCommand implements CommandPreRun { async getMetadata(): Promise { @@ -19,7 +19,7 @@ export class BuildCommand extends CordovaCommand implements CommandPreRun { type: Boolean, default: true, }, - ...COMMON_CORDOVA_BUILD_COMMAND_OPTIONS, + ...CORDOVA_COMPILE_OPTIONS, ]; const footnotes: Footnote[] = [ diff --git a/packages/ionic/src/commands/cordova/compile.ts b/packages/ionic/src/commands/cordova/compile.ts index 38b3e97370..a7358ba270 100644 --- a/packages/ionic/src/commands/cordova/compile.ts +++ b/packages/ionic/src/commands/cordova/compile.ts @@ -4,7 +4,7 @@ import chalk from 'chalk'; import { CommandInstanceInfo, CommandLineInputs, CommandLineOptions, CommandMetadata, CommandPreRun } from '../../definitions'; import { filterArgumentsForCordova } from '../../lib/integrations/cordova/utils'; -import { COMMON_CORDOVA_BUILD_COMMAND_OPTIONS, CordovaCommand } from './base'; +import { CORDOVA_COMPILE_OPTIONS, CordovaCommand } from './base'; export class CompileCommand extends CordovaCommand implements CommandPreRun { async getMetadata(): Promise { @@ -28,7 +28,7 @@ Like running ${chalk.green('cordova compile')} directly, but provides friendly c }, ], options: [ - ...COMMON_CORDOVA_BUILD_COMMAND_OPTIONS, + ...CORDOVA_COMPILE_OPTIONS, ], }; } diff --git a/packages/ionic/src/commands/cordova/run.ts b/packages/ionic/src/commands/cordova/run.ts index 94af079b31..11c907dc7b 100644 --- a/packages/ionic/src/commands/cordova/run.ts +++ b/packages/ionic/src/commands/cordova/run.ts @@ -13,57 +13,12 @@ import { COMMON_SERVE_COMMAND_OPTIONS, LOCAL_ADDRESSES, serve } from '../../lib/ import { createDefaultLoggerHandlers } from '../../lib/utils/logger'; import { pkgManagerArgs } from '../../lib/utils/npm'; -import { CORDOVA_BUILD_EXAMPLE_COMMANDS, CordovaCommand } from './base'; +import { CORDOVA_BUILD_EXAMPLE_COMMANDS, CORDOVA_RUN_OPTIONS, CordovaCommand } from './base'; const CORDOVA_ANDROID_PACKAGE_PATH = 'platforms/android/app/build/outputs/apk/'; const CORDOVA_IOS_SIMULATOR_PACKAGE_PATH = 'platforms/ios/build/emulator'; const CORDOVA_IOS_DEVICE_PACKAGE_PATH = 'platforms/ios/build/device'; -const CORDOVA_RUN_OPTIONS: ReadonlyArray = [ - { - name: 'debug', - summary: 'Mark as a debug build', - type: Boolean, - groups: ['cordova'], - hint: chalk.dim('[cordova]'), - }, - { - name: 'release', - summary: 'Mark as a release build', - type: Boolean, - groups: ['cordova'], - hint: chalk.dim('[cordova]'), - }, - { - name: 'device', - summary: 'Deploy build to a device', - type: Boolean, - groups: ['cordova', 'native-run'], - hint: chalk.dim('[cordova]'), - }, - { - name: 'emulator', - summary: 'Deploy build to an emulator', - type: Boolean, - groups: ['cordova', 'native-run'], - hint: chalk.dim('[cordova]'), - }, - { - name: 'target', - summary: `Deploy build to a device (use ${chalk.green('--list')} to see all)`, - type: String, - groups: [OptionGroup.Advanced, 'cordova', 'native-run'], - hint: chalk.dim('[cordova]'), - }, - { - name: 'buildConfig', - summary: 'Use the specified build configuration', - groups: [OptionGroup.Advanced, 'cordova'], - hint: chalk.dim('[cordova]'), - spec: { value: 'file' }, - }, -]; - const NATIVE_RUN_OPTIONS: ReadonlyArray = [ { name: 'native-run', From 2d6c2cd326f59a76036ae41960ce4189d79ec2fe Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Tue, 22 Jan 2019 15:29:10 -0600 Subject: [PATCH 10/14] chore(docs): newline at end of json files --- packages/cli-scripts/src/docs/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli-scripts/src/docs/index.ts b/packages/cli-scripts/src/docs/index.ts index 189c5770ca..f2f5374d9c 100644 --- a/packages/cli-scripts/src/docs/index.ts +++ b/packages/cli-scripts/src/docs/index.ts @@ -41,7 +41,7 @@ export class DocsCommand extends Command { projectJson.commands = await Promise.all(projectJson.commands.map(async cmd => this.extractCommand(cmd as CommandHelpSchema))); projectJson.commands.sort((a, b) => strcmp(a.name, b.name)); - await writeFile(path.resolve(STAGING_DIRECTORY, `${projectType}.json`), JSON.stringify(projectJson, undefined, 2), { encoding: 'utf8' }); + await writeFile(path.resolve(STAGING_DIRECTORY, `${projectType}.json`), JSON.stringify(projectJson, undefined, 2) + '\n', { encoding: 'utf8' }); } process.stdout.write(`${chalk.green('Done.')}\n`); From cbc9a8b959a33877e904f2b0a6375b75c0e30a6a Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Tue, 22 Jan 2019 15:32:50 -0600 Subject: [PATCH 11/14] chore(docs): keep < and > characters --- packages/cli-scripts/src/docs/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli-scripts/src/docs/utils.ts b/packages/cli-scripts/src/docs/utils.ts index 6ad8892e72..48e0f15814 100644 --- a/packages/cli-scripts/src/docs/utils.ts +++ b/packages/cli-scripts/src/docs/utils.ts @@ -13,7 +13,6 @@ export function links2md(str: string) { } export function ansi2md(str: string) { - str = str.replace(//g, '>'); str = convertAnsiToMd(str, ansiStyle.green, { open: '`', close: '`' }); str = convertAnsiToMd(str, ansiStyle.yellow, { open: '', close: '' }); str = convertAnsiToMd(str, ansiStyle.bold, { open: '**', close: '**' }); From 3c27e05661131cc243d4903adad944b1511e362e Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Tue, 22 Jan 2019 20:25:34 -0600 Subject: [PATCH 12/14] feat(resources): --cordova-res option for local resource generation This hidden option offers an alternative to using Ionic's remote server to generate Cordova icons and splash screens to specific platform sizes. If specified, it will use `cordova-res` to generate resources locally using sharp: https://github.com/lovell/sharp --- .../ionic/src/commands/cordova/resources.ts | 63 +++++++++++++++++-- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/packages/ionic/src/commands/cordova/resources.ts b/packages/ionic/src/commands/cordova/resources.ts index 372ea13adc..6dea44c3a6 100644 --- a/packages/ionic/src/commands/cordova/resources.ts +++ b/packages/ionic/src/commands/cordova/resources.ts @@ -1,3 +1,4 @@ +import { ERROR_SHELL_COMMAND_NOT_FOUND, LOGGER_LEVELS, OptionGroup, ShellCommandError, createPrefixedFormatter } from '@ionic/cli-framework'; import { prettyPath } from '@ionic/cli-framework/utils/format'; import { cacheFileChecksum, copy, pathExists } from '@ionic/utils-fs'; import chalk from 'chalk'; @@ -5,6 +6,8 @@ import * as Debug from 'debug'; import { CommandInstanceInfo, CommandLineInputs, CommandLineOptions, CommandMetadata, CommandPreRun, KnownPlatform, ResourcesConfig, ResourcesImageConfig, SourceImage } from '../../definitions'; import { FatalException } from '../../lib/errors'; +import { createDefaultLoggerHandlers } from '../../lib/utils/logger'; +import { pkgManagerArgs } from '../../lib/utils/npm'; import { CordovaCommand } from './base'; @@ -63,6 +66,12 @@ This command uses Ionic servers, so we require you to be logged into your free I type: Boolean, aliases: ['s'], }, + { + name: 'cordova-res', + summary: `Use ${chalk.green('cordova-res')} instead of Ionic resource server`, + type: Boolean, + groups: [OptionGroup.Hidden], + }, ], }; } @@ -74,7 +83,7 @@ This command uses Ionic servers, so we require you to be logged into your free I const isLoggedIn = this.env.session.isLoggedIn(); - if (!isLoggedIn) { + if (!options['cordova-res'] && !isLoggedIn) { this.env.log.warn(`You need to be logged into your Ionic account in order to run ${chalk.green(`ionic cordova resources`)}.\n`); await promptToLogin(this.env); } @@ -96,16 +105,60 @@ This command uses Ionic servers, so we require you to be logged into your free I } async run(inputs: CommandLineInputs, options: CommandLineOptions): Promise { - const { loadConfigXml } = await import('../../lib/integrations/cordova/config'); - const { addResourcesToConfigXml, createImgDestinationDirectories, findMostSpecificSourceImage, getImageResources, getSourceImages, transformResourceImage, uploadSourceImage } = await import('../../lib/integrations/cordova/resources'); + const platform = inputs[0] ? String(inputs[0]) : undefined; + + if (options['cordova-res']) { + await this.runCordovaRes(platform, options); + } else { + await this.runResourceServer(platform, options); + } + } + async runCordovaRes(platform: string | undefined, options: CommandLineOptions): Promise { if (!this.project) { throw new FatalException(`Cannot run ${chalk.green('ionic cordova resources')} outside a project directory.`); } - const platform = inputs[0] ? String(inputs[0]) : undefined; - const { force } = options; + const log = this.env.log.clone(); + log.handlers = createDefaultLoggerHandlers(createPrefixedFormatter(chalk.dim(`[cordova-res]`))); + const ws = log.createWriteStream(LOGGER_LEVELS.INFO); + + const args: string[] = []; + if (platform) { + args.push(platform); + } + + if (options['icon']) { + args.push('--type', 'icon'); + } else if (options['splash']) { + args.push('--type', 'splash'); + } + + if (options['verbose']) { + args.push('--verbose'); + } + + try { + await this.env.shell.run('cordova-res', args, { showCommand: true, fatalOnNotFound: false, cwd: this.project.directory, stream: ws }); + } catch (e) { + if (e instanceof ShellCommandError && e.code === ERROR_SHELL_COMMAND_NOT_FOUND) { + const installArgs = await pkgManagerArgs(this.env.config.get('npmClient'), { command: 'install', pkg: 'cordova-res', global: true }); + throw new FatalException( + `${chalk.green('cordova-res')} was not found on your PATH. Please install it globally:\n` + + `${chalk.green(installArgs.join(' '))}\n` + ); + } + + throw e; + } + } + + async runResourceServer(platform: string | undefined, options: CommandLineOptions): Promise { + const { loadConfigXml } = await import('../../lib/integrations/cordova/config'); + const { addResourcesToConfigXml, createImgDestinationDirectories, findMostSpecificSourceImage, getImageResources, getSourceImages, transformResourceImage, uploadSourceImage } = await import('../../lib/integrations/cordova/resources'); + + const { force } = options; const tasks = this.createTaskChain(); // if no resource filters are passed as arguments assume to use all. From 0195f96db82f97b5b893d43b907387ab2d7fd496 Mon Sep 17 00:00:00 2001 From: Mike Hartington Date: Wed, 23 Jan 2019 11:58:36 -0500 Subject: [PATCH 13/14] feat(start): make Ionic 4 the default for new projects (#3820) --- packages/ionic/src/commands/start.ts | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/packages/ionic/src/commands/start.ts b/packages/ionic/src/commands/start.ts index f8a787ccd3..e656574ed6 100644 --- a/packages/ionic/src/commands/start.ts +++ b/packages/ionic/src/commands/start.ts @@ -223,29 +223,7 @@ The second argument is the ${chalk.green('template')} from which to generate you } } - if (!options['type']) { - if (this.env.flags.interactive) { - this.env.log.info( - `You are about to create an Ionic 3 app. Would you like to try the release candidate for Ionic 4?\n` + - `Ionic 4 uses the power of the modern Web and embraces the Angular CLI and Angular Router to bring you the best version of Ionic ever. See our blog announcement${chalk.cyan('[1]')} and documentation${chalk.cyan('[2]')} for more information. We'd love to hear your feedback on our latest version!\n\n` + - `${chalk.cyan('[1]')}: ${chalk.bold('https://blog.ionicframework.com/ionic-framework-4-0-rc-shipped-paving-way-for-final')}\n` + - `${chalk.cyan('[2]')}: ${chalk.bold('https://beta.ionicframework.com/docs/')}\n` - ); - } - - const confirm = await this.env.prompt({ - type: 'confirm', - name: 'confirm', - message: 'Try Ionic 4?', - default: false, - }); - - if (confirm) { - options['type'] = 'angular'; - } - } - - const projectType = options['type'] ? String(options['type']) : 'ionic-angular'; + const projectType = options['type'] ? String(options['type']) : 'angular'; const appflowId = options['id'] ? String(options['id']) : undefined; await this.validateProjectType(projectType); @@ -690,7 +668,7 @@ The second argument is the ${chalk.green('template')} from which to generate you `Go to your ${cloned ? 'cloned' : 'newly created'} project: ${chalk.green(`cd ${prettyPath(projectDir)}`)}`, `Run ${chalk.green('ionic serve')} within the app directory to see your app`, `Build features and components: ${chalk.bold('https://ion.link/scaffolding-docs')}`, - `Get Ionic DevApp for easy device testing: ${chalk.bold('https://bit.ly/ionic-dev-app')}`, + `Get Ionic DevApp for easy device testing: ${chalk.bold('https://ion.link/devapp')}`, ]; if (linkConfirmed) { From 1be212131a9bb4a726e04a6ff0cc5aa2ba508a8b Mon Sep 17 00:00:00 2001 From: Ionitron Date: Wed, 23 Jan 2019 17:51:38 +0000 Subject: [PATCH 14/14] chore(release): publish [skip ci] - @ionic/cli-framework@1.6.0 - @ionic/discover@1.0.11 - @ionic/lab@1.0.19 - @ionic/v1-toolkit@1.0.18 - cli-scripts@0.0.29 - ionic@4.9.0 --- packages/@ionic/cli-framework/CHANGELOG.md | 11 +++++++++++ packages/@ionic/cli-framework/package.json | 2 +- packages/@ionic/discover/CHANGELOG.md | 8 ++++++++ packages/@ionic/discover/package.json | 2 +- packages/@ionic/lab/CHANGELOG.md | 8 ++++++++ packages/@ionic/lab/package.json | 4 ++-- packages/@ionic/v1-toolkit/CHANGELOG.md | 8 ++++++++ packages/@ionic/v1-toolkit/package.json | 4 ++-- packages/cli-scripts/CHANGELOG.md | 8 ++++++++ packages/cli-scripts/package.json | 6 +++--- packages/ionic/CHANGELOG.md | 13 +++++++++++++ packages/ionic/package.json | 6 +++--- 12 files changed, 68 insertions(+), 12 deletions(-) diff --git a/packages/@ionic/cli-framework/CHANGELOG.md b/packages/@ionic/cli-framework/CHANGELOG.md index 47f48ca005..881ba66224 100644 --- a/packages/@ionic/cli-framework/CHANGELOG.md +++ b/packages/@ionic/cli-framework/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.6.0](https://github.com/ionic-team/ionic-cli/compare/@ionic/cli-framework@1.5.3...@ionic/cli-framework@1.6.0) (2019-01-23) + + +### Features + +* **help:** text/link footnotes for descriptions ([2c74d53](https://github.com/ionic-team/ionic-cli/commit/2c74d53)) + + + + ## [1.5.3](https://github.com/ionic-team/ionic-cli/compare/@ionic/cli-framework@1.5.2...@ionic/cli-framework@1.5.3) (2019-01-08) diff --git a/packages/@ionic/cli-framework/package.json b/packages/@ionic/cli-framework/package.json index c86b36c542..a3967cbd60 100644 --- a/packages/@ionic/cli-framework/package.json +++ b/packages/@ionic/cli-framework/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/cli-framework", - "version": "1.5.3", + "version": "1.6.0", "description": "The foundation framework of the Ionic CLI", "homepage": "https://ionicframework.com/", "author": "Ionic Team (https://ionicframework.com) ", diff --git a/packages/@ionic/discover/CHANGELOG.md b/packages/@ionic/discover/CHANGELOG.md index 96f05be97a..0181fa7b4e 100644 --- a/packages/@ionic/discover/CHANGELOG.md +++ b/packages/@ionic/discover/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.11](https://github.com/ionic-team/ionic-cli/compare/@ionic/discover@1.0.10...@ionic/discover@1.0.11) (2019-01-23) + + + + +**Note:** Version bump only for package @ionic/discover + ## [1.0.10](https://github.com/ionic-team/ionic-cli/compare/@ionic/discover@1.0.9...@ionic/discover@1.0.10) (2019-01-07) diff --git a/packages/@ionic/discover/package.json b/packages/@ionic/discover/package.json index 43ed492b96..c173bffbf1 100644 --- a/packages/@ionic/discover/package.json +++ b/packages/@ionic/discover/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/discover", - "version": "1.0.10", + "version": "1.0.11", "description": "Simple UDP based protocol for service discovery implemented in pure JS.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/@ionic/lab/CHANGELOG.md b/packages/@ionic/lab/CHANGELOG.md index 06cc85bfbf..3709136054 100644 --- a/packages/@ionic/lab/CHANGELOG.md +++ b/packages/@ionic/lab/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.19](https://github.com/ionic-team/ionic-cli/compare/@ionic/lab@1.0.18...@ionic/lab@1.0.19) (2019-01-23) + + + + +**Note:** Version bump only for package @ionic/lab + ## [1.0.18](https://github.com/ionic-team/ionic-cli/compare/@ionic/lab@1.0.17...@ionic/lab@1.0.18) (2019-01-08) diff --git a/packages/@ionic/lab/package.json b/packages/@ionic/lab/package.json index 50310fff49..c823f46ad7 100644 --- a/packages/@ionic/lab/package.json +++ b/packages/@ionic/lab/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/lab", - "version": "1.0.18", + "version": "1.0.19", "description": "Ionic Lab utility for developing Ionic apps, used by Ionic CLI", "bin": { "ionic-lab": "./bin/ionic-lab" @@ -47,7 +47,7 @@ ] }, "dependencies": { - "@ionic/cli-framework": "1.5.3", + "@ionic/cli-framework": "1.6.0", "@ionic/utils-fs": "1.0.0", "chalk": "^2.3.0", "express": "^4.16.2", diff --git a/packages/@ionic/v1-toolkit/CHANGELOG.md b/packages/@ionic/v1-toolkit/CHANGELOG.md index 702f4a87db..1cddf9cf19 100644 --- a/packages/@ionic/v1-toolkit/CHANGELOG.md +++ b/packages/@ionic/v1-toolkit/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.18](https://github.com/ionic-team/ionic-cli/compare/@ionic/v1-toolkit@1.0.17...@ionic/v1-toolkit@1.0.18) (2019-01-23) + + + + +**Note:** Version bump only for package @ionic/v1-toolkit + ## [1.0.17](https://github.com/ionic-team/ionic-cli/compare/@ionic/v1-toolkit@1.0.16...@ionic/v1-toolkit@1.0.17) (2019-01-08) diff --git a/packages/@ionic/v1-toolkit/package.json b/packages/@ionic/v1-toolkit/package.json index 53db5c2f2a..c72a9baab9 100644 --- a/packages/@ionic/v1-toolkit/package.json +++ b/packages/@ionic/v1-toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/v1-toolkit", - "version": "1.0.17", + "version": "1.0.18", "description": "Utility CLI for Ionic 1.x apps, used by Ionic CLI", "bin": { "ionic-v1": "./bin/ionic-v1" @@ -41,7 +41,7 @@ ] }, "dependencies": { - "@ionic/cli-framework": "1.5.3", + "@ionic/cli-framework": "1.6.0", "@ionic/utils-fs": "1.0.0", "chalk": "^2.3.0", "chokidar": "^2.0.0", diff --git a/packages/cli-scripts/CHANGELOG.md b/packages/cli-scripts/CHANGELOG.md index 4dc8a78092..e6b9f6a341 100644 --- a/packages/cli-scripts/CHANGELOG.md +++ b/packages/cli-scripts/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.0.29](https://github.com/ionic-team/ionic-cli/compare/cli-scripts@0.0.28...cli-scripts@0.0.29) (2019-01-23) + + + + +**Note:** Version bump only for package cli-scripts + ## [0.0.28](https://github.com/ionic-team/ionic-cli/compare/cli-scripts@0.0.27...cli-scripts@0.0.28) (2019-01-14) diff --git a/packages/cli-scripts/package.json b/packages/cli-scripts/package.json index d35cae4eb5..f0d1425694 100644 --- a/packages/cli-scripts/package.json +++ b/packages/cli-scripts/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "cli-scripts", - "version": "0.0.28", + "version": "0.0.29", "bin": { "ionic-cli-scripts": "./bin/ionic-cli-scripts" }, @@ -24,12 +24,12 @@ "main": "./dist/index.js", "types": "./dist/index.d.ts", "dependencies": { - "@ionic/cli-framework": "1.5.3", + "@ionic/cli-framework": "1.6.0", "@ionic/utils-fs": "1.0.0", "ansi-styles": "^3.2.0", "chalk": "^2.3.0", "escape-string-regexp": "^1.0.5", - "ionic": "4.8.0", + "ionic": "4.9.0", "strip-ansi": "^5.0.0", "tslib": "^1.9.0" }, diff --git a/packages/ionic/CHANGELOG.md b/packages/ionic/CHANGELOG.md index 801287ac82..d2e42414dc 100644 --- a/packages/ionic/CHANGELOG.md +++ b/packages/ionic/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [4.9.0](https://github.com/ionic-team/ionic-cli/compare/ionic@4.8.0...ionic@4.9.0) (2019-01-23) + + +### Features + +* **custom:** run ionic:build/ionic:serve scripts ([9898fa8](https://github.com/ionic-team/ionic-cli/commit/9898fa8)) +* **resources:** --cordova-res option for local resource generation ([3c27e05](https://github.com/ionic-team/ionic-cli/commit/3c27e05)) +* **start:** make Ionic 4 the default for new projects ([#3820](https://github.com/ionic-team/ionic-cli/issues/3820)) ([0195f96](https://github.com/ionic-team/ionic-cli/commit/0195f96)) + + + + # [4.8.0](https://github.com/ionic-team/ionic-cli/compare/ionic@4.7.1...ionic@4.8.0) (2019-01-14) diff --git a/packages/ionic/package.json b/packages/ionic/package.json index 1581762561..7741cdc4af 100644 --- a/packages/ionic/package.json +++ b/packages/ionic/package.json @@ -1,6 +1,6 @@ { "name": "ionic", - "version": "4.8.0", + "version": "4.9.0", "description": "A tool for creating and developing Ionic Framework mobile apps.", "homepage": "https://ionicframework.com", "author": "Ionic Team (https://ionicframework.com) ", @@ -48,8 +48,8 @@ }, "license": "MIT", "dependencies": { - "@ionic/cli-framework": "1.5.3", - "@ionic/discover": "1.0.10", + "@ionic/cli-framework": "1.6.0", + "@ionic/discover": "1.0.11", "@ionic/utils-fs": "1.0.0", "@ionic/utils-network": "0.0.6", "chalk": "^2.3.0",