a3-stack-cli 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "a3-stack-cli",
3
- "version": "0.1.4",
4
- "description": "A3 Stack CLI - Create modern full-stack apps with SvelteKit, Better Auth, and Kysely",
3
+ "version": "0.1.6",
4
+ "description": "A3 Stack CLI - Create modern full-stack apps with SvelteKit, Better Auth, Convex, and Kysely",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "a3": "./src/index.ts"
@@ -36,6 +36,7 @@
36
36
  "a3-stack",
37
37
  "sveltekit",
38
38
  "better-auth",
39
+ "convex",
39
40
  "kysely",
40
41
  "cli",
41
42
  "scaffold"
@@ -2,11 +2,11 @@ import { defineCommand } from 'citty';
2
2
  import consola from 'consola';
3
3
  import prompts from 'prompts';
4
4
  import pc from 'picocolors';
5
- import { downloadTemplate, listTemplates, processTemplate } from '../lib/template';
5
+ import { downloadTemplate, getTemplateInfo, listTemplates, processTemplate } from '../lib/template';
6
6
  import { initGit, isGitInstalled } from '../lib/git';
7
7
  import { installDependencies, runPostInstall } from '../lib/setup';
8
8
  import { resolve } from 'path';
9
- import { existsSync } from 'fs';
9
+ import { existsSync, mkdirSync, statSync } from 'fs';
10
10
 
11
11
  export const createCommand = defineCommand({
12
12
  meta: {
@@ -22,7 +22,12 @@ export const createCommand = defineCommand({
22
22
  template: {
23
23
  type: 'string',
24
24
  alias: 't',
25
- description: 'Template to use (e.g., kysely)',
25
+ description: 'Template to use (e.g., convex)',
26
+ },
27
+ path: {
28
+ type: 'string',
29
+ alias: 'p',
30
+ description: 'Directory where the project folder should be created',
26
31
  },
27
32
  git: {
28
33
  type: 'boolean',
@@ -49,15 +54,8 @@ export const createCommand = defineCommand({
49
54
 
50
55
  // Get available templates
51
56
  const templates = await listTemplates();
52
-
53
- // Check if target directory already exists (if name provided)
54
- if (args.name) {
55
- const targetDir = resolve(process.cwd(), args.name);
56
- if (existsSync(targetDir)) {
57
- consola.error(`Directory ${pc.cyan(args.name)} already exists`);
58
- return;
59
- }
60
- }
57
+ const invocationCwd = process.env.INIT_CWD ? resolve(process.env.INIT_CWD) : process.cwd();
58
+ const resolveFromInvocation = (inputPath: string) => resolve(invocationCwd, inputPath);
61
59
 
62
60
  // Interactive prompts
63
61
  const response = await prompts(
@@ -72,9 +70,19 @@ export const createCommand = defineCommand({
72
70
  if (!/^[a-z0-9-_]+$/i.test(value)) {
73
71
  return 'Project name can only contain letters, numbers, dashes, and underscores';
74
72
  }
75
- const targetDir = resolve(process.cwd(), value);
76
- if (existsSync(targetDir)) {
77
- return `Directory ${value} already exists`;
73
+ return true;
74
+ },
75
+ },
76
+ {
77
+ type: args.path ? null : 'text',
78
+ name: 'path',
79
+ message: 'Project location:',
80
+ initial: invocationCwd,
81
+ validate: (value: string) => {
82
+ if (!value) return 'Project location is required';
83
+ const baseDir = resolveFromInvocation(value);
84
+ if (existsSync(baseDir) && !statSync(baseDir).isDirectory()) {
85
+ return `${baseDir} exists and is not a directory`;
78
86
  }
79
87
  return true;
80
88
  },
@@ -112,15 +120,31 @@ export const createCommand = defineCommand({
112
120
 
113
121
  const projectName = args.name || response.name;
114
122
  const template = args.template || response.template;
123
+ const projectPathInput = args.path || response.path || '.';
115
124
  const shouldInitGit = args.git !== false && response.git !== false;
116
125
  const shouldInstall = args.install !== false && response.install !== false;
126
+ const templateInfo = await getTemplateInfo(template || '');
117
127
 
118
- if (!projectName || !template) {
128
+ if (!projectName || !template || !projectPathInput) {
119
129
  consola.error('Missing required options');
120
130
  return;
121
131
  }
122
132
 
123
- const targetDir = resolve(process.cwd(), projectName);
133
+ const baseDir = resolveFromInvocation(projectPathInput);
134
+ if (existsSync(baseDir) && !statSync(baseDir).isDirectory()) {
135
+ consola.error(`${pc.cyan(baseDir)} exists and is not a directory`);
136
+ return;
137
+ }
138
+
139
+ if (!existsSync(baseDir)) {
140
+ mkdirSync(baseDir, { recursive: true });
141
+ }
142
+
143
+ const targetDir = resolve(baseDir, projectName);
144
+ if (existsSync(targetDir)) {
145
+ consola.error(`Directory ${pc.cyan(targetDir)} already exists`);
146
+ return;
147
+ }
124
148
 
125
149
  console.log('');
126
150
  consola.start(`Creating ${pc.cyan(projectName)} with ${pc.green(template)} template...`);
@@ -134,6 +158,17 @@ export const createCommand = defineCommand({
134
158
  return;
135
159
  }
136
160
 
161
+ const packageJsonPath = resolve(targetDir, 'package.json');
162
+ if (!existsSync(packageJsonPath)) {
163
+ consola.error(
164
+ `Downloaded template is invalid (missing package.json at ${pc.cyan(packageJsonPath)}).`
165
+ );
166
+ consola.info(
167
+ 'This usually means the selected template path is empty or not available in the remote repository.'
168
+ );
169
+ return;
170
+ }
171
+
137
172
  // Process template (replace placeholders)
138
173
  try {
139
174
  await processTemplate(targetDir, { projectName });
@@ -163,6 +198,7 @@ export const createCommand = defineCommand({
163
198
  try {
164
199
  await installDependencies(targetDir);
165
200
  consola.success('Installed dependencies');
201
+ await runPostInstall(targetDir, templateInfo?.postInstall);
166
202
  } catch (error) {
167
203
  consola.warn(`Failed to install dependencies: ${error}`);
168
204
  consola.info('You can install them manually with: bun install');
@@ -182,13 +218,22 @@ export const createCommand = defineCommand({
182
218
  console.log('');
183
219
  consola.info(`${pc.bold('Next steps:')}`);
184
220
  console.log('');
185
- console.log(` ${pc.cyan('cd')} ${projectName}`);
221
+ console.log(` ${pc.cyan('cd')} ${targetDir}`);
186
222
 
187
223
  if (!shouldInstall) {
188
224
  console.log(` ${pc.cyan('bun install')}`);
225
+ if (templateInfo?.postInstall) {
226
+ console.log(` ${pc.cyan(templateInfo.postInstall)} ${pc.dim('# Configure environment')}`);
227
+ }
228
+ } else if (template === 'convex') {
229
+ console.log(
230
+ ` ${pc.dim('# Setup script ran. Next, initialize Convex in another terminal:')}`
231
+ );
189
232
  }
190
233
 
191
- console.log(` ${pc.cyan('bun run scripts/setup-project.ts')} ${pc.dim('# Configure environment')}`);
234
+ if (template === 'convex') {
235
+ console.log(` ${pc.cyan('bun run convex:dev')}`);
236
+ }
192
237
  console.log(` ${pc.cyan('bun run dev')}`);
193
238
  console.log('');
194
239
  consola.info(`Visit ${pc.cyan('http://localhost:5173')} to see your app`);
@@ -26,7 +26,7 @@ export const templatesCommand = defineCommand({
26
26
  console.log('');
27
27
  }
28
28
 
29
- consola.info(`Create a project with: ${pc.cyan('a3 create my-app --template kysely')}`);
29
+ consola.info(`Create a project with: ${pc.cyan('a3 create my-app --template convex')}`);
30
30
  console.log('');
31
31
  },
32
32
  });
package/src/index.ts CHANGED
@@ -7,7 +7,7 @@ const main = defineCommand({
7
7
  meta: {
8
8
  name: 'a3',
9
9
  version: '0.1.1',
10
- description: 'A3 Stack CLI - Create modern full-stack apps with SvelteKit, Better Auth, and Kysely',
10
+ description: 'A3 Stack CLI - Create modern full-stack apps with SvelteKit, Better Auth, and Convex',
11
11
  },
12
12
  subCommands: {
13
13
  create: createCommand,
@@ -25,6 +25,22 @@ const GITHUB_REPO = 'AdamAugustinsky/a3-stack';
25
25
  */
26
26
  export async function listTemplates(): Promise<Template[]> {
27
27
  return [
28
+ {
29
+ name: 'convex',
30
+ displayName: 'A3 Stack + Convex',
31
+ description: 'SvelteKit 5 + Better Auth + Convex',
32
+ features: [
33
+ 'Real-time backend with Convex',
34
+ 'Better Auth authentication',
35
+ 'Organization/multi-tenant support',
36
+ 'shadcn-svelte components',
37
+ 'TailwindCSS v4',
38
+ 'Remote functions (experimental)',
39
+ ],
40
+ icon: 'database',
41
+ version: '1.0.0',
42
+ postInstall: 'bun run scripts/setup-project.ts',
43
+ },
28
44
  {
29
45
  name: 'kysely',
30
46
  displayName: 'A3 Stack + Kysely',
@@ -86,12 +102,13 @@ export async function processTemplate(
86
102
  // template.json might not exist, that's fine
87
103
  }
88
104
 
89
- // Remove .claude directory if it exists (AI assistant specific configs)
105
+ // Remove AI assistant specific config directories if they exist
90
106
  try {
91
107
  const { rm } = await import('fs/promises');
92
108
  await rm(join(dir, '.claude'), { recursive: true, force: true });
109
+ await rm(join(dir, '.agents'), { recursive: true, force: true });
93
110
  } catch {
94
- // .claude might not exist, that's fine
111
+ // AI config directories might not exist, that's fine
95
112
  }
96
113
 
97
114
  // Remove CLAUDE.md, AGENTS.md if they exist