a3-stack-cli 0.1.5 → 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 +3 -2
- package/src/commands/create.ts +64 -19
- package/src/commands/templates.ts +1 -1
- package/src/index.ts +1 -1
- package/src/lib/template.ts +19 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "a3-stack-cli",
|
|
3
|
-
"version": "0.1.
|
|
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"
|
package/src/commands/create.ts
CHANGED
|
@@ -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.,
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
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')} ${
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
package/src/lib/template.ts
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
111
|
+
// AI config directories might not exist, that's fine
|
|
95
112
|
}
|
|
96
113
|
|
|
97
114
|
// Remove CLAUDE.md, AGENTS.md if they exist
|