@aex.is/zero 0.1.3 → 0.1.5
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/dist/cli/prompts.js +163 -0
- package/dist/config/frameworks.js +2 -0
- package/dist/config/modules.js +56 -5
- package/dist/engine/scaffold.js +98 -96
- package/dist/engine/templates.js +603 -39
- package/dist/index.js +2 -9
- package/package.json +4 -9
- package/dist/ui/App.js +0 -75
- package/dist/ui/components/SelectList.js +0 -67
- package/dist/ui/screens/Confirm.js +0 -22
- package/dist/ui/screens/DomainPrompt.js +0 -11
- package/dist/ui/screens/FrameworkSelect.js +0 -12
- package/dist/ui/screens/Intro.js +0 -18
- package/dist/ui/screens/ModuleSelect.js +0 -12
- package/dist/ui/screens/NamePrompt.js +0 -18
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { frameworks } from '../config/frameworks.js';
|
|
3
|
+
import { modules } from '../config/modules.js';
|
|
4
|
+
const introArt = [
|
|
5
|
+
' _____',
|
|
6
|
+
' / ___ \\\\',
|
|
7
|
+
' / / _ \\\\ \\\\',
|
|
8
|
+
' | |/ /| |',
|
|
9
|
+
' \\\\ \\\\_/ / /',
|
|
10
|
+
' \\\\___/_/'
|
|
11
|
+
].join('\n');
|
|
12
|
+
export async function runWizard() {
|
|
13
|
+
p.intro(`${introArt}\nAexis Zero`);
|
|
14
|
+
let directory = '.';
|
|
15
|
+
let appName = '';
|
|
16
|
+
let domain = '';
|
|
17
|
+
let framework = 'nextjs';
|
|
18
|
+
let selectedModules = [];
|
|
19
|
+
let step = 'directory';
|
|
20
|
+
while (true) {
|
|
21
|
+
if (step === 'directory') {
|
|
22
|
+
const value = await p.text({
|
|
23
|
+
message: 'Project directory',
|
|
24
|
+
placeholder: '.',
|
|
25
|
+
validate: (input) => {
|
|
26
|
+
if (typeof input !== 'string') {
|
|
27
|
+
return 'Enter a directory.';
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
if (isCancelled(value))
|
|
33
|
+
return null;
|
|
34
|
+
const normalized = String(value).trim();
|
|
35
|
+
directory = normalized.length === 0 ? '.' : normalized;
|
|
36
|
+
step = 'name';
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (step === 'name') {
|
|
40
|
+
const value = await p.text({
|
|
41
|
+
message: 'App name',
|
|
42
|
+
placeholder: 'my-app',
|
|
43
|
+
validate: (input) => {
|
|
44
|
+
if (typeof input !== 'string' || input.trim().length === 0) {
|
|
45
|
+
return 'App name is required.';
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
if (isCancelled(value))
|
|
51
|
+
return null;
|
|
52
|
+
appName = String(value).trim();
|
|
53
|
+
step = 'domain';
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (step === 'domain') {
|
|
57
|
+
const value = await p.text({
|
|
58
|
+
message: 'Domain (optional)',
|
|
59
|
+
placeholder: 'example.com'
|
|
60
|
+
});
|
|
61
|
+
if (isCancelled(value))
|
|
62
|
+
return null;
|
|
63
|
+
domain = String(value).trim();
|
|
64
|
+
step = 'framework';
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (step === 'framework') {
|
|
68
|
+
const value = await p.select({
|
|
69
|
+
message: 'Framework',
|
|
70
|
+
options: frameworks.map((item) => ({
|
|
71
|
+
value: item.id,
|
|
72
|
+
label: item.label,
|
|
73
|
+
hint: item.description
|
|
74
|
+
}))
|
|
75
|
+
});
|
|
76
|
+
if (isCancelled(value))
|
|
77
|
+
return null;
|
|
78
|
+
framework = value;
|
|
79
|
+
step = 'modules';
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (step === 'modules') {
|
|
83
|
+
const value = await p.multiselect({
|
|
84
|
+
message: 'Modules',
|
|
85
|
+
options: modules.map((item) => ({
|
|
86
|
+
value: item.id,
|
|
87
|
+
label: item.label,
|
|
88
|
+
hint: item.description
|
|
89
|
+
})),
|
|
90
|
+
required: false
|
|
91
|
+
});
|
|
92
|
+
if (isCancelled(value))
|
|
93
|
+
return null;
|
|
94
|
+
selectedModules = value;
|
|
95
|
+
step = 'confirm';
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (step === 'confirm') {
|
|
99
|
+
const frameworkLabel = frameworks.find((item) => item.id === framework)?.label ?? framework;
|
|
100
|
+
const moduleLabels = selectedModules
|
|
101
|
+
.map((id) => modules.find((item) => item.id === id)?.label ?? id)
|
|
102
|
+
.join(', ');
|
|
103
|
+
p.note([
|
|
104
|
+
`Directory: ${directory}`,
|
|
105
|
+
`App name: ${appName}`,
|
|
106
|
+
`Domain: ${domain || 'None'}`,
|
|
107
|
+
`Framework: ${frameworkLabel}`,
|
|
108
|
+
`Modules: ${moduleLabels || 'None'}`
|
|
109
|
+
].join('\n'), 'Review');
|
|
110
|
+
const action = await p.select({
|
|
111
|
+
message: 'Next step',
|
|
112
|
+
options: [
|
|
113
|
+
{ value: 'continue', label: 'Continue' },
|
|
114
|
+
{ value: 'edit-directory', label: 'Edit directory' },
|
|
115
|
+
{ value: 'edit-name', label: 'Edit name' },
|
|
116
|
+
{ value: 'edit-domain', label: 'Edit domain' },
|
|
117
|
+
{ value: 'edit-framework', label: 'Edit framework' },
|
|
118
|
+
{ value: 'edit-modules', label: 'Edit modules' },
|
|
119
|
+
{ value: 'cancel', label: 'Cancel' }
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
if (isCancelled(action))
|
|
123
|
+
return null;
|
|
124
|
+
switch (action) {
|
|
125
|
+
case 'continue':
|
|
126
|
+
return {
|
|
127
|
+
directory,
|
|
128
|
+
appName,
|
|
129
|
+
domain,
|
|
130
|
+
framework,
|
|
131
|
+
modules: selectedModules
|
|
132
|
+
};
|
|
133
|
+
case 'edit-directory':
|
|
134
|
+
step = 'directory';
|
|
135
|
+
continue;
|
|
136
|
+
case 'edit-name':
|
|
137
|
+
step = 'name';
|
|
138
|
+
continue;
|
|
139
|
+
case 'edit-domain':
|
|
140
|
+
step = 'domain';
|
|
141
|
+
continue;
|
|
142
|
+
case 'edit-framework':
|
|
143
|
+
step = 'framework';
|
|
144
|
+
continue;
|
|
145
|
+
case 'edit-modules':
|
|
146
|
+
step = 'modules';
|
|
147
|
+
continue;
|
|
148
|
+
case 'cancel':
|
|
149
|
+
p.cancel('Cancelled.');
|
|
150
|
+
return null;
|
|
151
|
+
default:
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function isCancelled(value) {
|
|
158
|
+
if (p.isCancel(value)) {
|
|
159
|
+
p.cancel('Cancelled.');
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
package/dist/config/modules.js
CHANGED
|
@@ -3,7 +3,13 @@ export const modules = [
|
|
|
3
3
|
id: 'neon',
|
|
4
4
|
label: 'Database (Neon)',
|
|
5
5
|
description: 'Serverless Postgres with Neon.',
|
|
6
|
-
envVars: [
|
|
6
|
+
envVars: [
|
|
7
|
+
{
|
|
8
|
+
key: 'DATABASE_URL',
|
|
9
|
+
description: 'Neon connection string',
|
|
10
|
+
url: 'https://neon.com/docs/get-started/connect-neon'
|
|
11
|
+
}
|
|
12
|
+
],
|
|
7
13
|
packages: {
|
|
8
14
|
nextjs: ['@neondatabase/serverless'],
|
|
9
15
|
expo: ['@neondatabase/serverless']
|
|
@@ -13,7 +19,18 @@ export const modules = [
|
|
|
13
19
|
id: 'clerk',
|
|
14
20
|
label: 'Auth (Clerk)',
|
|
15
21
|
description: 'Authentication with Clerk.',
|
|
16
|
-
envVars: [
|
|
22
|
+
envVars: [
|
|
23
|
+
{
|
|
24
|
+
key: 'CLERK_PUBLISHABLE_KEY',
|
|
25
|
+
description: 'Clerk publishable key',
|
|
26
|
+
url: 'https://dashboard.clerk.com'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: 'CLERK_SECRET_KEY',
|
|
30
|
+
description: 'Clerk secret key',
|
|
31
|
+
url: 'https://dashboard.clerk.com'
|
|
32
|
+
}
|
|
33
|
+
],
|
|
17
34
|
packages: {
|
|
18
35
|
nextjs: ['@clerk/nextjs'],
|
|
19
36
|
expo: ['@clerk/clerk-expo']
|
|
@@ -23,7 +40,18 @@ export const modules = [
|
|
|
23
40
|
id: 'payload',
|
|
24
41
|
label: 'CMS (Payload)',
|
|
25
42
|
description: 'Headless CMS using Payload.',
|
|
26
|
-
envVars: [
|
|
43
|
+
envVars: [
|
|
44
|
+
{
|
|
45
|
+
key: 'PAYLOAD_SECRET',
|
|
46
|
+
description: 'Payload secret (generate a long random string)',
|
|
47
|
+
url: 'https://payloadcms.com/docs'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
key: 'DATABASE_URL',
|
|
51
|
+
description: 'Database connection string',
|
|
52
|
+
url: 'https://payloadcms.com/docs'
|
|
53
|
+
}
|
|
54
|
+
],
|
|
27
55
|
packages: {
|
|
28
56
|
nextjs: ['payload'],
|
|
29
57
|
expo: ['payload']
|
|
@@ -33,7 +61,18 @@ export const modules = [
|
|
|
33
61
|
id: 'stripe',
|
|
34
62
|
label: 'Payments (Stripe)',
|
|
35
63
|
description: 'Payments via Stripe SDK.',
|
|
36
|
-
envVars: [
|
|
64
|
+
envVars: [
|
|
65
|
+
{
|
|
66
|
+
key: 'STRIPE_SECRET_KEY',
|
|
67
|
+
description: 'Stripe secret key',
|
|
68
|
+
url: 'https://dashboard.stripe.com/apikeys'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: 'STRIPE_WEBHOOK_SECRET',
|
|
72
|
+
description: 'Stripe webhook signing secret',
|
|
73
|
+
url: 'https://dashboard.stripe.com/webhooks'
|
|
74
|
+
}
|
|
75
|
+
],
|
|
37
76
|
packages: {
|
|
38
77
|
nextjs: ['stripe'],
|
|
39
78
|
expo: ['stripe']
|
|
@@ -62,8 +101,20 @@ export function getModuleEnvVars(moduleIds) {
|
|
|
62
101
|
for (const id of moduleIds) {
|
|
63
102
|
const module = getModuleDefinition(id);
|
|
64
103
|
for (const envVar of module.envVars) {
|
|
65
|
-
envVars.add(envVar);
|
|
104
|
+
envVars.add(envVar.key);
|
|
66
105
|
}
|
|
67
106
|
}
|
|
68
107
|
return Array.from(envVars).sort();
|
|
69
108
|
}
|
|
109
|
+
export function getModuleEnvHelp(moduleIds) {
|
|
110
|
+
const map = new Map();
|
|
111
|
+
for (const id of moduleIds) {
|
|
112
|
+
const module = getModuleDefinition(id);
|
|
113
|
+
for (const envVar of module.envVars) {
|
|
114
|
+
if (!map.has(envVar.key)) {
|
|
115
|
+
map.set(envVar.key, envVar);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return Array.from(map.values());
|
|
120
|
+
}
|
package/dist/engine/scaffold.js
CHANGED
|
@@ -2,25 +2,27 @@ import path from 'path';
|
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
3
|
import { execa } from 'execa';
|
|
4
4
|
import { getFrameworkDefinition } from '../config/frameworks.js';
|
|
5
|
+
import { getModuleEnvHelp } from '../config/modules.js';
|
|
5
6
|
import { installBaseDependencies, installModulePackages } from './installers.js';
|
|
6
7
|
import { writeEnvExample } from './env.js';
|
|
7
|
-
import {
|
|
8
|
+
import { buildNextTemplateFiles, buildExpoTemplateFiles, componentsJsonTemplate } from './templates.js';
|
|
8
9
|
const baseEnv = {
|
|
9
10
|
...process.env,
|
|
10
11
|
CI: '1'
|
|
11
12
|
};
|
|
12
13
|
export async function scaffoldProject(config) {
|
|
13
|
-
const
|
|
14
|
+
const directoryInput = config.directory.trim().length === 0 ? '.' : config.directory.trim();
|
|
15
|
+
const targetDir = path.resolve(process.cwd(), directoryInput);
|
|
14
16
|
await ensureEmptyTargetDir(targetDir);
|
|
15
17
|
const framework = getFrameworkDefinition(config.framework);
|
|
16
18
|
console.log(`Scaffolding ${framework.label}...`);
|
|
17
|
-
await runScaffoldCommand(framework.scaffold.command, framework.scaffold.packageName,
|
|
19
|
+
await runScaffoldCommand(framework.scaffold.command, framework.scaffold.packageName, directoryInput, framework.scaffold.argSets);
|
|
18
20
|
console.log('Applying framework templates...');
|
|
19
21
|
if (config.framework === 'nextjs') {
|
|
20
|
-
await applyNextTemplates(targetDir);
|
|
22
|
+
await applyNextTemplates(config, targetDir);
|
|
21
23
|
}
|
|
22
24
|
else {
|
|
23
|
-
await applyExpoTemplates(targetDir);
|
|
25
|
+
await applyExpoTemplates(config, targetDir);
|
|
24
26
|
}
|
|
25
27
|
console.log('Installing base dependencies with Bun...');
|
|
26
28
|
await installBaseDependencies(targetDir, framework.packages);
|
|
@@ -29,7 +31,7 @@ export async function scaffoldProject(config) {
|
|
|
29
31
|
console.log('Generating .env.example...');
|
|
30
32
|
await writeEnvExample(config.modules, targetDir);
|
|
31
33
|
console.log('Scaffold complete.');
|
|
32
|
-
const cdTarget =
|
|
34
|
+
const cdTarget = directoryInput === '.' ? '.' : directoryInput.includes(' ') ? `"${directoryInput}"` : directoryInput;
|
|
33
35
|
console.log(`\nNext steps:\n 1) cd ${cdTarget}\n 2) bun run dev`);
|
|
34
36
|
}
|
|
35
37
|
async function ensureEmptyTargetDir(targetDir) {
|
|
@@ -50,11 +52,12 @@ async function ensureEmptyTargetDir(targetDir) {
|
|
|
50
52
|
throw error;
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
|
-
async function runScaffoldCommand(command, packageName,
|
|
55
|
+
async function runScaffoldCommand(command, packageName, directoryInput, argSets) {
|
|
54
56
|
const errors = [];
|
|
57
|
+
const targetArg = directoryInput === '.' ? '.' : directoryInput;
|
|
55
58
|
for (const args of argSets) {
|
|
56
59
|
try {
|
|
57
|
-
await execa(command, [packageName,
|
|
60
|
+
await execa(command, [packageName, targetArg, ...args], {
|
|
58
61
|
stdio: 'inherit',
|
|
59
62
|
env: baseEnv,
|
|
60
63
|
shell: false
|
|
@@ -70,32 +73,45 @@ async function runScaffoldCommand(command, packageName, appName, argSets) {
|
|
|
70
73
|
: 'Scaffold failed for unknown reasons.';
|
|
71
74
|
throw new Error(message);
|
|
72
75
|
}
|
|
73
|
-
async function applyNextTemplates(targetDir) {
|
|
74
|
-
const rootAppDir = path.join(targetDir, 'app');
|
|
76
|
+
async function applyNextTemplates(config, targetDir) {
|
|
75
77
|
const srcAppDir = path.join(targetDir, 'src', 'app');
|
|
76
78
|
const usesSrcDir = await pathExists(srcAppDir);
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
const basePath = usesSrcDir ? 'src' : '';
|
|
80
|
+
const envHelp = getModuleEnvHelp(config.modules);
|
|
81
|
+
const files = buildNextTemplateFiles({
|
|
82
|
+
appName: config.appName,
|
|
83
|
+
domain: config.domain,
|
|
84
|
+
envVars: envHelp,
|
|
85
|
+
basePath
|
|
86
|
+
});
|
|
87
|
+
await writeTemplateFiles(targetDir, files);
|
|
88
|
+
const globalsPath = usesSrcDir ? 'src/app/globals.css' : 'app/globals.css';
|
|
89
|
+
const tailwindConfig = await detectTailwindConfig(targetDir);
|
|
90
|
+
const componentsJson = componentsJsonTemplate(globalsPath, tailwindConfig ?? 'tailwind.config.ts');
|
|
91
|
+
await fs.writeFile(path.join(targetDir, 'components.json'), componentsJson, 'utf8');
|
|
83
92
|
await ensureNextTurbo(targetDir);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
await ensurePackageName(targetDir, config.appName);
|
|
94
|
+
}
|
|
95
|
+
async function applyExpoTemplates(config, targetDir) {
|
|
96
|
+
const envHelp = getModuleEnvHelp(config.modules);
|
|
97
|
+
const files = buildExpoTemplateFiles({
|
|
98
|
+
appName: config.appName,
|
|
99
|
+
domain: config.domain,
|
|
100
|
+
envVars: envHelp,
|
|
101
|
+
basePath: ''
|
|
102
|
+
});
|
|
103
|
+
await writeTemplateFiles(targetDir, files);
|
|
104
|
+
await ensureExpoConfig(targetDir, config.appName);
|
|
105
|
+
await ensurePackageName(targetDir, config.appName);
|
|
106
|
+
}
|
|
107
|
+
async function ensureExpoConfig(targetDir, appName) {
|
|
94
108
|
const appJsonPath = path.join(targetDir, 'app.json');
|
|
95
109
|
const appJson = await readJson(appJsonPath, { expo: {} });
|
|
96
110
|
if (!appJson.expo || typeof appJson.expo !== 'object') {
|
|
97
111
|
appJson.expo = {};
|
|
98
112
|
}
|
|
113
|
+
appJson.expo.name = appName;
|
|
114
|
+
appJson.expo.slug = toSlug(appName);
|
|
99
115
|
if (!Array.isArray(appJson.expo.platforms)) {
|
|
100
116
|
appJson.expo.platforms = ['ios', 'android', 'macos', 'windows'];
|
|
101
117
|
}
|
|
@@ -134,6 +150,31 @@ async function ensureExpoConfig(targetDir) {
|
|
|
134
150
|
};
|
|
135
151
|
await writeJson(easPath, easConfig);
|
|
136
152
|
}
|
|
153
|
+
async function ensureNextTurbo(targetDir) {
|
|
154
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
155
|
+
const packageJson = await readJson(packageJsonPath, {});
|
|
156
|
+
if (!packageJson.scripts || typeof packageJson.scripts !== 'object') {
|
|
157
|
+
packageJson.scripts = {};
|
|
158
|
+
}
|
|
159
|
+
const currentDev = typeof packageJson.scripts.dev === 'string' ? packageJson.scripts.dev : 'next dev';
|
|
160
|
+
if (!currentDev.includes('--turbo')) {
|
|
161
|
+
packageJson.scripts.dev = `${currentDev} --turbo`;
|
|
162
|
+
}
|
|
163
|
+
await writeJson(packageJsonPath, packageJson);
|
|
164
|
+
}
|
|
165
|
+
async function ensurePackageName(targetDir, appName) {
|
|
166
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
167
|
+
const packageJson = await readJson(packageJsonPath, {});
|
|
168
|
+
packageJson.name = toPackageName(appName);
|
|
169
|
+
await writeJson(packageJsonPath, packageJson);
|
|
170
|
+
}
|
|
171
|
+
async function writeTemplateFiles(targetDir, files) {
|
|
172
|
+
for (const file of files) {
|
|
173
|
+
const fullPath = path.join(targetDir, ...file.path.split('/'));
|
|
174
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
175
|
+
await fs.writeFile(fullPath, file.content, 'utf8');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
137
178
|
function mergeUnique(values, additions) {
|
|
138
179
|
const set = new Set(values);
|
|
139
180
|
for (const value of additions) {
|
|
@@ -141,6 +182,21 @@ function mergeUnique(values, additions) {
|
|
|
141
182
|
}
|
|
142
183
|
return Array.from(set);
|
|
143
184
|
}
|
|
185
|
+
async function detectTailwindConfig(targetDir) {
|
|
186
|
+
const candidates = [
|
|
187
|
+
'tailwind.config.ts',
|
|
188
|
+
'tailwind.config.js',
|
|
189
|
+
'tailwind.config.cjs',
|
|
190
|
+
'tailwind.config.mjs'
|
|
191
|
+
];
|
|
192
|
+
for (const filename of candidates) {
|
|
193
|
+
const fullPath = path.join(targetDir, filename);
|
|
194
|
+
if (await pathExists(fullPath)) {
|
|
195
|
+
return filename;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
144
200
|
async function readJson(filePath, fallback) {
|
|
145
201
|
try {
|
|
146
202
|
const data = await fs.readFile(filePath, 'utf8');
|
|
@@ -178,74 +234,20 @@ async function pathExists(targetPath) {
|
|
|
178
234
|
throw error;
|
|
179
235
|
}
|
|
180
236
|
}
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const globalsPath = usesSrcDir ? 'src/app/globals.css' : 'app/globals.css';
|
|
198
|
-
const tailwindConfigPath = await detectTailwindConfig(targetDir);
|
|
199
|
-
const componentsJson = componentsJsonTemplate(globalsPath, tailwindConfigPath ?? 'tailwind.config.ts');
|
|
200
|
-
await fs.writeFile(path.join(targetDir, 'components.json'), componentsJson, 'utf8');
|
|
201
|
-
}
|
|
202
|
-
async function detectTailwindConfig(targetDir) {
|
|
203
|
-
const candidates = [
|
|
204
|
-
'tailwind.config.ts',
|
|
205
|
-
'tailwind.config.js',
|
|
206
|
-
'tailwind.config.cjs',
|
|
207
|
-
'tailwind.config.mjs'
|
|
208
|
-
];
|
|
209
|
-
for (const filename of candidates) {
|
|
210
|
-
const fullPath = path.join(targetDir, filename);
|
|
211
|
-
if (await pathExists(fullPath)) {
|
|
212
|
-
return filename;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
async function ensureExpoTamagui(targetDir) {
|
|
218
|
-
const configPath = path.join(targetDir, 'tamagui.config.ts');
|
|
219
|
-
await fs.writeFile(configPath, tamaguiConfigTemplate, 'utf8');
|
|
220
|
-
const metroPath = path.join(targetDir, 'metro.config.js');
|
|
221
|
-
await fs.writeFile(metroPath, metroConfigTemplate, 'utf8');
|
|
222
|
-
await ensureBabelTamagui(targetDir);
|
|
223
|
-
}
|
|
224
|
-
async function ensureBabelTamagui(targetDir) {
|
|
225
|
-
const babelPath = path.join(targetDir, 'babel.config.js');
|
|
226
|
-
let content = '';
|
|
227
|
-
if (await pathExists(babelPath)) {
|
|
228
|
-
content = await fs.readFile(babelPath, 'utf8');
|
|
229
|
-
}
|
|
230
|
-
if (!content) {
|
|
231
|
-
const defaultConfig = `module.exports = function (api) {\n api.cache(true);\n return {\n presets: ['babel-preset-expo'],\n plugins: [\n 'expo-router/babel',\n [\n '@tamagui/babel-plugin',\n {\n config: './tamagui.config.ts',\n components: ['tamagui']\n }\n ]\n ]\n };\n};\n`;
|
|
232
|
-
await fs.writeFile(babelPath, defaultConfig, 'utf8');
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
if (content.includes('@tamagui/babel-plugin')) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
const pluginSnippet = `[\n '@tamagui/babel-plugin',\n {\n config: './tamagui.config.ts',\n components: ['tamagui']\n }\n ]`;
|
|
239
|
-
const pluginsRegex = /plugins:\\s*\\[(.*)\\]/s;
|
|
240
|
-
if (pluginsRegex.test(content)) {
|
|
241
|
-
content = content.replace(pluginsRegex, (match, inner) => {
|
|
242
|
-
const trimmed = inner.trim();
|
|
243
|
-
const updatedInner = trimmed.length > 0 ? `${trimmed},\n ${pluginSnippet}` : pluginSnippet;
|
|
244
|
-
return `plugins: [${updatedInner}]`;
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
content = content.replace(/return\\s*\\{/, (match) => `${match}\n plugins: [${pluginSnippet}],`);
|
|
249
|
-
}
|
|
250
|
-
await fs.writeFile(babelPath, content, 'utf8');
|
|
237
|
+
function toPackageName(name) {
|
|
238
|
+
const cleaned = name
|
|
239
|
+
.trim()
|
|
240
|
+
.toLowerCase()
|
|
241
|
+
.replace(/[^a-z0-9-._]/g, '-')
|
|
242
|
+
.replace(/-+/g, '-')
|
|
243
|
+
.replace(/^[-_.]+|[-_.]+$/g, '');
|
|
244
|
+
return cleaned || 'aexis-zero-app';
|
|
245
|
+
}
|
|
246
|
+
function toSlug(name) {
|
|
247
|
+
return name
|
|
248
|
+
.trim()
|
|
249
|
+
.toLowerCase()
|
|
250
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
251
|
+
.replace(/-+/g, '-')
|
|
252
|
+
.replace(/^-|-$/g, '') || 'aexis-zero-app';
|
|
251
253
|
}
|