purgetss
Version:
A package that simplifies mobile app creation for Titanium developers.
204 lines (182 loc) • 7.44 kB
JavaScript
/**
* PurgeTSS v7.1 - Create Command
*
* CLI command for creating new Alloy projects with PurgeTSS.
* COPIED from src/index.js v6 original with Android App ID fix.
*
* @fileoverview Project creation command
* @version 7.1.0
* @author César Estrada
* @since 2025-06-15
*/
import fs from 'fs'
import path from 'path'
import chalk from 'chalk'
import inquirer from 'inquirer'
import { exec, execSync } from 'child_process'
import commandExistsSync from 'command-exists'
import { logger } from '../../shared/logger.js'
import { projectRoot } from '../../shared/constants.js'
import { start, finish } from '../utils/cli-helpers.js'
/**
* Robust folder deletion with retry for node_modules issues
* @param {string} folderPath - Path to folder to delete
* @param {Function} callback - Callback function
*/
function robustDelete(folderPath, callback) {
const deleteCommand = `chown -R $USER "${folderPath}" 2>/dev/null; rm -rf "${folderPath}"`
exec(deleteCommand, (error) => {
if (error) {
// Retry once after a brief delay for stubborn node_modules
setTimeout(() => {
exec(`rm -rf "${folderPath}"`, (retryError) => {
if (retryError) {
logger.error(`Failed to delete folder: ${retryError.message}`)
return callback(retryError)
}
callback(null)
})
}, 1000) // 1 second delay
} else {
callback(null)
}
})
}
/**
* Create a new Titanium project with PurgeTSS
* COPIED exactly from original v6 createProject() function
* with Android App ID sanitization fix
*
* @param {string} workspace - Workspace path
* @param {string} argsName - Project name
* @param {string} projectID - Project ID
* @param {Object} options - Creation options
*/
function createProject(workspace, argsName, projectID, options) {
const projectName = `"${argsName}"`
const projectDirectory = `${workspace}/${projectName}`
logger.startSection()
logger.info('Creating Titanium project', chalk.yellow(`'${argsName}'`), 'in', chalk.yellow(workspace))
execSync(`ti create -n ${projectName} -t app -p all --alloy --no-prompt --id ${projectID}`)
execSync(`cd ${projectDirectory} && purgetss w && purgetss b`)
// Remove default index.tss file (not needed with PurgeTSS - app.tss is auto-generated)
// Use argsName instead of projectName because projectName includes quotes for shell commands
const indexTssPath = `${workspace}/${argsName}/app/styles/index.tss`
if (fs.existsSync(indexTssPath)) {
try {
fs.unlinkSync(indexTssPath)
} catch (error) {
logger.error(`Failed to remove index.tss: ${error.message}`)
}
}
// Copy PurgeTSS welcome screen (index.xml and index.js)
const templatesDir = path.resolve(projectRoot, 'lib/templates/create')
const viewsDir = `${workspace}/${argsName}/app/views`
const controllersDir = `${workspace}/${argsName}/app/controllers`
try {
fs.copyFileSync(`${templatesDir}/index.xml`, `${viewsDir}/index.xml`)
fs.copyFileSync(`${templatesDir}/index.js`, `${controllersDir}/index.js`)
logger.info('PurgeTSS welcome screen installed')
} catch (error) {
logger.error(`Failed to copy PurgeTSS templates: ${error.message}`)
}
if (options.vendor) {
logger.info('Installing Fonts')
execSync(`cd ${projectDirectory} && purgetss il -m -v=${options.vendor}`)
}
if (options.module) {
logger.info(`Installing ${chalk.green('purgetss.ui')}`)
execSync(`cd ${projectDirectory} && purgetss m`)
}
if (options.dependencies) {
logger.info(`Creating a new ${chalk.yellow('package.json')} file`)
execSync(`cd ${projectDirectory} && npm init -y`)
execSync(`cd ${projectDirectory} && echo "/node_modules" >>.gitignore`)
if (commandExistsSync.sync('code')) {
execSync(`cp -R ${path.resolve(projectRoot)}/dist/configs/vscode/ ${projectDirectory}/.vscode`)
}
execSync(`cp ${path.resolve(projectRoot)}/dist/configs/invisible/.editorconfig ${projectDirectory}`)
logger.info(`Installing ${chalk.green('ESLint')}`)
execSync(`cd ${projectDirectory} && npm i -D eslint eslint-config-axway eslint-plugin-alloy --silent`)
execSync(`cp ${path.resolve(projectRoot)}/dist/configs/invisible/.eslintrc.js ${projectDirectory}`)
logger.info(`Installing ${chalk.green('Tailwind CSS')}`)
execSync(`cd ${projectDirectory} && npm i -D tailwindcss@3 --silent && npx tailwindcss init`)
}
finish(`The ${chalk.yellow(`'${argsName}'`)} project was created successfully in`)
logger.endSection()
// Auto-open editor like original v6
if (commandExistsSync.sync('code')) {
execSync(`cd ${projectDirectory} && code .`)
} else if (commandExistsSync.sync('subl')) {
execSync(`cd ${projectDirectory} && subl .`)
} else {
execSync(`cd ${projectDirectory} && open .`)
}
}
/**
* Create new Alloy project command
* COPIED exactly from original v6 create() function
* with Android App ID sanitization fix
*
* @param {Object} args - Command arguments
* @param {Object} options - Command options
*/
export function create(args, options) {
start()
exec('ti config app.idprefix && ti config app.workspace', (_error, stdout) => {
const results = stdout.split('\n')
const idPrefix = results[0]
const workspace = results[1]
if (idPrefix !== 'app.idprefix not found' && workspace !== '') {
// Android App ID fix: remove numbers and special chars
const projectID = `${idPrefix}.${args.name.normalize('NFD').replace(/[\u0300-\u036f]/g, '').split(/ |-|_/).join('').toLowerCase().replace(/[^a-z]/g, '')}`
console.log('')
if (fs.existsSync(`${workspace}/${args.name}`)) {
if (options.force) {
logger.info('Deleting existing project\'s folder')
robustDelete(`${workspace}/${args.name}`, error => {
if (error) return logger.error(error)
createProject(workspace, args.name, projectID, options)
})
} else {
inquirer.prompt([{
type: 'confirm',
name: 'delete',
message: `The folder '${args.name}' already exists. Do you want to delete it?`,
default: false
}]).then(answers => {
if (answers.delete) {
logger.info('Deleting existing project\'s folder')
robustDelete(`${workspace}/${args.name}`, error => {
if (error) return logger.error(error)
createProject(workspace, args.name, projectID, options)
})
} else {
console.log('')
logger.warn(chalk.yellow('Project creation has been canceled!'))
}
return null
}).catch(error => {
logger.error(error)
})
}
} else {
createProject(workspace, args.name, projectID, options)
}
} else {
console.log('')
logger.block(
chalk.red("Can't create a Titanium project"),
`You must have ${chalk.green('`app.idprefix`')} and ${chalk.green('`app.workspace`')} configured to create a project with ${chalk.green('`PurgeTSS`')}`,
'',
'Please, set them like this:',
` ${chalk.green('ti config app.idprefix')} ${chalk.yellow(`'com.your.reverse.domain'`)}`,
` ${chalk.green('ti config app.workspace')} ${chalk.yellow(`'path/to/your/workspace'`)}`
)
}
})
}
/**
* Export for CLI usage
*/
export default create