@entro314labs/create-at3-app
Version:
Scaffold new AT3 Stack projects with AI, edge, and modern tooling
977 lines (959 loc) • 30.5 kB
JavaScript
#!/usr/bin/env node
#!/usr/bin/env node
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
// src/index.ts
import { existsSync as existsSync2, promises as fs } from "fs";
import path from "path";
import { fileURLToPath } from "url";
import {
cancel,
confirm,
intro,
isCancel,
note,
outro,
select,
spinner,
text
} from "@clack/prompts";
// ../../node_modules/.pnpm/chalk@5.5.0/node_modules/chalk/source/vendor/ansi-styles/index.js
var ANSI_BACKGROUND_OFFSET = 10;
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
var styles = {
modifier: {
reset: [0, 0],
// 21 isn't widely supported and 22 does the same thing
bold: [1, 22],
dim: [2, 22],
italic: [3, 23],
underline: [4, 24],
overline: [53, 55],
inverse: [7, 27],
hidden: [8, 28],
strikethrough: [9, 29]
},
color: {
black: [30, 39],
red: [31, 39],
green: [32, 39],
yellow: [33, 39],
blue: [34, 39],
magenta: [35, 39],
cyan: [36, 39],
white: [37, 39],
// Bright color
blackBright: [90, 39],
gray: [90, 39],
// Alias of `blackBright`
grey: [90, 39],
// Alias of `blackBright`
redBright: [91, 39],
greenBright: [92, 39],
yellowBright: [93, 39],
blueBright: [94, 39],
magentaBright: [95, 39],
cyanBright: [96, 39],
whiteBright: [97, 39]
},
bgColor: {
bgBlack: [40, 49],
bgRed: [41, 49],
bgGreen: [42, 49],
bgYellow: [43, 49],
bgBlue: [44, 49],
bgMagenta: [45, 49],
bgCyan: [46, 49],
bgWhite: [47, 49],
// Bright color
bgBlackBright: [100, 49],
bgGray: [100, 49],
// Alias of `bgBlackBright`
bgGrey: [100, 49],
// Alias of `bgBlackBright`
bgRedBright: [101, 49],
bgGreenBright: [102, 49],
bgYellowBright: [103, 49],
bgBlueBright: [104, 49],
bgMagentaBright: [105, 49],
bgCyanBright: [106, 49],
bgWhiteBright: [107, 49]
}
};
var modifierNames = Object.keys(styles.modifier);
var foregroundColorNames = Object.keys(styles.color);
var backgroundColorNames = Object.keys(styles.bgColor);
var colorNames = [...foregroundColorNames, ...backgroundColorNames];
function assembleStyles() {
const codes = /* @__PURE__ */ new Map();
for (const [groupName, group] of Object.entries(styles)) {
for (const [styleName, style2] of Object.entries(group)) {
styles[styleName] = {
open: `\x1B[${style2[0]}m`,
close: `\x1B[${style2[1]}m`
};
group[styleName] = styles[styleName];
codes.set(style2[0], style2[1]);
}
Object.defineProperty(styles, groupName, {
value: group,
enumerable: false
});
}
Object.defineProperty(styles, "codes", {
value: codes,
enumerable: false
});
styles.color.close = "\x1B[39m";
styles.bgColor.close = "\x1B[49m";
styles.color.ansi = wrapAnsi16();
styles.color.ansi256 = wrapAnsi256();
styles.color.ansi16m = wrapAnsi16m();
styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
Object.defineProperties(styles, {
rgbToAnsi256: {
value(red, green, blue) {
if (red === green && green === blue) {
if (red < 8) {
return 16;
}
if (red > 248) {
return 231;
}
return Math.round((red - 8) / 247 * 24) + 232;
}
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
},
enumerable: false
},
hexToRgb: {
value(hex) {
const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
if (!matches) {
return [0, 0, 0];
}
let [colorString] = matches;
if (colorString.length === 3) {
colorString = [...colorString].map((character) => character + character).join("");
}
const integer = Number.parseInt(colorString, 16);
return [
/* eslint-disable no-bitwise */
integer >> 16 & 255,
integer >> 8 & 255,
integer & 255
/* eslint-enable no-bitwise */
];
},
enumerable: false
},
hexToAnsi256: {
value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
enumerable: false
},
ansi256ToAnsi: {
value(code) {
if (code < 8) {
return 30 + code;
}
if (code < 16) {
return 90 + (code - 8);
}
let red;
let green;
let blue;
if (code >= 232) {
red = ((code - 232) * 10 + 8) / 255;
green = red;
blue = red;
} else {
code -= 16;
const remainder = code % 36;
red = Math.floor(code / 36) / 5;
green = Math.floor(remainder / 6) / 5;
blue = remainder % 6 / 5;
}
const value = Math.max(red, green, blue) * 2;
if (value === 0) {
return 30;
}
let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
if (value === 2) {
result += 60;
}
return result;
},
enumerable: false
},
rgbToAnsi: {
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
enumerable: false
},
hexToAnsi: {
value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
enumerable: false
}
});
return styles;
}
var ansiStyles = assembleStyles();
var ansi_styles_default = ansiStyles;
// ../../node_modules/.pnpm/chalk@5.5.0/node_modules/chalk/source/vendor/supports-color/index.js
import process2 from "process";
import os from "os";
import tty from "tty";
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
const position = argv.indexOf(prefix + flag);
const terminatorPosition = argv.indexOf("--");
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
}
var { env } = process2;
var flagForceColor;
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
flagForceColor = 0;
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
flagForceColor = 1;
}
function envForceColor() {
if ("FORCE_COLOR" in env) {
if (env.FORCE_COLOR === "true") {
return 1;
}
if (env.FORCE_COLOR === "false") {
return 0;
}
return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
}
}
function translateLevel(level) {
if (level === 0) {
return false;
}
return {
level,
hasBasic: true,
has256: level >= 2,
has16m: level >= 3
};
}
function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
const noFlagForceColor = envForceColor();
if (noFlagForceColor !== void 0) {
flagForceColor = noFlagForceColor;
}
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
if (forceColor === 0) {
return 0;
}
if (sniffFlags) {
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
return 3;
}
if (hasFlag("color=256")) {
return 2;
}
}
if ("TF_BUILD" in env && "AGENT_NAME" in env) {
return 1;
}
if (haveStream && !streamIsTTY && forceColor === void 0) {
return 0;
}
const min = forceColor || 0;
if (env.TERM === "dumb") {
return min;
}
if (process2.platform === "win32") {
const osRelease = os.release().split(".");
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
return Number(osRelease[2]) >= 14931 ? 3 : 2;
}
return 1;
}
if ("CI" in env) {
if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
return 3;
}
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
return 1;
}
return min;
}
if ("TEAMCITY_VERSION" in env) {
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
}
if (env.COLORTERM === "truecolor") {
return 3;
}
if (env.TERM === "xterm-kitty") {
return 3;
}
if (env.TERM === "xterm-ghostty") {
return 3;
}
if ("TERM_PROGRAM" in env) {
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
switch (env.TERM_PROGRAM) {
case "iTerm.app": {
return version >= 3 ? 3 : 2;
}
case "Apple_Terminal": {
return 2;
}
}
}
if (/-256(color)?$/i.test(env.TERM)) {
return 2;
}
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
return 1;
}
if ("COLORTERM" in env) {
return 1;
}
return min;
}
function createSupportsColor(stream, options = {}) {
const level = _supportsColor(stream, {
streamIsTTY: stream && stream.isTTY,
...options
});
return translateLevel(level);
}
var supportsColor = {
stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
stderr: createSupportsColor({ isTTY: tty.isatty(2) })
};
var supports_color_default = supportsColor;
// ../../node_modules/.pnpm/chalk@5.5.0/node_modules/chalk/source/utilities.js
function stringReplaceAll(string, substring, replacer) {
let index = string.indexOf(substring);
if (index === -1) {
return string;
}
const substringLength = substring.length;
let endIndex = 0;
let returnValue = "";
do {
returnValue += string.slice(endIndex, index) + substring + replacer;
endIndex = index + substringLength;
index = string.indexOf(substring, endIndex);
} while (index !== -1);
returnValue += string.slice(endIndex);
return returnValue;
}
function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
let endIndex = 0;
let returnValue = "";
do {
const gotCR = string[index - 1] === "\r";
returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix;
endIndex = index + 1;
index = string.indexOf("\n", endIndex);
} while (index !== -1);
returnValue += string.slice(endIndex);
return returnValue;
}
// ../../node_modules/.pnpm/chalk@5.5.0/node_modules/chalk/source/index.js
var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
var GENERATOR = Symbol("GENERATOR");
var STYLER = Symbol("STYLER");
var IS_EMPTY = Symbol("IS_EMPTY");
var levelMapping = [
"ansi",
"ansi",
"ansi256",
"ansi16m"
];
var styles2 = /* @__PURE__ */ Object.create(null);
var applyOptions = (object, options = {}) => {
if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
throw new Error("The `level` option should be an integer from 0 to 3");
}
const colorLevel = stdoutColor ? stdoutColor.level : 0;
object.level = options.level === void 0 ? colorLevel : options.level;
};
var chalkFactory = (options) => {
const chalk2 = (...strings) => strings.join(" ");
applyOptions(chalk2, options);
Object.setPrototypeOf(chalk2, createChalk.prototype);
return chalk2;
};
function createChalk(options) {
return chalkFactory(options);
}
Object.setPrototypeOf(createChalk.prototype, Function.prototype);
for (const [styleName, style2] of Object.entries(ansi_styles_default)) {
styles2[styleName] = {
get() {
const builder = createBuilder(this, createStyler(style2.open, style2.close, this[STYLER]), this[IS_EMPTY]);
Object.defineProperty(this, styleName, { value: builder });
return builder;
}
};
}
styles2.visible = {
get() {
const builder = createBuilder(this, this[STYLER], true);
Object.defineProperty(this, "visible", { value: builder });
return builder;
}
};
var getModelAnsi = (model, level, type, ...arguments_) => {
if (model === "rgb") {
if (level === "ansi16m") {
return ansi_styles_default[type].ansi16m(...arguments_);
}
if (level === "ansi256") {
return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
}
return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
}
if (model === "hex") {
return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
}
return ansi_styles_default[type][model](...arguments_);
};
var usedModels = ["rgb", "hex", "ansi256"];
for (const model of usedModels) {
styles2[model] = {
get() {
const { level } = this;
return function(...arguments_) {
const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
return createBuilder(this, styler, this[IS_EMPTY]);
};
}
};
const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
styles2[bgModel] = {
get() {
const { level } = this;
return function(...arguments_) {
const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
return createBuilder(this, styler, this[IS_EMPTY]);
};
}
};
}
var proto = Object.defineProperties(() => {
}, {
...styles2,
level: {
enumerable: true,
get() {
return this[GENERATOR].level;
},
set(level) {
this[GENERATOR].level = level;
}
}
});
var createStyler = (open, close, parent) => {
let openAll;
let closeAll;
if (parent === void 0) {
openAll = open;
closeAll = close;
} else {
openAll = parent.openAll + open;
closeAll = close + parent.closeAll;
}
return {
open,
close,
openAll,
closeAll,
parent
};
};
var createBuilder = (self, _styler, _isEmpty) => {
const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
Object.setPrototypeOf(builder, proto);
builder[GENERATOR] = self;
builder[STYLER] = _styler;
builder[IS_EMPTY] = _isEmpty;
return builder;
};
var applyStyle = (self, string) => {
if (self.level <= 0 || !string) {
return self[IS_EMPTY] ? "" : string;
}
let styler = self[STYLER];
if (styler === void 0) {
return string;
}
const { openAll, closeAll } = styler;
if (string.includes("\x1B")) {
while (styler !== void 0) {
string = stringReplaceAll(string, styler.close, styler.open);
styler = styler.parent;
}
}
const lfIndex = string.indexOf("\n");
if (lfIndex !== -1) {
string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
}
return openAll + string + closeAll;
};
Object.defineProperties(createChalk.prototype, styles2);
var chalk = createChalk();
var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
var source_default = chalk;
// src/index.ts
import { program } from "commander";
import spawn from "cross-spawn";
import { detect as detectPackageManager } from "detect-package-manager";
import validateNpmPackageName from "validate-npm-package-name";
// src/utils/cli-styling.ts
var colors = {
primary: source_default.cyan,
secondary: source_default.blue,
success: source_default.green,
error: source_default.red,
warning: source_default.yellow,
info: source_default.blue,
muted: source_default.gray,
accent: source_default.magenta
};
var style = {
title: (text2) => colors.primary.bold(text2),
subtitle: (text2) => colors.secondary(text2),
heading: (text2) => colors.primary.bold(text2),
label: (text2) => colors.muted(text2),
value: (text2) => colors.primary(text2),
success: (text2) => colors.success(text2),
error: (text2) => colors.error(text2),
warning: (text2) => colors.warning(text2),
info: (text2) => colors.info(text2),
muted: (text2) => colors.muted(text2),
accent: (text2) => colors.accent(text2),
code: (text2) => source_default.cyan.italic(text2),
path: (text2) => source_default.dim.underline(text2),
command: (text2) => source_default.bgBlack.white.bold(` ${text2} `)
};
var featureStyle = {
nextjs: source_default.black,
typescript: source_default.blue,
tailwind: source_default.cyan,
trpc: source_default.magenta,
supabase: source_default.green,
ai: source_default.hex("#10b981"),
edge: source_default.yellow,
pwa: source_default.magenta,
i18n: source_default.blue,
testing: source_default.red,
streaming: source_default.cyan
};
var formatFeatures = (features) => {
return features.map((feature) => {
const colorFn = featureStyle[feature] || colors.muted;
return colorFn(`#${feature}`);
}).join(" ");
};
// src/utils/integration.ts
import { existsSync } from "fs";
import { join } from "path";
function createAT3Config(projectPath, config) {
const fullConfig = {
version: "0.1.0",
created: (/* @__PURE__ */ new Date()).toISOString(),
...config,
toolsUsed: [.../* @__PURE__ */ new Set(["create-at3-app", ...config.toolsUsed || []])]
};
try {
const configPath = join(projectPath, ".at3-config.json");
__require("fs").writeFileSync(configPath, JSON.stringify(fullConfig, null, 2));
} catch {
}
return fullConfig;
}
function suggestAT3Tools(template, features) {
const suggestions = [];
if (template !== "83-flavor" && template !== "suggested") {
suggestions.push(
`${colors.info("\u{1F4A1} Tip:")} Use ${style.command("at3-kit")} to upgrade existing projects to AT3 Stack`
);
}
if (!features.includes("testing")) {
suggestions.push(
`${colors.info("\u{1F4A1} Tip:")} Use ${style.command("@entro314-labs/at3t")} for advanced linting, testing, and development tools`
);
}
return suggestions;
}
function getWorkflowSuggestions(template) {
const workflows = [];
switch (template) {
case "t3":
workflows.push("Consider adding AI features with at3-kit for enhanced functionality");
workflows.push("Use at3t for advanced TypeScript and testing configurations");
break;
case "83-flavor":
workflows.push("Your project includes the full AT3 stack - ready for production!");
workflows.push("Use at3t migrate for advanced project maintenance");
break;
case "suggested":
workflows.push("Complete AT3 stack configured - explore all features");
workflows.push("Use at3-kit to fine-tune specific features as needed");
break;
default:
workflows.push("Explore at3-kit to add more AT3 features");
workflows.push("Use at3t for development workflow optimization");
}
return workflows;
}
// src/index.ts
var __filename2 = fileURLToPath(import.meta.url);
var __dirname2 = path.dirname(__filename2);
var TEMPLATES = {
t3: {
name: "T3 Base",
description: "Classic T3 stack: Next.js + TypeScript + Tailwind + tRPC",
features: ["nextjs", "typescript", "tailwind", "trpc"]
},
"t3-edge": {
name: "T3 + Edge",
description: "T3 stack + Supabase for edge-first deployment",
features: ["nextjs", "typescript", "tailwind", "supabase", "edge"]
},
"t3-ai-custom": {
name: "T3 + AI (Custom)",
description: "T3 + custom AI integration with multiple providers",
features: ["nextjs", "typescript", "tailwind", "custom-ai", "openai", "anthropic"]
},
"t3-ai-vercel": {
name: "T3 + AI (Vercel SDK)",
description: "T3 + Vercel AI SDK integration",
features: ["nextjs", "typescript", "tailwind", "vercel-ai", "streaming"]
},
"t3-ai-both": {
name: "T3 + AI (Both)",
description: "T3 + both custom AI and Vercel SDK integration",
features: ["nextjs", "typescript", "tailwind", "custom-ai", "vercel-ai", "streaming"]
},
suggested: {
name: "AT3 Suggested",
description: "Everything included: T3 + Supabase + AI + PWA + i18n + testing",
features: [
"nextjs",
"typescript",
"tailwind",
"supabase",
"custom-ai",
"vercel-ai",
"pwa",
"i18n",
"testing",
"edge"
]
},
"83-flavor": {
name: "83 Flavor",
description: "Signature stack: T3 + Supabase/Vercel Edge + Vercel AI SDK",
features: [
"nextjs",
"typescript",
"tailwind",
"supabase",
"vercel-edge",
"vercel-ai",
"streaming"
]
}
};
async function main() {
console.clear();
intro(source_default.bgCyan.black(" create-at3-app "));
const projectName = await text({
message: "What is your project named?",
placeholder: "my-at3-app",
validate(value) {
if (!value) return "Project name is required";
const validation = validateNpmPackageName(value);
if (!validation.validForNewPackages) {
return "Invalid project name. Use lowercase letters, numbers, and hyphens only.";
}
return;
}
});
if (isCancel(projectName)) {
cancel("Operation cancelled.");
process.exit(0);
}
const template = await select({
message: "Which template would you like to use?",
options: Object.entries(TEMPLATES).map(([key, template2]) => ({
value: key,
label: template2.name,
hint: template2.description
}))
});
if (isCancel(template)) {
cancel("Operation cancelled.");
process.exit(0);
}
let detectedPackageManager = "pnpm";
try {
detectedPackageManager = await detectPackageManager({ cwd: process.cwd() });
} catch {
if (existsSync2(path.join(process.cwd(), "pnpm-lock.yaml"))) detectedPackageManager = "pnpm";
else if (existsSync2(path.join(process.cwd(), "yarn.lock"))) detectedPackageManager = "yarn";
else if (existsSync2(path.join(process.cwd(), "package-lock.json")))
detectedPackageManager = "npm";
}
const packageManager = await select({
message: "Which package manager would you like to use?",
options: [
{ value: "pnpm", label: "pnpm", hint: "Recommended - fast and efficient" },
{ value: "npm", label: "npm", hint: "Default Node.js package manager" },
{ value: "yarn", label: "yarn", hint: "Popular alternative" }
],
initialValue: detectedPackageManager
});
if (isCancel(packageManager)) {
cancel("Operation cancelled.");
process.exit(0);
}
const installDeps = await confirm({
message: "Install dependencies?",
initialValue: true
});
if (isCancel(installDeps)) {
cancel("Operation cancelled.");
process.exit(0);
}
const setupSupabase = await confirm({
message: "Set up Supabase project?",
initialValue: true
});
if (isCancel(setupSupabase)) {
cancel("Operation cancelled.");
process.exit(0);
}
const skipGit = await confirm({
message: "Initialize Git repository?",
initialValue: true
});
if (isCancel(skipGit)) {
cancel("Operation cancelled.");
process.exit(0);
}
const projectDir = path.resolve(process.cwd(), projectName);
try {
await fs.access(projectDir);
const overwrite = await confirm({
message: `Directory ${projectName} already exists. Overwrite?`,
initialValue: false
});
if (isCancel(overwrite) || !overwrite) {
cancel("Operation cancelled.");
process.exit(0);
}
} catch {
}
await createApp({
projectName,
projectDir,
template,
packageManager,
installDeps,
setupSupabase,
skipGit: !skipGit
});
const selectedTemplate = TEMPLATES[template];
createAT3Config(projectDir, {
template,
features: [...selectedTemplate.features],
toolsUsed: ["create-at3-app"]
});
const toolSuggestions = suggestAT3Tools(template, [...selectedTemplate.features]);
const _workflows = getWorkflowSuggestions(template);
outro(source_default.green("\u{1F389} Your AT3 app is ready!"));
note(
`
${source_default.cyan("Next steps:")}
${source_default.dim("1.")} cd ${projectName}
${source_default.dim("2.")} Copy .env.example to .env.local and add your API keys
${source_default.dim("3.")} ${packageManager} dev
${source_default.cyan("Template features:")}
${formatFeatures([...selectedTemplate.features])}
${source_default.cyan("Documentation:")}
${source_default.dim("\u2022")} Getting Started: https://at3-stack.dev/docs/getting-started
${source_default.dim("\u2022")} AI Integration: https://at3-stack.dev/docs/ai-integration
${source_default.dim("\u2022")} Deployment: https://at3-stack.dev/docs/deployment
${toolSuggestions.length > 0 ? `${source_default.cyan("AT3 Ecosystem:")}
${toolSuggestions.map((s) => `${source_default.dim("\u2022")} ${s}`).join("\n ")}
` : ""}${source_default.cyan("Community:")}
${source_default.dim("\u2022")} GitHub: https://github.com/entro314-labs/at3-stack-kit
${source_default.dim("\u2022")} Discord: https://discord.gg/at3-stack
`,
"Welcome to AT3!"
);
}
async function createApp(params) {
const { projectName, projectDir, template, packageManager, installDeps, setupSupabase, skipGit } = params;
const s = spinner();
try {
s.start("Creating project structure...");
await copyTemplate(template, projectDir);
s.stop("Project structure created");
s.start("Updating package.json...");
await updatePackageJson(projectDir, projectName);
s.stop("package.json updated");
if (!skipGit) {
s.start("Initializing Git repository...");
await initGit(projectDir);
s.stop("Git repository initialized");
}
if (installDeps) {
s.start(`Installing dependencies with ${packageManager}...`);
await installDependencies(projectDir, packageManager);
s.stop("Dependencies installed");
}
if (setupSupabase) {
s.start("Setting up Supabase...");
await setupSupabaseProject(projectDir, packageManager);
s.stop("Supabase configured");
}
} catch (error) {
s.stop("Error occurred");
console.error(source_default.red("Error creating app:"), error);
process.exit(1);
}
}
async function copyTemplate(template, projectDir) {
const templateDir = path.resolve(__dirname2, `../templates/${template}`);
let sourceDir;
try {
await fs.access(templateDir);
sourceDir = templateDir;
} catch {
sourceDir = path.resolve(__dirname2, "../../../");
}
await fs.cp(sourceDir, projectDir, {
recursive: true,
filter: (src) => {
const basename = path.basename(src);
const skipList = [
"node_modules",
".git",
".next",
"dist",
"build",
".turbo",
".env.local",
"packages"
// Don't copy the packages directory
];
return !skipList.includes(basename);
}
});
const envExamplePath = path.join(projectDir, ".env.example");
try {
await fs.access(envExamplePath);
} catch {
const envContent = `# Supabase
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
# AI Providers (choose one or more)
OPENAI_API_KEY=your_openai_api_key
ANTHROPIC_API_KEY=your_anthropic_api_key
GOOGLE_AI_API_KEY=your_google_ai_api_key
# Optional: OpenRouter for additional models
OPENROUTER_API_KEY=your_openrouter_api_key
# Analytics (optional)
NEXT_PUBLIC_VERCEL_ANALYTICS=true
NEXT_PUBLIC_GA_ID=your_google_analytics_id
# Other
NEXTAUTH_SECRET=your_nextauth_secret
NEXTAUTH_URL=http://localhost:3000
`;
await fs.writeFile(envExamplePath, envContent);
}
}
async function updatePackageJson(projectDir, projectName) {
const packageJsonPath = path.join(projectDir, "package.json");
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
packageJson.name = projectName;
packageJson.version = "0.1.0";
packageJson.author = void 0;
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
}
function initGit(projectDir) {
return new Promise((resolve, reject) => {
const child = spawn("git", ["init"], {
cwd: projectDir,
stdio: "ignore"
});
child.on("close", (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Git init failed with code ${code}`));
}
});
});
}
function installDependencies(projectDir, packageManager) {
return new Promise((resolve, reject) => {
const child = spawn(packageManager, ["install"], {
cwd: projectDir,
stdio: "inherit"
});
child.on("close", (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Package installation failed with code ${code}`));
}
});
});
}
function setupSupabaseProject(projectDir, packageManager) {
return new Promise((resolve, _reject) => {
const child = spawn(packageManager, ["dlx", "supabase", "init"], {
cwd: projectDir,
stdio: "inherit"
});
child.on("close", (code) => {
if (code === 0) {
resolve();
} else {
console.warn(
source_default.yellow("Warning: Supabase setup failed. You can set it up manually later.")
);
resolve();
}
});
});
}
program.name("create-at3-app").description("Create AT3 (AIT3E) apps with a single command").version("0.1.0").argument("[project-name]", "Name of the project").option("-t, --template <template>", "Template to use", "minimal").option("--pm <package-manager>", "Package manager to use", "pnpm").option("--no-install", "Skip installing dependencies").option("--no-git", "Skip Git initialization").option("--no-supabase", "Skip Supabase setup").action(async (projectName, options) => {
if (projectName) {
const projectDir = path.resolve(process.cwd(), projectName);
await createApp({
projectName,
projectDir,
template: options.template,
packageManager: options.pm,
installDeps: options.install !== false,
setupSupabase: options.supabase !== false,
skipGit: options.git === false
});
console.log(source_default.green(`\u2705 Created ${projectName} successfully!`));
} else {
await main();
}
});
program.parse();
//# sourceMappingURL=index.js.map