1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.StartCommand = void 0;
|
4 | const cli_framework_1 = require("@ionic/cli-framework");
|
5 | const string_1 = require("@ionic/cli-framework/utils/string");
|
6 | const utils_fs_1 = require("@ionic/utils-fs");
|
7 | const utils_terminal_1 = require("@ionic/utils-terminal");
|
8 | const chalk = require("chalk");
|
9 | const Debug = require("debug");
|
10 | const path = require("path");
|
11 | const constants_1 = require("../constants");
|
12 | const color_1 = require("../lib/color");
|
13 | const command_1 = require("../lib/command");
|
14 | const errors_1 = require("../lib/errors");
|
15 | const executor_1 = require("../lib/executor");
|
16 | const project_1 = require("../lib/project");
|
17 | const session_1 = require("../lib/session");
|
18 | const shell_1 = require("../lib/shell");
|
19 | const start_1 = require("../lib/start");
|
20 | const emoji_1 = require("../lib/utils/emoji");
|
21 | const http_1 = require("../lib/utils/http");
|
22 | const debug = Debug('ionic:commands:start');
|
23 | class StartCommand extends command_1.Command {
|
24 | constructor() {
|
25 | super(...arguments);
|
26 | this.canRemoveExisting = false;
|
27 | }
|
28 | async getMetadata() {
|
29 | return {
|
30 | name: 'start',
|
31 | type: 'global',
|
32 | summary: 'Create a new project',
|
33 | description: `
|
34 | This command creates a working Ionic app. It installs dependencies for you and sets up your project.
|
35 |
|
36 | Running ${color_1.input('ionic start')} without any arguments will prompt you for information about your new project.
|
37 |
|
38 | The first argument is your app's ${color_1.input('name')}. Don't worry--you can always change this later. The ${color_1.input('--project-id')} is generated from ${color_1.input('name')} unless explicitly specified.
|
39 |
|
40 | The second argument is the ${color_1.input('template')} from which to generate your app. You can list all templates with the ${color_1.input('--list')} option. You can also specify a git repository URL for ${color_1.input('template')}, in which case the existing project will be cloned.
|
41 |
|
42 | Use the ${color_1.input('--type')} option to start projects using older versions of Ionic. For example, you can start an Ionic 3 project with ${color_1.input('--type=ionic-angular')}. Use ${color_1.input('--list')} to see all project types and templates.
|
43 | `,
|
44 | exampleCommands: [
|
45 | '',
|
46 | '--list',
|
47 | 'myApp',
|
48 | 'myApp blank',
|
49 | 'myApp tabs --cordova',
|
50 | 'myApp tabs --capacitor',
|
51 | 'myApp super --type=ionic-angular',
|
52 | 'myApp blank --type=ionic1',
|
53 | 'cordovaApp tabs --cordova',
|
54 | '"My App" blank',
|
55 | '"Conference App" https://github.com/ionic-team/ionic-conference-app',
|
56 | ],
|
57 | inputs: [
|
58 | {
|
59 | name: 'name',
|
60 | summary: `The name of your new project (e.g. ${color_1.input('myApp')}, ${color_1.input('"My App"')})`,
|
61 | validators: [cli_framework_1.validators.required],
|
62 | },
|
63 | {
|
64 | name: 'template',
|
65 | summary: `The starter template to use (e.g. ${['blank', 'tabs'].map(t => color_1.input(t)).join(', ')}; use ${color_1.input('--list')} to see all)`,
|
66 | validators: [cli_framework_1.validators.required],
|
67 | },
|
68 | ],
|
69 | options: [
|
70 | {
|
71 | name: 'list',
|
72 | summary: 'List available starter templates',
|
73 | type: Boolean,
|
74 | aliases: ['l'],
|
75 | },
|
76 | {
|
77 | name: 'type',
|
78 | summary: `Type of project to start (e.g. ${start_1.getStarterProjectTypes().map(type => color_1.input(type)).join(', ')})`,
|
79 | type: String,
|
80 | },
|
81 | {
|
82 | name: 'cordova',
|
83 | summary: 'Include Cordova integration',
|
84 | type: Boolean,
|
85 | },
|
86 | {
|
87 | name: 'capacitor',
|
88 | summary: 'Include Capacitor integration',
|
89 | type: Boolean,
|
90 | groups: ["experimental" ],
|
91 | },
|
92 | {
|
93 | name: 'deps',
|
94 | summary: 'Do not install npm/yarn dependencies',
|
95 | type: Boolean,
|
96 | default: true,
|
97 | groups: ["advanced" ],
|
98 | },
|
99 | {
|
100 | name: 'git',
|
101 | summary: 'Do not initialize a git repo',
|
102 | type: Boolean,
|
103 | default: true,
|
104 | groups: ["advanced" ],
|
105 | },
|
106 | {
|
107 | name: 'link',
|
108 | summary: 'Connect your new app to Ionic',
|
109 | type: Boolean,
|
110 | groups: ["advanced" ],
|
111 | },
|
112 | {
|
113 | name: 'id',
|
114 | summary: 'Specify an Ionic App ID to link',
|
115 | },
|
116 | {
|
117 | name: 'project-id',
|
118 | summary: 'Specify a slug for your app (used for the directory name and package name)',
|
119 | groups: ["advanced" ],
|
120 | spec: { value: 'slug' },
|
121 | },
|
122 | {
|
123 | name: 'package-id',
|
124 | summary: 'Specify the bundle ID/application ID for your app (reverse-DNS notation)',
|
125 | groups: ["advanced" ],
|
126 | spec: { value: 'id' },
|
127 | },
|
128 | {
|
129 | name: 'start-id',
|
130 | summary: 'Used by the Ionic app start experience to generate an associated app locally',
|
131 | groups: ["hidden" ],
|
132 | spec: { value: 'id' },
|
133 | },
|
134 | {
|
135 | name: 'tag',
|
136 | summary: `Specify a tag to use for the starters (e.g. ${['latest', 'testing', 'next'].map(t => color_1.input(t)).join(', ')})`,
|
137 | default: 'latest',
|
138 | groups: ["hidden" ],
|
139 | },
|
140 | ],
|
141 | };
|
142 | }
|
143 | async startIdStart(inputs, options) {
|
144 | const startId = options['start-id'];
|
145 | const wizardApiUrl = process.env.START_WIZARD_URL_BASE || `https://ionicframework.com`;
|
146 | const { req } = await http_1.createRequest('GET', `${wizardApiUrl}/api/v1/wizard/app/${startId}`, this.env.config.getHTTPConfig());
|
147 | const error = (e) => {
|
148 | this.env.log.error(`No such app ${chalk.bold(startId)}. This app configuration may have expired. Please retry at https://ionicframework.com/start`);
|
149 | if (e) {
|
150 | throw e;
|
151 | }
|
152 | };
|
153 | let data;
|
154 | try {
|
155 | const ret = await req;
|
156 | if (ret.status !== 200) {
|
157 | return error();
|
158 | }
|
159 | data = (await req).body;
|
160 | if (!data) {
|
161 | return error();
|
162 | }
|
163 | }
|
164 | catch (e) {
|
165 | return error(e);
|
166 | }
|
167 | let projectDir = string_1.slugify(data.name);
|
168 | if (inputs.length === 1) {
|
169 | projectDir = inputs[0];
|
170 | }
|
171 | await this.checkForExisting(projectDir);
|
172 | inputs.push(data.name);
|
173 | inputs.push(data.template);
|
174 | await this.startIdConvert(startId);
|
175 | const appIconBuffer = data.appIcon ?
|
176 | Buffer.from(data.appIcon.replace(/^data:image\/\w+;base64,/, ''), 'base64') :
|
177 | undefined;
|
178 | const splashBuffer = data.appSplash ?
|
179 | Buffer.from(data.appSplash.replace(/^data:image\/\w+;base64,/, ''), 'base64') :
|
180 | undefined;
|
181 | this.schema = {
|
182 | cloned: false,
|
183 | name: data.name,
|
184 | type: data.type,
|
185 | template: data.template,
|
186 | projectId: string_1.slugify(data.name),
|
187 | projectDir,
|
188 | packageId: data['package-id'],
|
189 | appflowId: undefined,
|
190 | appIcon: appIconBuffer,
|
191 | splash: splashBuffer,
|
192 | themeColor: data.theme,
|
193 | };
|
194 | }
|
195 | async startIdConvert(id) {
|
196 | const wizardApiUrl = process.env.START_WIZARD_URL_BASE || `https://ionicframework.com`;
|
197 | if (!wizardApiUrl) {
|
198 | return;
|
199 | }
|
200 | const { req } = await http_1.createRequest('POST', `${wizardApiUrl}/api/v1/wizard/app/${id}/start`, this.env.config.getHTTPConfig());
|
201 | try {
|
202 | await req;
|
203 | }
|
204 | catch (e) {
|
205 | this.env.log.warn(`Unable to set app flag on server: ${e.message}`);
|
206 | }
|
207 | }
|
208 | async preRun(inputs, options) {
|
209 | const { promptToLogin } = await Promise.resolve().then(() => require('../lib/session'));
|
210 | start_1.verifyOptions(options, this.env);
|
211 | const appflowId = options['id'] ? String(options['id']) : undefined;
|
212 | if (appflowId) {
|
213 | if (!this.env.session.isLoggedIn()) {
|
214 | await promptToLogin(this.env);
|
215 | }
|
216 | }
|
217 |
|
218 | if (options['start-id']) {
|
219 | await this.startIdStart(inputs, options);
|
220 | return;
|
221 | }
|
222 | const projectType = string_1.isValidURL(inputs[1]) ? 'custom' : options['type'] ? String(options['type']) : await this.getProjectType();
|
223 | if (options['cordova']) {
|
224 | const { checkForUnsupportedProject } = await Promise.resolve().then(() => require('../lib/integrations/cordova/utils'));
|
225 | try {
|
226 | await checkForUnsupportedProject(projectType);
|
227 | }
|
228 | catch (e) {
|
229 | this.env.log.error(e.message);
|
230 | options['cordova'] = false;
|
231 | }
|
232 | }
|
233 | if (!inputs[0]) {
|
234 | if (appflowId) {
|
235 | const { AppClient } = await Promise.resolve().then(() => require('../lib/app'));
|
236 | const token = await this.env.session.getUserToken();
|
237 | const appClient = new AppClient(token, this.env);
|
238 | const tasks = this.createTaskChain();
|
239 | tasks.next(`Looking up app ${color_1.input(appflowId)}`);
|
240 | const app = await appClient.load(appflowId);
|
241 |
|
242 | tasks.end();
|
243 | this.env.log.info(`Using ${color_1.strong(app.name)} for ${color_1.input('name')} and ${color_1.strong(app.slug)} for ${color_1.input('--project-id')}.`);
|
244 | inputs[0] = app.name;
|
245 | options['project-id'] = app.slug;
|
246 | }
|
247 | else {
|
248 | if (this.env.flags.interactive) {
|
249 | this.env.log.nl();
|
250 | this.env.log.msg(`${color_1.strong(`Every great app needs a name! ${emoji_1.emoji('😍', '')}`)}\n` +
|
251 | `Please enter the full name of your app. You can change this at any time. To bypass this prompt next time, supply ${color_1.input('name')}, the first argument to ${color_1.input('ionic start')}.\n\n`);
|
252 | }
|
253 | const name = await this.env.prompt({
|
254 | type: 'input',
|
255 | name: 'name',
|
256 | message: 'Project name:',
|
257 | validate: v => cli_framework_1.validators.required(v),
|
258 | });
|
259 | inputs[0] = name;
|
260 | }
|
261 | }
|
262 | if (!inputs[1]) {
|
263 | if (this.env.flags.interactive) {
|
264 | this.env.log.nl();
|
265 | this.env.log.msg(`${color_1.strong(`Let's pick the perfect starter template! ${emoji_1.emoji('💪', '')}`)}\n` +
|
266 | `Starter templates are ready-to-go Ionic apps that come packed with everything you need to build your app. To bypass this prompt next time, supply ${color_1.input('template')}, the second argument to ${color_1.input('ionic start')}.\n\n`);
|
267 | }
|
268 | const template = await this.env.prompt({
|
269 | type: 'list',
|
270 | name: 'template',
|
271 | message: 'Starter template:',
|
272 | choices: () => {
|
273 | const starterTemplateList = start_1.STARTER_TEMPLATES.filter(st => st.projectType === projectType);
|
274 | const cols = utils_terminal_1.columnar(starterTemplateList.map(({ name, description }) => [color_1.input(name), description || '']), constants_1.COLUMNAR_OPTIONS).split('\n');
|
275 | if (starterTemplateList.length === 0) {
|
276 | throw new errors_1.FatalException(`No starter templates found for project type: ${color_1.input(projectType)}.`);
|
277 | }
|
278 | return starterTemplateList.map((starter, i) => {
|
279 | return {
|
280 | name: cols[i],
|
281 | short: starter.name,
|
282 | value: starter.name,
|
283 | };
|
284 | });
|
285 | },
|
286 | });
|
287 | inputs[1] = template;
|
288 | }
|
289 | const starterTemplate = start_1.STARTER_TEMPLATES.find(t => t.name === inputs[1] && t.projectType === projectType);
|
290 | if (starterTemplate && starterTemplate.type === 'repo') {
|
291 | inputs[1] = starterTemplate.repo;
|
292 | }
|
293 | const cloned = string_1.isValidURL(inputs[1]);
|
294 | if (this.project && this.project.details.context === 'app') {
|
295 | const confirm = await this.env.prompt({
|
296 | type: 'confirm',
|
297 | name: 'confirm',
|
298 | message: 'You are already in an Ionic project directory. Do you really want to start another project here?',
|
299 | default: false,
|
300 | });
|
301 | if (!confirm) {
|
302 | this.env.log.info('Not starting project within existing project.');
|
303 | throw new errors_1.FatalException();
|
304 | }
|
305 | }
|
306 | await this.validateProjectType(projectType);
|
307 | if (cloned) {
|
308 | if (!options['git']) {
|
309 | this.env.log.warn(`The ${color_1.input('--no-git')} option has no effect when cloning apps. Git must be used.`);
|
310 | }
|
311 | options['git'] = true;
|
312 | }
|
313 | if (options['v1'] || options['v2']) {
|
314 | throw new errors_1.FatalException(`The ${color_1.input('--v1')} and ${color_1.input('--v2')} flags have been removed.\n` +
|
315 | `Use the ${color_1.input('--type')} option. (see ${color_1.input('ionic start --help')})`);
|
316 | }
|
317 | if (options['app-name']) {
|
318 | this.env.log.warn(`The ${color_1.input('--app-name')} option has been removed. Use the ${color_1.input('name')} argument with double quotes: e.g. ${color_1.input('ionic start "My App"')}`);
|
319 | }
|
320 | if (options['display-name']) {
|
321 | this.env.log.warn(`The ${color_1.input('--display-name')} option has been removed. Use the ${color_1.input('name')} argument with double quotes: e.g. ${color_1.input('ionic start "My App"')}`);
|
322 | }
|
323 | if (options['bundle-id']) {
|
324 | this.env.log.warn(`The ${color_1.input('--bundle-id')} option has been deprecated. Please use ${color_1.input('--package-id')}.`);
|
325 | options['package-id'] = options['bundle-id'];
|
326 | }
|
327 | let projectId = options['project-id'] ? String(options['project-id']) : undefined;
|
328 | if (projectId) {
|
329 | await this.validateProjectId(projectId);
|
330 | }
|
331 | else {
|
332 | projectId = options['project-id'] = project_1.isValidProjectId(inputs[0]) ? inputs[0] : string_1.slugify(inputs[0]);
|
333 | }
|
334 | const projectDir = path.resolve(projectId);
|
335 | const packageId = options['package-id'] ? String(options['package-id']) : undefined;
|
336 | if (projectId) {
|
337 | await this.checkForExisting(projectDir);
|
338 | }
|
339 | if (cloned) {
|
340 | this.schema = {
|
341 | cloned: true,
|
342 | url: inputs[1],
|
343 | projectId,
|
344 | projectDir,
|
345 | };
|
346 | }
|
347 | else {
|
348 | this.schema = {
|
349 | cloned: false,
|
350 | name: inputs[0],
|
351 | type: projectType,
|
352 | template: inputs[1],
|
353 | projectId,
|
354 | projectDir,
|
355 | packageId,
|
356 | appflowId,
|
357 | themeColor: undefined,
|
358 | };
|
359 | }
|
360 | }
|
361 | async getProjectType() {
|
362 | if (this.env.flags.interactive) {
|
363 | this.env.log.nl();
|
364 | this.env.log.msg(`${color_1.strong(`Pick a framework! ${emoji_1.emoji('😁', '')}`)}\n\n` +
|
365 | `Please select the JavaScript framework to use for your new app. To bypass this prompt next time, supply a value for the ${color_1.input('--type')} option.\n\n`);
|
366 | }
|
367 | const frameworkChoice = await this.env.prompt({
|
368 | type: 'list',
|
369 | name: 'frameworks',
|
370 | message: 'Framework:',
|
371 | default: 'angular',
|
372 | choices: () => {
|
373 | const cols = utils_terminal_1.columnar(start_1.SUPPORTED_FRAMEWORKS.map(({ name, description }) => [color_1.input(name), description]), constants_1.COLUMNAR_OPTIONS).split('\n');
|
374 | return start_1.SUPPORTED_FRAMEWORKS.map((starterTemplate, i) => {
|
375 | return {
|
376 | name: cols[i],
|
377 | short: starterTemplate.name,
|
378 | value: starterTemplate.type,
|
379 | };
|
380 | });
|
381 | },
|
382 | });
|
383 | return frameworkChoice;
|
384 | }
|
385 | async run(inputs, options, runinfo) {
|
386 | const { pkgManagerArgs } = await Promise.resolve().then(() => require('../lib/utils/npm'));
|
387 | const { getTopLevel, isGitInstalled } = await Promise.resolve().then(() => require('../lib/git'));
|
388 | if (!this.schema) {
|
389 | throw new errors_1.FatalException(`Invalid start schema: cannot start app.`);
|
390 | }
|
391 | const { projectId, projectDir, packageId, appflowId } = this.schema;
|
392 | const tag = options['tag'] ? String(options['tag']) : 'latest';
|
393 | let linkConfirmed = typeof appflowId === 'string';
|
394 | const gitDesired = options['git'] ? true : false;
|
395 | const gitInstalled = await isGitInstalled(this.env);
|
396 | const gitTopLevel = await getTopLevel(this.env);
|
397 | let gitIntegration = gitDesired && gitInstalled && !gitTopLevel ? true : false;
|
398 | if (!gitInstalled) {
|
399 | const installationDocs = `See installation docs for git: ${color_1.strong('https://git-scm.com/book/en/v2/Getting-Started-Installing-Git')}`;
|
400 | if (appflowId) {
|
401 | throw new errors_1.FatalException(`Git CLI not found on your PATH.\n` +
|
402 | `Git must be installed to connect this app to Ionic. ${installationDocs}`);
|
403 | }
|
404 | if (this.schema.cloned) {
|
405 | throw new errors_1.FatalException(`Git CLI not found on your PATH.\n` +
|
406 | `Git must be installed to clone apps with ${color_1.input('ionic start')}. ${installationDocs}`);
|
407 | }
|
408 | }
|
409 | if (gitTopLevel && !this.schema.cloned) {
|
410 | this.env.log.info(`Existing git project found (${color_1.strong(gitTopLevel)}). Git operations are disabled.`);
|
411 | }
|
412 | const tasks = this.createTaskChain();
|
413 | tasks.next(`Preparing directory ${color_1.input(utils_terminal_1.prettyPath(projectDir))}`);
|
414 | if (this.canRemoveExisting) {
|
415 | await utils_fs_1.remove(projectDir);
|
416 | }
|
417 | await utils_fs_1.mkdir(projectDir);
|
418 | tasks.end();
|
419 | if (this.schema.cloned) {
|
420 | await this.env.shell.run('git', ['clone', this.schema.url, projectDir, '--progress'], { stdio: 'inherit' });
|
421 | }
|
422 | else {
|
423 | const starterTemplate = await this.findStarterTemplate(this.schema.template, this.schema.type, tag);
|
424 | await this.downloadStarterTemplate(projectDir, starterTemplate);
|
425 | }
|
426 | let project;
|
427 | if (this.project && this.project.details.context === 'multiapp' && !this.schema.cloned) {
|
428 |
|
429 | await utils_fs_1.unlink(path.resolve(projectDir, constants_1.PROJECT_FILE));
|
430 | project = await project_1.createProjectFromDetails({ context: 'multiapp', configPath: path.resolve(this.project.rootDirectory, constants_1.PROJECT_FILE), id: projectId, type: this.schema.type, errors: [] }, this.env);
|
431 | project.config.set('type', this.schema.type);
|
432 | project.config.set('root', path.relative(this.project.rootDirectory, projectDir));
|
433 | }
|
434 | else {
|
435 | project = await project_1.createProjectFromDirectory(projectDir, { _: [] }, this.env, { logErrors: false });
|
436 | }
|
437 |
|
438 |
|
439 |
|
440 | this.namespace.root.project = project;
|
441 | if (!this.project) {
|
442 | throw new errors_1.FatalException('Error while loading project.');
|
443 | }
|
444 | this.env.shell.alterPath = p => shell_1.prependNodeModulesBinToPath(projectDir, p);
|
445 | if (!this.schema.cloned) {
|
446 | if (this.schema.type === 'react') {
|
447 | options['capacitor'] = true;
|
448 | }
|
449 | if (options['cordova']) {
|
450 | const { confirmCordovaUsage } = await Promise.resolve().then(() => require('../lib/integrations/cordova/utils'));
|
451 | const confirm = await confirmCordovaUsage(this.env);
|
452 | if (confirm) {
|
453 | await executor_1.runCommand(runinfo, ['integrations', 'enable', 'cordova', '--quiet']);
|
454 | }
|
455 | else {
|
456 | options['cordova'] = false;
|
457 | }
|
458 | }
|
459 | if (options['capacitor'] === null && !options['cordova']) {
|
460 | const confirm = await this.env.prompt({
|
461 | type: 'confirm',
|
462 | name: 'confirm',
|
463 | message: 'Integrate your new app with Capacitor to target native iOS and Android?',
|
464 | default: false,
|
465 | });
|
466 | if (confirm) {
|
467 | options['capacitor'] = true;
|
468 | }
|
469 | }
|
470 | if (options['capacitor']) {
|
471 | await executor_1.runCommand(runinfo, ['integrations', 'enable', 'capacitor', '--quiet', '--', this.schema.name, packageId ? packageId : 'io.ionic.starter']);
|
472 | }
|
473 | await this.project.personalize({
|
474 | name: this.schema.name,
|
475 | projectId,
|
476 | packageId,
|
477 | appIcon: this.schema.appIcon,
|
478 | splash: this.schema.splash,
|
479 | themeColor: this.schema.themeColor,
|
480 | });
|
481 | this.env.log.nl();
|
482 | }
|
483 | const shellOptions = { cwd: projectDir, stdio: 'inherit' };
|
484 | if (options['deps']) {
|
485 | this.env.log.msg('Installing dependencies may take several minutes.');
|
486 | this.env.log.rawmsg(start_1.getAdvertisement());
|
487 | const [installer, ...installerArgs] = await pkgManagerArgs(this.env.config.get('npmClient'), { command: 'install' });
|
488 | await this.env.shell.run(installer, installerArgs, shellOptions);
|
489 | }
|
490 | if (!this.schema.cloned) {
|
491 | if (gitIntegration) {
|
492 | try {
|
493 | await this.env.shell.run('git', ['init'], shellOptions);
|
494 | }
|
495 | catch (e) {
|
496 | this.env.log.warn('Error encountered during repo initialization. Disabling further git operations.');
|
497 | gitIntegration = false;
|
498 | }
|
499 | }
|
500 |
|
501 | if (!this.env.session.isLoggedIn()) {
|
502 | await session_1.promptToSignup(this.env);
|
503 | }
|
504 | if (options['link']) {
|
505 | const cmdArgs = ['link'];
|
506 | if (appflowId) {
|
507 | cmdArgs.push(appflowId);
|
508 | }
|
509 | cmdArgs.push('--name', this.schema.name);
|
510 | await executor_1.runCommand(runinfo, cmdArgs);
|
511 | linkConfirmed = true;
|
512 | }
|
513 | const manifestPath = path.resolve(projectDir, 'ionic.starter.json');
|
514 | const manifest = await this.loadManifest(manifestPath);
|
515 | if (manifest) {
|
516 | await utils_fs_1.unlink(manifestPath);
|
517 | }
|
518 | if (gitIntegration) {
|
519 | try {
|
520 | await this.env.shell.run('git', ['add', '-A'], shellOptions);
|
521 | await this.env.shell.run('git', ['commit', '-m', 'Initial commit', '--no-gpg-sign'], shellOptions);
|
522 | }
|
523 | catch (e) {
|
524 | this.env.log.warn('Error encountered during commit. Disabling further git operations.');
|
525 | gitIntegration = false;
|
526 | }
|
527 | }
|
528 | if (manifest) {
|
529 | await this.performManifestOps(manifest);
|
530 | }
|
531 | }
|
532 | this.env.log.nl();
|
533 | await this.showNextSteps(projectDir, this.schema.cloned, linkConfirmed, !options['cordova']);
|
534 | }
|
535 | async checkForExisting(projectDir) {
|
536 | const projectExists = await utils_fs_1.pathExists(projectDir);
|
537 | if (projectExists) {
|
538 | const confirm = await this.env.prompt({
|
539 | type: 'confirm',
|
540 | name: 'confirm',
|
541 | message: `${color_1.input(utils_terminal_1.prettyPath(projectDir))} exists. ${color_1.failure('Overwrite?')}`,
|
542 | default: false,
|
543 | });
|
544 | if (!confirm) {
|
545 | this.env.log.msg(`Not erasing existing project in ${color_1.input(utils_terminal_1.prettyPath(projectDir))}.`);
|
546 | throw new errors_1.FatalException();
|
547 | }
|
548 | this.canRemoveExisting = confirm;
|
549 | }
|
550 | }
|
551 | async findStarterTemplate(template, type, tag) {
|
552 | const starterTemplate = start_1.STARTER_TEMPLATES.find(t => t.projectType === type && t.name === template);
|
553 | if (starterTemplate && starterTemplate.type === 'managed') {
|
554 | return {
|
555 | ...starterTemplate,
|
556 | archive: `${start_1.STARTER_BASE_URL}/${tag === 'latest' ? '' : `${tag}/`}${starterTemplate.id}.tar.gz`,
|
557 | };
|
558 | }
|
559 | const tasks = this.createTaskChain();
|
560 | tasks.next('Looking up starter');
|
561 | const starterList = await start_1.getStarterList(this.env.config, tag);
|
562 | const starter = starterList.starters.find(t => t.type === type && t.name === template);
|
563 | if (starter) {
|
564 | tasks.end();
|
565 | return {
|
566 | name: starter.name,
|
567 | projectType: starter.type,
|
568 | archive: `${start_1.STARTER_BASE_URL}/${tag === 'latest' ? '' : `${tag}/`}${starter.id}.tar.gz`,
|
569 | };
|
570 | }
|
571 | else {
|
572 | throw new errors_1.FatalException(`Unable to find starter template for ${color_1.input(template)}\n` +
|
573 | `If this is not a typo, please make sure it is a valid starter template within the starters repo: ${color_1.strong('https://github.com/ionic-team/starters')}`);
|
574 | }
|
575 | }
|
576 | async validateProjectType(type) {
|
577 | const projectTypes = start_1.getStarterProjectTypes();
|
578 | if (!['custom', ...projectTypes].includes(type)) {
|
579 | throw new errors_1.FatalException(`${color_1.input(type)} is not a valid project type.\n` +
|
580 | `Please choose a different ${color_1.input('--type')}. Use ${color_1.input('ionic start --list')} to list all available starter templates.`);
|
581 | }
|
582 | }
|
583 | async validateProjectId(projectId) {
|
584 | if (!project_1.isValidProjectId(projectId)) {
|
585 | throw new errors_1.FatalException(`${color_1.input(projectId)} is not a valid package or directory name.\n` +
|
586 | `Please choose a different ${color_1.input('--project-id')}. Alphanumeric characters are always safe.`);
|
587 | }
|
588 | }
|
589 | async loadManifest(manifestPath) {
|
590 | try {
|
591 | return await start_1.readStarterManifest(manifestPath);
|
592 | }
|
593 | catch (e) {
|
594 | debug(`Error with manifest file ${color_1.strong(utils_terminal_1.prettyPath(manifestPath))}: ${e}`);
|
595 | }
|
596 | }
|
597 | async performManifestOps(manifest) {
|
598 | if (manifest.welcome) {
|
599 | this.env.log.nl();
|
600 | this.env.log.msg(`${color_1.strong('Starter Welcome')}:`);
|
601 | this.env.log.msg(manifest.welcome);
|
602 | }
|
603 | }
|
604 | async downloadStarterTemplate(projectDir, starterTemplate) {
|
605 | const { createRequest, download } = await Promise.resolve().then(() => require('../lib/utils/http'));
|
606 | const { tar } = await Promise.resolve().then(() => require('../lib/utils/archive'));
|
607 | const tasks = this.createTaskChain();
|
608 | const task = tasks.next(`Downloading and extracting ${color_1.input(starterTemplate.name.toString())} starter`);
|
609 | debug('Tar extraction created for %s', projectDir);
|
610 | const ws = tar.extract({ cwd: projectDir });
|
611 | const { req } = await createRequest('GET', starterTemplate.archive, this.env.config.getHTTPConfig());
|
612 | await download(req, ws, { progress: (loaded, total) => task.progress(loaded, total) });
|
613 | tasks.end();
|
614 | }
|
615 | async showNextSteps(projectDir, cloned, linkConfirmed, isCapacitor) {
|
616 | const cordovaResCommand = isCapacitor ? 'cordova-res --skip-config --copy' : 'cordova-res';
|
617 | const steps = [
|
618 | `Go to your ${cloned ? 'cloned' : 'new'} project: ${color_1.input(`cd ${utils_terminal_1.prettyPath(projectDir)}`)}`,
|
619 | `Run ${color_1.input('ionic serve')} within the app directory to see your app in the browser`,
|
620 | isCapacitor ?
|
621 | `Run ${color_1.input('ionic capacitor add')} to add a native iOS or Android project using Capacitor` :
|
622 | `Run ${color_1.input('ionic cordova platform add')} to add a native iOS or Android project using Cordova`,
|
623 | `Generate your app icon and splash screens using ${color_1.input(cordovaResCommand)}`,
|
624 | `Explore the Ionic docs for components, tutorials, and more: ${color_1.strong('https://ion.link/docs')}`,
|
625 | `Building an enterprise app? Ionic has Enterprise Support and Features: ${color_1.strong('https://ion.link/enterprise-edition')}`,
|
626 | ];
|
627 | if (linkConfirmed) {
|
628 | steps.push(`Push your code to Ionic Appflow to perform real-time updates, and more: ${color_1.input('git push ionic master')}`);
|
629 | }
|
630 | this.env.log.msg(`${color_1.strong('Your Ionic app is ready! Follow these next steps')}:\n${steps.map(s => ` - ${s}`).join('\n')}`);
|
631 | }
|
632 | }
|
633 | exports.StartCommand = StartCommand;
|