create-zephyr-apps
Version:
A CLI tool to create web applications using Zephyr.
187 lines (183 loc) • 7.72 kB
JavaScript
import { parseArgs } from "node:util";
import { DEFAULT_WEB_TEMPLATE, ProjectTypes, TemplateRepositories, Templates, getTemplate } from "./templates.js";
const PackageManagers = [
'pnpm',
'npm',
'yarn',
'bun'
];
class OperationCancelled extends Error {
constructor(){
super('Operation cancelled.');
this.name = 'OperationCancelled';
}
}
const NON_INTERACTIVE_EXAMPLE_ARGS = [
'./apps/example',
'--template',
'react-rsbuild',
'--package-manager',
'pnpm',
'--no-git',
'--install',
'--build',
'--json'
];
const NON_INTERACTIVE_EXAMPLE = `create-zephyr-apps ${NON_INTERACTIVE_EXAMPLE_ARGS.join(' ')}`;
const HELP_TEXT = `
Usage: create-zephyr-apps [directory] [options]
Create a Zephyr application from a version-pinned template.
Options:
--directory, -d <path> Project directory (alternative to positional)
--template, -t <id> Web template ID (default: ${DEFAULT_WEB_TEMPLATE})
--project-type <type> web or react-native (default: web)
--package-manager <manager> pnpm, npm, yarn, or bun
--template-revision <commit> Override the pinned template with a full commit SHA
--git Initialize Git and create an initial commit
--no-git Do not initialize Git
--install Install dependencies
--build Install dependencies and run the build script
--json Emit one machine-readable JSON result
--yes, -y Use deterministic defaults without prompting
--list-templates List available web template IDs
--version, -v Print the CLI version
--help, -h Show this help
Example:
${NON_INTERACTIVE_EXAMPLE}
`.trim();
function parseCliArgs(args) {
const parsed = parseArgs({
args,
allowPositionals: true,
strict: true,
options: {
directory: {
type: 'string',
short: 'd'
},
template: {
type: 'string',
short: 't'
},
'project-type': {
type: 'string'
},
'package-manager': {
type: 'string'
},
'template-revision': {
type: 'string'
},
git: {
type: 'boolean'
},
'no-git': {
type: 'boolean'
},
install: {
type: 'boolean'
},
build: {
type: 'boolean'
},
json: {
type: 'boolean'
},
yes: {
type: 'boolean',
short: 'y'
},
'list-templates': {
type: 'boolean'
},
version: {
type: 'boolean',
short: 'v'
},
help: {
type: 'boolean',
short: 'h'
}
}
});
if (parsed.positionals.length > 1) throw new Error('Only one positional project directory is supported.');
const positionalDirectory = parsed.positionals[0];
const flagDirectory = parsed.values.directory;
if (positionalDirectory && flagDirectory) throw new Error('Provide the project directory either positionally or with --directory, not both.');
if (parsed.values.git && parsed.values['no-git']) throw new Error('--git and --no-git cannot be used together.');
const projectType = parsed.values['project-type'];
if (void 0 !== projectType && !ProjectTypes.some((candidate)=>candidate.value === projectType)) throw new Error(`Unsupported project type "${projectType}". Expected web or react-native.`);
const packageManager = parsed.values['package-manager'];
if (void 0 !== packageManager && !PackageManagers.includes(packageManager)) throw new Error(`Unsupported package manager "${packageManager}". Expected ${PackageManagers.join(', ')}.`);
const template = parsed.values.template;
if (void 0 !== template && !getTemplate(template)) throw new Error(`Unknown template "${template}". Available templates: ${Templates.map(({ name })=>name).join(', ')}.`);
if ('react-native' === projectType && void 0 !== template) throw new Error('--template is only supported for web projects.');
const templateRevision = parsed.values['template-revision'];
if (void 0 !== templateRevision && !/^[a-f\d]{40}$/iu.test(templateRevision)) throw new Error('--template-revision must be a full 40-character commit SHA.');
return {
directory: flagDirectory ?? positionalDirectory,
template,
projectType: projectType,
packageManager: packageManager,
initializeGit: parsed.values.git ? true : parsed.values['no-git'] ? false : void 0,
install: parsed.values.install ?? false,
build: parsed.values.build ?? false,
json: parsed.values.json ?? false,
yes: parsed.values.yes ?? false,
templateRevision,
listTemplates: parsed.values['list-templates'] ?? false,
help: parsed.values.help ?? false,
version: parsed.values.version ?? false
};
}
function shouldUseInteractiveMode(options, terminal) {
return void 0 === options.directory && !options.yes && !options.json && Boolean(terminal.inputIsTTY && terminal.outputIsTTY);
}
async function resolveCliOptions(options, context) {
const prompts = context.prompts;
let directory = options.directory;
if (void 0 === directory && context.interactive && prompts) {
directory = await prompts.directory();
if (void 0 === directory) throw new OperationCancelled();
}
if (!directory?.trim()) throw new Error('A project directory is required in non-interactive mode. Pass it positionally or with --directory.');
let projectType = options.projectType ?? (options.template ? 'web' : void 0);
if (void 0 === projectType && context.interactive && prompts) {
projectType = await prompts.projectType();
if (void 0 === projectType) throw new OperationCancelled();
}
projectType ??= 'web';
let template = 'web' === projectType ? options.template : void 0;
if ('web' === projectType && void 0 === template && context.interactive && prompts) {
template = await prompts.template();
if (void 0 === template) throw new OperationCancelled();
}
template ??= 'web' === projectType ? DEFAULT_WEB_TEMPLATE : void 0;
let initializeGit = options.initializeGit;
if (void 0 === initializeGit && context.interactive && prompts) {
initializeGit = await prompts.initializeGit();
if (void 0 === initializeGit) throw new OperationCancelled();
}
initializeGit ??= false;
const repository = TemplateRepositories[projectType];
return {
directory,
template,
projectType,
packageManager: options.packageManager,
initializeGit,
install: options.install || options.build,
build: options.build,
json: options.json,
templateRevision: options.templateRevision ?? repository.revision
};
}
function listTemplatesJson() {
return JSON.stringify({
default: DEFAULT_WEB_TEMPLATE,
revision: TemplateRepositories.web.revision,
templates: Templates
}, null, 2);
}
export { HELP_TEXT, NON_INTERACTIVE_EXAMPLE, NON_INTERACTIVE_EXAMPLE_ARGS, OperationCancelled, PackageManagers, listTemplatesJson, parseCliArgs, resolveCliOptions, shouldUseInteractiveMode };
//# sourceMappingURL=cli.js.map