Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions cli/commands/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -99,14 +99,14 @@ export class CreateCommand {
options: Object.assign({
type: {
abbr: 't',
default: cli.argv.prompt ? undefined : 'app',
default: cli.argv.prompt ? undefined : '1',
desc: 'the type of project to create',
order: 100,
prompt: (callback) => {
callback(fields.select({
title: 'What type of project would you like to create?',
promptLabel: 'Select a type by number or name',
default: 'app',
default: '1',
margin: '',
numbered: true,
relistOnError: true,
Expand Down Expand Up @@ -148,6 +148,12 @@ export class CreateCommand {
var type = cli.argv.type,
creator = this.creators[type];

let useAlloy = false;
if (creator.type === 'alloy') {
useAlloy = true;
creator.type = 'app';
}

// load the project type lib
logger.info(`Creating ${type.cyan} project`);

Expand Down Expand Up @@ -178,10 +184,14 @@ export class CreateCommand {
logger.error(`Failed to create project after ${appc.time.prettyDiff(cli.startTime, Date.now())}\n`);
} else {
logger.info(`Project created successfully in ${appc.time.prettyDiff(cli.startTime, Date.now())}\n`);
}

if (cli.argv.alloy !== undefined) {
execSync(`alloy new "${path.join(cli.argv['workspace-dir'], cli.argv.name)}"`, { stdio: 'inherit' });
if (cli.argv.alloy !== undefined || useAlloy) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be moved before the "Project created successfully".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bildschirmfoto_20260219_161500

vs

Bildschirmfoto_20260219_161615

Not sure if we should end with the error message or not. I see the argument that in the first one it will be more visible that the project is created.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what I am trying to suggest is we have multiple sad paths and one happy path.

  • If the create fails
    • then show error
    • exit w/ code 1
  • If useAlloy
    • run alloy new
    • if error
      • show error
      • exit w/ code 1
  • show success message

Copy link
Copy Markdown
Contributor Author

@m1ga m1ga Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trying to move it above the message "Project created successfully" but I'm having troubles with github at the moment 😞

update: looks like it worked, guess it was some caching.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking we should process.exit(1), but now I'm wondering we should throw and let the CLI catch it and exit w/ code 1.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pushed an update that will show the Project created successfully message ONLY if it's really created.

So: ti create --alloy without Alloy installed will now just show the ERROR message as you wanted to have an Alloy project and it's not "successful".
All other cases (alloy installed or creating a classic project) will show Project created successfully at the end.

try {
const output = execSync(`alloy new "${path.join(cli.argv['workspace-dir'], cli.argv.name)}"`, { encoding: 'utf8' });
(output?.trim() || '').split('\n').forEach(line => logger.info(line));
} catch (_alloyError) {
logger.error('Alloy is not installed. Run "npm i -g alloy" to install it, then run "alloy new" inside the project folder.');
}
}
}

finished(err);
Expand Down
4 changes: 4 additions & 0 deletions cli/lib/creator.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export class Creator {
* @param {Function} callback - A function to call after the function finishes
*/
run() {
if (this.cli.argv.type === '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);
}
Expand Down
178 changes: 6 additions & 172 deletions cli/lib/creators/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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));
}
}

Expand Down
50 changes: 50 additions & 0 deletions cli/lib/creators/app_alloy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @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';
import { execSync } from 'node:child_process';

/**
* 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: 'alloy'
});
}

init() {
// check if alloy is installed
try {
execSync('alloy --version');
} catch (err) {
throw new Error('Alloy is not installed');
}
return super.init();
}
}

export default AppCreator;
2 changes: 1 addition & 1 deletion cli/lib/creators/applewatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}

Expand Down
Loading
Loading