diff --git a/cli/commands/create.js b/cli/commands/create.js index 45311a94a8c..6f0d3875f5d 100644 --- a/cli/commands/create.js +++ b/cli/commands/create.js @@ -61,7 +61,7 @@ export class CreateCommand { const typeConf = {}; for (const filename of fs.readdirSync(creatorDir)) { - if (!jsRegExp.test(filename)) { + if (!jsRegExp.test(filename) || filename === 'base_app.js') { continue; } @@ -148,6 +148,12 @@ export class CreateCommand { var type = cli.argv.type, creator = this.creators[type]; + let useAlloy = false; + if (creator.type === 'app_alloy') { + useAlloy = true; + creator.type = 'app'; + } + // load the project type lib logger.info(`Creating ${type.cyan} project`); @@ -179,8 +185,7 @@ export class CreateCommand { } else { logger.info(`Project created successfully in ${appc.time.prettyDiff(cli.startTime, Date.now())}\n`); } - - if (cli.argv.alloy !== undefined) { + if (cli.argv.alloy !== undefined || useAlloy) { execSync(`alloy new "${path.join(cli.argv['workspace-dir'], cli.argv.name)}"`, { stdio: 'inherit' }); } diff --git a/cli/lib/creator.js b/cli/lib/creator.js index acab08f7c11..6e6e81681b3 100644 --- a/cli/lib/creator.js +++ b/cli/lib/creator.js @@ -63,6 +63,10 @@ export class Creator { * @param {Function} callback - A function to call after the function finishes */ run() { + if (this.cli.argv.type === 'app_alloy') { + // alloy app - reset type to normal app + this.cli.argv.type = 'app'; + } this.projectType = this.cli.argv.type; this.sdk = this.cli.env.getSDK(this.cli.argv.sdk); } diff --git a/cli/lib/creators/app.js b/cli/lib/creators/app.js index ba7cf3b6679..9bbee8c617a 100644 --- a/cli/lib/creators/app.js +++ b/cli/lib/creators/app.js @@ -10,19 +10,14 @@ * Please see the LICENSE included with this distribution for details. */ -import appc from 'node-appc'; -import { Creator } from '../creator.js'; -import fs from 'fs-extra'; -import path from 'node:path'; -import ti from 'node-titanium-sdk'; -import { randomUUID } from 'node:crypto'; +import { BaseAppCreator } from './base_app.js'; /** * Creates application projects. * * @module lib/creators/app */ -export class AppCreator extends Creator { +export class AppCreator extends BaseAppCreator { /** * Constructs the app creator. * @class @@ -33,172 +28,11 @@ export class AppCreator extends Creator { * @param {Object} cli - The CLI instance */ constructor(logger, config, cli) { // eslint-disable-line no-unused-vars - super(logger, config, cli); - - this.title = 'Titanium App'; - this.titleOrder = 1; - this.type = 'app'; - - // build list of all valid platforms - const availablePlatforms = {}, - validPlatforms = {}; - - ti.platforms.forEach(function (platform) { - if (/^iphone|ios|ipad$/.test(platform)) { - validPlatforms['iphone'] = availablePlatforms['iphone'] = 1; - validPlatforms['ipad'] = availablePlatforms['ipad'] = 1; - validPlatforms['ios'] = 1; - } else { - validPlatforms[platform] = availablePlatforms[platform] = 1; - } + super(logger, config, cli, { + title: 'Titanium App (Classic)', + titleOrder: 1, + type: 'app' }); - - // add "all" - validPlatforms['all'] = 1; - - this.availablePlatforms = [ 'all' ].concat(Object.keys(availablePlatforms)); - this.validPlatforms = validPlatforms; - } - - /** - * Initializes the app creator. - * @return {object} - */ - init() { - return { - options: { - id: this.configOptionId(150), - name: this.configOptionName(140), - platforms: this.configOptionPlatforms(120), - template: this.configOptionTemplate(110), - 'workspace-dir': this.configOptionWorkspaceDir(170) - } - }; - } - - /** - * Creates the project directory and copies the project files. - * @param {Function} callback - A function to call after the project has been created - */ - run(callback) { - super.run(); - - const argv = this.cli.argv, - platforms = ti.scrubPlatforms(argv.platforms), - projectDir = appc.fs.resolvePath(argv['workspace-dir'], argv.name); - - fs.ensureDirSync(projectDir); - - // download/install the project template - this.processTemplate(function (err, templateDir) { - if (err) { - return callback(err); - } - - let projectConfig = null; - const tasks = [ - function (next) { - // copy the template files, if exists - const dir = path.join(templateDir, 'template'); - if (!fs.existsSync(dir)) { - next(); - } else { - this.logger.info(`Template directory: ${templateDir.cyan}`); - this.copyDir(dir, projectDir, next); - } - }, - - function (next) { - // create the tiapp.xml - const params = { - id: argv.id, - name: argv.name, - url: argv.url || '', - version: '1.0', - guid: randomUUID(), - 'deployment-targets': {}, - 'sdk-version': this.sdk.name - }, - tiappFile = path.join(projectDir, 'tiapp.xml'); - - if (platforms.original.indexOf('ios') !== -1) { - platforms.original.indexOf('ipad') !== -1 || platforms.original.push('ipad'); - platforms.original.indexOf('iphone') !== -1 || platforms.original.push('iphone'); - } - - ti.availablePlatformsNames.forEach(function (p) { - if (p !== 'ios') { - params['deployment-targets'][p] = platforms.original.indexOf(p) !== -1; - } - }); - - this.cli.createHook('create.populateTiappXml', this, function (tiapp, params, done) { - // read and populate the tiapp.xml - this.logger.info('Writing tiapp.xml'); - projectConfig = appc.util.mix(tiapp, params); - projectConfig.save(tiappFile); - done(); - }.bind(this))(fs.existsSync(tiappFile) ? new ti.tiappxml(tiappFile) : new ti.tiappxml(), params, next); - }, - - function (next) { - // make sure the Resources dir exists - const dir = path.join(projectDir, 'Resources'); - fs.ensureDirSync(dir); - next(); - } - ]; - - platforms.scrubbed.forEach(function (platform) { - // if we're using the built-in template, load the platform specific template hooks - const usingBuiltinTemplate = templateDir.indexOf(this.sdk.path) === 0, - platformTemplateDir = path.join(this.sdk.path, platform, 'templates', this.projectType, this.cli.argv.template); - - if (usingBuiltinTemplate) { - this.cli.scanHooks(path.join(platformTemplateDir, 'hooks')); - } - - tasks.push(function (next) { - this.cli.emit([ - 'create.pre.platform.' + platform, - 'create.pre.' + this.projectType + '.platform.' + platform - ], this, async function (err) { - if (err) { - return next(err); - } - - const finalize = () => { - this.cli.emit([ - 'create.post.' + this.projectType + '.platform.' + platform, - 'create.post.platform.' + platform - ], this, next); - }; - - // legacy... only copy platform specific files if we're copying from a built-in template - if (!usingBuiltinTemplate) { - return finalize(); - } - - const p = path.join(this.sdk.path, platform, 'cli', 'commands', '_create.js'); - if (fs.existsSync(p)) { - this.logger.info(`Copying ${platform.cyan} platform resources`); - const { run } = await import(p); - await run(this.logger, this.config, this.cli, projectConfig); - return finalize(); - } - - // does this platform have new or old style implementations? - const templatePath = path.join(platformTemplateDir, 'template'); - if (!fs.existsSync(templatePath)) { - return finalize(); - } - this.copyDir(templatePath, projectDir, finalize); - }.bind(this)); - }); - }, this); - - appc.async.series(this, tasks, callback); - }.bind(this)); } } diff --git a/cli/lib/creators/app_alloy.js b/cli/lib/creators/app_alloy.js new file mode 100644 index 00000000000..52d61c12389 --- /dev/null +++ b/cli/lib/creators/app_alloy.js @@ -0,0 +1,39 @@ +/** + * @overview + * Logic for creating new Titanium apps. + * + * @copyright + * Copyright TiDev, Inc. 04/07/2022-Present + * + * @license + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ + +import { BaseAppCreator } from './base_app.js'; + +/** + * Creates application projects. + * + * @module lib/creators/app + */ +export class AppCreator extends BaseAppCreator { + /** + * Constructs the app creator. + * @class + * @classdesc Creates an app project. + * @constructor + * @param {Object} logger - The logger instance + * @param {Object} config - The CLI config + * @param {Object} cli - The CLI instance + */ + constructor(logger, config, cli) { // eslint-disable-line no-unused-vars + super(logger, config, cli, { + title: 'Titanium App (Alloy)', + titleOrder: 2, + type: 'app_alloy' + }); + } +} + +export default AppCreator; diff --git a/cli/lib/creators/applewatch.js b/cli/lib/creators/applewatch.js index e2cf8420399..e611c4a6a2a 100644 --- a/cli/lib/creators/applewatch.js +++ b/cli/lib/creators/applewatch.js @@ -42,7 +42,7 @@ export class AppleWatchCreator extends Creator { super(logger, config, cli); this.title = 'Apple Watchâ„¢ App'; - this.titleOrder = 3; + this.titleOrder = 4; this.type = 'applewatch'; } diff --git a/cli/lib/creators/base_app.js b/cli/lib/creators/base_app.js new file mode 100644 index 00000000000..0818b49a3a9 --- /dev/null +++ b/cli/lib/creators/base_app.js @@ -0,0 +1,207 @@ +/** + * @overview + * Base logic for creating new Titanium apps. + * + * @copyright + * Copyright TiDev, Inc. 04/07/2022-Present + * + * @license + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ + +import appc from 'node-appc'; +import { Creator } from '../creator.js'; +import fs from 'fs-extra'; +import path from 'node:path'; +import ti from 'node-titanium-sdk'; +import { randomUUID } from 'node:crypto'; + +/** + * Creates application projects. + * + * @module lib/creators/base_app + */ +export class BaseAppCreator extends Creator { + /** + * Constructs the base app creator. + * @class + * @classdesc Creates an app project. + * @constructor + * @param {Object} logger - The logger instance + * @param {Object} config - The CLI config + * @param {Object} cli - The CLI instance + * @param {Object} options - Configuration options + * @param {string} options.title - The display title for this creator + * @param {number} options.titleOrder - The order for display in prompts + * @param {string} options.type - The project type identifier + */ + constructor(logger, config, cli, options = {}) { + super(logger, config, cli); + + this.title = options.title || 'Titanium App (Classic)'; + this.titleOrder = options.titleOrder || 1; + this.type = options.type || 'app'; + + // build list of all valid platforms + const availablePlatforms = {}, + validPlatforms = {}; + + ti.platforms.forEach(function (platform) { + if (/^iphone|ios|ipad$/.test(platform)) { + validPlatforms['iphone'] = availablePlatforms['iphone'] = 1; + validPlatforms['ipad'] = availablePlatforms['ipad'] = 1; + validPlatforms['ios'] = 1; + } else { + validPlatforms[platform] = availablePlatforms[platform] = 1; + } + }); + + // add "all" + validPlatforms['all'] = 1; + + this.availablePlatforms = [ 'all' ].concat(Object.keys(availablePlatforms)); + this.validPlatforms = validPlatforms; + } + + /** + * Initializes the app creator. + * @return {object} + */ + init() { + return { + options: { + id: this.configOptionId(150), + name: this.configOptionName(140), + platforms: this.configOptionPlatforms(120), + template: this.configOptionTemplate(110), + 'workspace-dir': this.configOptionWorkspaceDir(170) + } + }; + } + + /** + * Creates the project directory and copies the project files. + * @param {Function} callback - A function to call after the project has been created + */ + run(callback) { + super.run(); + + const argv = this.cli.argv, + platforms = ti.scrubPlatforms(argv.platforms), + projectDir = appc.fs.resolvePath(argv['workspace-dir'], argv.name); + + fs.ensureDirSync(projectDir); + + // download/install the project template + this.processTemplate(function (err, templateDir) { + if (err) { + return callback(err); + } + + let projectConfig = null; + const tasks = [ + function (next) { + // copy the template files, if exists + const dir = path.join(templateDir, 'template'); + if (!fs.existsSync(dir)) { + next(); + } else { + this.logger.info(`Template directory: ${templateDir.cyan}`); + this.copyDir(dir, projectDir, next); + } + }, + + function (next) { + // create the tiapp.xml + const params = { + id: argv.id, + name: argv.name, + url: argv.url || '', + version: '1.0', + guid: randomUUID(), + 'deployment-targets': {}, + 'sdk-version': this.sdk.name + }, + tiappFile = path.join(projectDir, 'tiapp.xml'); + + if (platforms.original.indexOf('ios') !== -1) { + platforms.original.indexOf('ipad') !== -1 || platforms.original.push('ipad'); + platforms.original.indexOf('iphone') !== -1 || platforms.original.push('iphone'); + } + + ti.availablePlatformsNames.forEach(function (p) { + if (p !== 'ios') { + params['deployment-targets'][p] = platforms.original.indexOf(p) !== -1; + } + }); + + this.cli.createHook('create.populateTiappXml', this, function (tiapp, params, done) { + // read and populate the tiapp.xml + this.logger.info('Writing tiapp.xml'); + projectConfig = appc.util.mix(tiapp, params); + projectConfig.save(tiappFile); + done(); + }.bind(this))(fs.existsSync(tiappFile) ? new ti.tiappxml(tiappFile) : new ti.tiappxml(), params, next); + }, + + function (next) { + // make sure the Resources dir exists + const dir = path.join(projectDir, 'Resources'); + fs.ensureDirSync(dir); + next(); + } + ]; + + platforms.scrubbed.forEach(function (platform) { + // if we're using the built-in template, load the platform specific template hooks + const usingBuiltinTemplate = templateDir.indexOf(this.sdk.path) === 0, + platformTemplateDir = path.join(this.sdk.path, platform, 'templates', this.projectType, this.cli.argv.template); + + if (usingBuiltinTemplate) { + this.cli.scanHooks(path.join(platformTemplateDir, 'hooks')); + } + + tasks.push(function (next) { + this.cli.emit([ + 'create.pre.platform.' + platform, + 'create.pre.' + this.projectType + '.platform.' + platform + ], this, async function (err) { + if (err) { + return next(err); + } + + const finalize = () => { + this.cli.emit([ + 'create.post.' + this.projectType + '.platform.' + platform, + 'create.post.platform.' + platform + ], this, next); + }; + + // legacy... only copy platform specific files if we're copying from a built-in template + if (!usingBuiltinTemplate) { + return finalize(); + } + + const p = path.join(this.sdk.path, platform, 'cli', 'commands', '_create.js'); + if (fs.existsSync(p)) { + this.logger.info(`Copying ${platform.cyan} platform resources`); + const { run } = await import(p); + await run(this.logger, this.config, this.cli, projectConfig); + return finalize(); + } + + // does this platform have new or old style implementations? + const templatePath = path.join(platformTemplateDir, 'template'); + if (!fs.existsSync(templatePath)) { + return finalize(); + } + this.copyDir(templatePath, projectDir, finalize); + }.bind(this)); + }); + }, this); + + appc.async.series(this, tasks, callback); + }.bind(this)); + } +} diff --git a/cli/lib/creators/module.js b/cli/lib/creators/module.js index ee226de232f..c9b66e3cb9e 100644 --- a/cli/lib/creators/module.js +++ b/cli/lib/creators/module.js @@ -37,7 +37,7 @@ export class ModuleCreator extends Creator { super(logger, config, cli); this.title = 'Titanium Module'; - this.titleOrder = 2; + this.titleOrder = 3; this.type = 'module'; // build list of all valid platforms