newcandies 0.1.13 → 0.1.14
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/index.js +117 -395
- package/package.json +1 -1
- package/templates/_base/app.json +40 -0
- package/templates/_base/babel.config.js +11 -0
- package/templates/_base/eas.json +10 -0
- package/templates/_base/expo-env.d.ts +3 -0
- package/templates/_base/global.css +18 -0
- package/templates/_base/metro.config.js +10 -0
- package/templates/_base/package.json +44 -0
- package/templates/_base/src/app/(tabs)/_layout.tsx +28 -0
- package/templates/_base/src/app/(tabs)/index.tsx +10 -0
- package/templates/_base/src/app/(tabs)/two.tsx +10 -0
- package/templates/_base/src/app/_layout.tsx +23 -0
- package/templates/_base/src/app/modal.tsx +10 -0
- package/templates/_base/src/components/core/text.tsx +42 -0
- package/templates/_base/tsconfig.json +17 -0
- package/templates/app-briefs/react-query/sproutsy/README.md +4 -0
- package/templates/app-briefs/react-query/sproutsy/assets/fonts/SpaceMono-Regular.ttf +0 -0
- package/templates/app-briefs/react-query/sproutsy/assets/images/adaptive-icon.png +0 -0
- package/templates/app-briefs/react-query/sproutsy/assets/images/favicon.png +0 -0
- package/templates/app-briefs/react-query/sproutsy/assets/images/icon.png +0 -0
- package/templates/app-briefs/react-query/sproutsy/assets/images/splash-icon.png +0 -0
- package/templates/app-briefs/react-query/sproutsy/assets/images/sproutsies-logo.png +0 -0
- package/templates/app-briefs/react-query/sproutsy/assets/images/sproutsy-hallmark.png +0 -0
- package/templates/app-briefs/react-query/sproutsy/assets/images/sproutsy-logo.png +0 -0
- package/templates/app-briefs/react-query/sproutsy/assets/images/sproutsy-logo.svg +1 -0
- package/templates/app-briefs/react-query/sproutsy/assets/images/sproutsy-main-logo.png +0 -0
- package/templates/app-briefs/react-query/sproutsy/babel.config.js +7 -0
- package/templates/app-briefs/react-query/sproutsy/env +3 -0
- package/templates/app-briefs/react-query/sproutsy/expo-env.d.ts +3 -0
- package/templates/app-briefs/react-query/sproutsy/global.css +29 -0
- package/templates/app-briefs/react-query/sproutsy/metro.config.js +14 -0
- package/templates/app-briefs/react-query/sproutsy/package.json +55 -0
- package/templates/app-briefs/react-query/sproutsy/src/lib/constants/index.ts +253 -0
- package/templates/app-briefs/react-query/sproutsy/src/types/plant.d.ts +50 -0
- package/templates/app-briefs/react-query/sproutsy/tsconfig.json +17 -0
- package/templates/boilerplate/holidia/babel.config.js +13 -0
- package/templates/boilerplate/holidia/global.css +4 -0
- package/templates/boilerplate/holidia/metro.config.js +7 -0
- package/templates/boilerplate/holidia/package.json +33 -0
- package/templates/boilerplate/holidia/src/app/_layout.tsx +16 -0
- package/templates/boilerplate/holidia/src/app/index.tsx +34 -0
- package/templates/boilerplate/holidia/tailwind.config.js +15 -0
- package/templates/boilerplate/supersimplenotes/babel.config.js +13 -0
- package/templates/boilerplate/supersimplenotes/global.css +4 -0
- package/templates/boilerplate/supersimplenotes/metro.config.js +7 -0
- package/templates/boilerplate/supersimplenotes/package.json +32 -0
- package/templates/boilerplate/supersimplenotes/src/app/_layout.tsx +11 -0
- package/templates/boilerplate/supersimplenotes/src/app/index.tsx +28 -0
- package/templates/boilerplate/supersimplenotes/tailwind.config.js +15 -0
- package/templates/registry.json +54 -0
- package/templates/default/README.md +0 -1
- /package/templates/{default → _base}/assets/fonts/SpaceMono-Regular.ttf +0 -0
- /package/templates/{default → _base}/assets/images/adaptive-icon.png +0 -0
- /package/templates/{default → _base}/assets/images/favicon.png +0 -0
- /package/templates/{default → _base}/assets/images/icon.png +0 -0
- /package/templates/{default → _base}/assets/images/splash-icon.png +0 -0
- /package/templates/boilerplate/holidia/{app → src/app}/holi.tsx +0 -0
- /package/templates/boilerplate/supersimplenotes/{app → src/app}/notes.tsx +0 -0
package/dist/index.js
CHANGED
|
@@ -8,38 +8,23 @@ import fs from "fs-extra";
|
|
|
8
8
|
import path from "path";
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
10
|
import { execa } from "execa";
|
|
11
|
-
var
|
|
12
|
-
|
|
13
|
-
extraDeps: ["@tanstack/react-query", "@react-native-async-storage/async-storage"]
|
|
14
|
-
},
|
|
15
|
-
"boilerplate/supersimplenotes": {
|
|
16
|
-
extraDeps: ["@react-native-community/netinfo"]
|
|
17
|
-
},
|
|
18
|
-
"app-brief/react-query/sproutsy": {
|
|
19
|
-
extraDeps: ["@tanstack/react-query"]
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
var TEMPLATES = {
|
|
23
|
-
"boilerplate": [
|
|
24
|
-
{ value: "holidia", label: "holidia" },
|
|
25
|
-
{ value: "supersimplenotes", label: "supersimplenotes" }
|
|
26
|
-
],
|
|
27
|
-
"mini-boilerplate": [
|
|
28
|
-
{ value: "auth-mini", label: "Auth Mini" }
|
|
29
|
-
],
|
|
30
|
-
"app-brief": {
|
|
31
|
-
categories: [
|
|
32
|
-
{ value: "react-query", label: "react-query", variants: [{ value: "sproutsy", label: "sproutsy" }] }
|
|
33
|
-
]
|
|
34
|
-
},
|
|
35
|
-
"mini-app-brief": { categories: [{ value: "forms", label: "forms", variants: [{ value: "lite", label: "lite" }] }] },
|
|
36
|
-
"default": [{ value: "default", label: "Default (Expo Router + Uniwind)" }]
|
|
37
|
-
};
|
|
11
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
var REGISTRY_PATH = path.join(__dirname, "..", "templates", "registry.json");
|
|
38
13
|
var program = new Command().name("newcandies").option("-t, --type <type>").option("--template <name>").option("--category <name>").option("--variant <name>").option("-y, --yes").option("--pm <pm>", "npm|pnpm|yarn|bun").option("--no-install").option("--no-prebuild").parse(process.argv);
|
|
39
14
|
var opts = program.opts();
|
|
15
|
+
function deepMerge(target, source) {
|
|
16
|
+
for (const key in source) {
|
|
17
|
+
if (source[key] instanceof Object && key in target) {
|
|
18
|
+
Object.assign(source[key], deepMerge(target[key], source[key]));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
Object.assign(target || {}, source);
|
|
22
|
+
return target;
|
|
23
|
+
}
|
|
40
24
|
async function main() {
|
|
41
25
|
console.clear();
|
|
42
26
|
p.intro(pc.bgMagenta(pc.black(" newcandies ")));
|
|
27
|
+
const registry = await fs.readJSON(REGISTRY_PATH);
|
|
43
28
|
const project = await p.group({
|
|
44
29
|
name: () => p.text({
|
|
45
30
|
message: "Project name?",
|
|
@@ -51,32 +36,44 @@ async function main() {
|
|
|
51
36
|
return p.select({
|
|
52
37
|
message: "Pick type",
|
|
53
38
|
options: [
|
|
54
|
-
{ value: "
|
|
55
|
-
{ value: "mini-
|
|
56
|
-
{ value: "app-
|
|
57
|
-
{ value: "mini-app-
|
|
58
|
-
{ value: "default", label: "Default" }
|
|
39
|
+
{ value: "boilerplates", label: "Boilerplate (Full Projects)" },
|
|
40
|
+
{ value: "mini-boilerplates", label: "Mini-boilerplate" },
|
|
41
|
+
{ value: "app-briefs", label: "App-brief (2 levels)" },
|
|
42
|
+
{ value: "mini-app-briefs", label: "Mini-app-brief (2 levels)" },
|
|
43
|
+
{ value: "default", label: "Default (Base)" }
|
|
59
44
|
]
|
|
60
45
|
});
|
|
61
46
|
},
|
|
62
47
|
template: async ({ results }) => {
|
|
63
|
-
|
|
48
|
+
const type = results.type;
|
|
49
|
+
if (type === "default" || type === "app-briefs" || type === "mini-app-briefs") return null;
|
|
64
50
|
if (opts.template) return opts.template;
|
|
65
|
-
const
|
|
66
|
-
return p.select({
|
|
51
|
+
const items = registry[type];
|
|
52
|
+
return p.select({
|
|
53
|
+
message: "Pick template",
|
|
54
|
+
options: items.map((i) => ({ value: i.name, label: `${i.name} - ${i.description}` }))
|
|
55
|
+
});
|
|
67
56
|
},
|
|
68
57
|
category: async ({ results }) => {
|
|
69
|
-
|
|
58
|
+
const type = results.type;
|
|
59
|
+
if (type !== "app-briefs" && type !== "mini-app-briefs") return null;
|
|
70
60
|
if (opts.category) return opts.category;
|
|
71
|
-
const cats =
|
|
72
|
-
return p.select({
|
|
61
|
+
const cats = registry[type];
|
|
62
|
+
return p.select({
|
|
63
|
+
message: "Pick category",
|
|
64
|
+
options: cats.map((c) => ({ value: c.category, label: c.category }))
|
|
65
|
+
});
|
|
73
66
|
},
|
|
74
67
|
variant: async ({ results }) => {
|
|
75
|
-
|
|
68
|
+
const type = results.type;
|
|
69
|
+
if (type !== "app-briefs" && type !== "mini-app-briefs") return null;
|
|
76
70
|
if (opts.variant) return opts.variant;
|
|
77
|
-
const cats =
|
|
78
|
-
const cat = cats.find((c) => c.
|
|
79
|
-
return p.select({
|
|
71
|
+
const cats = registry[type];
|
|
72
|
+
const cat = cats.find((c) => c.category === results.category);
|
|
73
|
+
return p.select({
|
|
74
|
+
message: "Pick variant",
|
|
75
|
+
options: cat.variants.map((v) => ({ value: v.name, label: `${v.name} - ${v.description}` }))
|
|
76
|
+
});
|
|
80
77
|
},
|
|
81
78
|
install: async () => {
|
|
82
79
|
if (opts.yes !== void 0) return !!opts.yes;
|
|
@@ -103,22 +100,92 @@ async function main() {
|
|
|
103
100
|
const dest = path.resolve(process.cwd(), project.name);
|
|
104
101
|
const s = p.spinner();
|
|
105
102
|
s.start("Scaffolding");
|
|
103
|
+
let selectedItem = null;
|
|
104
|
+
if (project.type === "default") {
|
|
105
|
+
selectedItem = { name: "default", path: "", description: "Default", strategy: "base" };
|
|
106
|
+
} else if (project.type === "boilerplates" || project.type === "mini-boilerplates") {
|
|
107
|
+
const list = registry[project.type];
|
|
108
|
+
selectedItem = list.find((i) => i.name === project.template) || null;
|
|
109
|
+
} else {
|
|
110
|
+
const type = project.type;
|
|
111
|
+
const cat = registry[type].find((c) => c.category === project.category);
|
|
112
|
+
selectedItem = cat?.variants.find((v) => v.name === project.variant) || null;
|
|
113
|
+
}
|
|
114
|
+
if (!selectedItem) {
|
|
115
|
+
s.stop("Error: Template not found");
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
106
118
|
await fs.ensureDir(dest);
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
const strategy = selectedItem.strategy || "base";
|
|
120
|
+
if (strategy === "base") {
|
|
121
|
+
const basePath = path.join(__dirname, "..", "templates", "_base");
|
|
122
|
+
await fs.copy(basePath, dest);
|
|
123
|
+
}
|
|
124
|
+
let basePkg = null;
|
|
125
|
+
let baseApp = null;
|
|
126
|
+
if (strategy === "base") {
|
|
127
|
+
const pkgPath2 = path.join(dest, "package.json");
|
|
128
|
+
const appPath = path.join(dest, "app.json");
|
|
129
|
+
if (await fs.pathExists(pkgPath2)) basePkg = await fs.readJSON(pkgPath2);
|
|
130
|
+
if (await fs.pathExists(appPath)) baseApp = await fs.readJSON(appPath);
|
|
131
|
+
}
|
|
132
|
+
if (selectedItem.path) {
|
|
133
|
+
const templatePath = path.join(__dirname, "..", "templates", selectedItem.path);
|
|
134
|
+
if (await fs.pathExists(templatePath)) {
|
|
135
|
+
await fs.copy(templatePath, dest, { overwrite: true });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (strategy === "base" && basePkg) {
|
|
139
|
+
const templatePkgPath = path.join(__dirname, "..", "templates", selectedItem.path, "package.json");
|
|
140
|
+
const templateAppPath = path.join(__dirname, "..", "templates", selectedItem.path, "app.json");
|
|
141
|
+
if (await fs.pathExists(templatePkgPath)) {
|
|
142
|
+
const tmplPkg = await fs.readJSON(templatePkgPath);
|
|
143
|
+
basePkg.dependencies = { ...basePkg.dependencies, ...tmplPkg.dependencies };
|
|
144
|
+
basePkg.devDependencies = { ...basePkg.devDependencies, ...tmplPkg.devDependencies };
|
|
145
|
+
basePkg.scripts = { ...basePkg.scripts, ...tmplPkg.scripts };
|
|
146
|
+
await fs.writeJSON(path.join(dest, "package.json"), basePkg, { spaces: 2 });
|
|
147
|
+
}
|
|
148
|
+
if (await fs.pathExists(templateAppPath) && baseApp) {
|
|
149
|
+
const tmplApp = await fs.readJSON(templateAppPath);
|
|
150
|
+
const mergedApp = deepMerge(baseApp, tmplApp);
|
|
151
|
+
await fs.writeJSON(path.join(dest, "app.json"), mergedApp, { spaces: 2 });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const envPath = path.join(dest, "env");
|
|
155
|
+
if (await fs.pathExists(envPath)) {
|
|
156
|
+
await fs.move(envPath, path.join(dest, ".env"), { overwrite: true });
|
|
157
|
+
}
|
|
158
|
+
const pkgPath = path.join(dest, "package.json");
|
|
159
|
+
if (await fs.pathExists(pkgPath)) {
|
|
160
|
+
const pkg = await fs.readJSON(pkgPath);
|
|
161
|
+
pkg.name = project.name;
|
|
162
|
+
await fs.writeJSON(pkgPath, pkg, { spaces: 2 });
|
|
163
|
+
}
|
|
164
|
+
const appJsonPath = path.join(dest, "app.json");
|
|
165
|
+
if (await fs.pathExists(appJsonPath)) {
|
|
166
|
+
const appJson = await fs.readJSON(appJsonPath);
|
|
167
|
+
if (appJson.expo) {
|
|
168
|
+
appJson.expo.name = project.name;
|
|
169
|
+
appJson.expo.slug = String(project.name).toLowerCase().replace(/\s+/g, "-");
|
|
170
|
+
appJson.expo.scheme = appJson.expo.slug;
|
|
171
|
+
}
|
|
172
|
+
await fs.writeJSON(appJsonPath, appJson, { spaces: 2 });
|
|
173
|
+
}
|
|
110
174
|
s.stop("Files ready");
|
|
111
|
-
const extraDeps = resolveExtraDeps({ type: project.type, template: project.template, category: project.category, variant: project.variant });
|
|
112
175
|
if (project.install) {
|
|
113
176
|
const si = p.spinner();
|
|
114
177
|
si.start("Installing dependencies");
|
|
115
|
-
await installDeps(project.pm, dest
|
|
178
|
+
await installDeps(project.pm, dest);
|
|
116
179
|
si.stop("Installed");
|
|
117
180
|
if (opts.prebuild !== false) {
|
|
118
181
|
const sp = p.spinner();
|
|
119
182
|
sp.start("Generating ios/android (expo prebuild)");
|
|
120
|
-
|
|
121
|
-
|
|
183
|
+
try {
|
|
184
|
+
await execa("npx", ["expo", "prebuild"], { cwd: dest, stdio: "inherit", env: { ...process.env, CI: "1", EXPO_ROUTER_APP_ROOT: "src/app" } });
|
|
185
|
+
sp.stop("Native projects generated");
|
|
186
|
+
} catch (e) {
|
|
187
|
+
sp.stop("Prebuild failed (check logs)");
|
|
188
|
+
}
|
|
122
189
|
}
|
|
123
190
|
}
|
|
124
191
|
p.outro([
|
|
@@ -128,352 +195,7 @@ async function main() {
|
|
|
128
195
|
` ${project.pm} ${project.pm === "npm" ? "run " : ""}start`
|
|
129
196
|
].filter(Boolean).join("\n"));
|
|
130
197
|
}
|
|
131
|
-
function
|
|
132
|
-
const t = TEMPLATES[type];
|
|
133
|
-
if (Array.isArray(t)) return t;
|
|
134
|
-
return [{ value: "default", label: "Default (Expo Router + Uniwind)" }];
|
|
135
|
-
}
|
|
136
|
-
async function writeBaseline({ dest, name, withQuery }) {
|
|
137
|
-
const baseDeps = {
|
|
138
|
-
// Expo core + platform libs
|
|
139
|
-
expo: "54.0.23",
|
|
140
|
-
"expo-constants": "~18.0.10",
|
|
141
|
-
"expo-font": "~14.0.9",
|
|
142
|
-
"expo-linking": "~8.0.8",
|
|
143
|
-
"expo-router": "~6.0.14",
|
|
144
|
-
"expo-splash-screen": "~31.0.10",
|
|
145
|
-
"expo-status-bar": "~3.0.8",
|
|
146
|
-
"expo-web-browser": "~15.0.9",
|
|
147
|
-
// React ecosystem
|
|
148
|
-
react: "19.1.0",
|
|
149
|
-
"react-dom": "19.1.0",
|
|
150
|
-
"react-native": "0.81.5",
|
|
151
|
-
"react-native-web": "~0.21.0",
|
|
152
|
-
// RN libs
|
|
153
|
-
"react-native-reanimated": "~4.1.1",
|
|
154
|
-
"react-native-gesture-handler": "latest",
|
|
155
|
-
"react-native-safe-area-context": "~5.6.0",
|
|
156
|
-
"react-native-screens": "~4.16.0",
|
|
157
|
-
"react-native-worklets": "0.5.1",
|
|
158
|
-
// Styling
|
|
159
|
-
tailwindcss: "^4.1.16",
|
|
160
|
-
uniwind: "^1.0.0",
|
|
161
|
-
"@expo/vector-icons": "latest"
|
|
162
|
-
};
|
|
163
|
-
if (withQuery) {
|
|
164
|
-
baseDeps["@tanstack/react-query"] = "latest";
|
|
165
|
-
}
|
|
166
|
-
baseDeps["tailwind-merge"] = "latest";
|
|
167
|
-
baseDeps["react-native-keyboard-controller"] = "latest";
|
|
168
|
-
baseDeps["@gorhom/bottom-sheet"] = "latest";
|
|
169
|
-
baseDeps["sonner-native"] = "latest";
|
|
170
|
-
const pkg = {
|
|
171
|
-
name,
|
|
172
|
-
version: "0.0.0",
|
|
173
|
-
private: true,
|
|
174
|
-
main: "expo-router/entry",
|
|
175
|
-
scripts: {
|
|
176
|
-
start: "EXPO_ROUTER_APP_ROOT=src/app expo start",
|
|
177
|
-
android: "EXPO_ROUTER_APP_ROOT=src/app expo run:android",
|
|
178
|
-
ios: "EXPO_ROUTER_APP_ROOT=src/app expo run:ios",
|
|
179
|
-
prebuild: "EXPO_ROUTER_APP_ROOT=src/app expo prebuild"
|
|
180
|
-
},
|
|
181
|
-
dependencies: baseDeps,
|
|
182
|
-
devDependencies: {
|
|
183
|
-
"@types/react": "~19.1.0",
|
|
184
|
-
typescript: "~5.9.2",
|
|
185
|
-
"babel-plugin-module-resolver": "latest"
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
await fs.writeJSON(path.join(dest, "package.json"), pkg, { spaces: 2 });
|
|
189
|
-
const slug = String(name).toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
190
|
-
const pkgNameSegment = slug.replace(/-/g, "") || "app";
|
|
191
|
-
const bundleId = `com.example.${pkgNameSegment}`;
|
|
192
|
-
const appJson = {
|
|
193
|
-
expo: {
|
|
194
|
-
name,
|
|
195
|
-
slug,
|
|
196
|
-
version: "1.0.0",
|
|
197
|
-
orientation: "portrait",
|
|
198
|
-
icon: "./assets/images/icon.png",
|
|
199
|
-
scheme: slug,
|
|
200
|
-
userInterfaceStyle: "automatic",
|
|
201
|
-
newArchEnabled: true,
|
|
202
|
-
splash: {
|
|
203
|
-
image: "./assets/images/splash-icon.png",
|
|
204
|
-
resizeMode: "contain",
|
|
205
|
-
backgroundColor: "#ffffff"
|
|
206
|
-
},
|
|
207
|
-
ios: {
|
|
208
|
-
supportsTablet: true,
|
|
209
|
-
bundleIdentifier: bundleId
|
|
210
|
-
},
|
|
211
|
-
android: {
|
|
212
|
-
adaptiveIcon: {
|
|
213
|
-
foregroundImage: "./assets/images/adaptive-icon.png",
|
|
214
|
-
backgroundColor: "#ffffff"
|
|
215
|
-
},
|
|
216
|
-
edgeToEdgeEnabled: true,
|
|
217
|
-
predictiveBackGestureEnabled: false,
|
|
218
|
-
package: bundleId
|
|
219
|
-
},
|
|
220
|
-
web: {
|
|
221
|
-
bundler: "metro",
|
|
222
|
-
output: "static",
|
|
223
|
-
favicon: "./assets/images/favicon.png"
|
|
224
|
-
},
|
|
225
|
-
plugins: ["expo-router"],
|
|
226
|
-
experiments: {
|
|
227
|
-
typedRoutes: true
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
await fs.writeJSON(path.join(dest, "app.json"), appJson, { spaces: 2 });
|
|
232
|
-
const babel = `module.exports = function (api) {
|
|
233
|
-
api.cache(true);
|
|
234
|
-
return {
|
|
235
|
-
presets: ['babel-preset-expo'],
|
|
236
|
-
plugins: [
|
|
237
|
-
['module-resolver', { alias: { '~': './' } }],
|
|
238
|
-
'react-native-reanimated/plugin'
|
|
239
|
-
]
|
|
240
|
-
};
|
|
241
|
-
};
|
|
242
|
-
`;
|
|
243
|
-
await fs.writeFile(path.join(dest, "babel.config.js"), babel);
|
|
244
|
-
const metro = `const { getDefaultConfig } = require('expo/metro-config');
|
|
245
|
-
const { withUniwindConfig } = require('uniwind/metro');
|
|
246
|
-
|
|
247
|
-
const config = getDefaultConfig(__dirname);
|
|
248
|
-
|
|
249
|
-
module.exports = withUniwindConfig(config, {
|
|
250
|
-
cssEntryFile: './global.css',
|
|
251
|
-
dtsFile: './uniwind-types.d.ts',
|
|
252
|
-
});
|
|
253
|
-
`;
|
|
254
|
-
await fs.writeFile(path.join(dest, "metro.config.js"), metro);
|
|
255
|
-
const tsconfig = {
|
|
256
|
-
extends: "expo/tsconfig.base",
|
|
257
|
-
compilerOptions: {
|
|
258
|
-
strict: true,
|
|
259
|
-
paths: {
|
|
260
|
-
"~/*": ["./*"]
|
|
261
|
-
}
|
|
262
|
-
},
|
|
263
|
-
include: [
|
|
264
|
-
"**/*.ts",
|
|
265
|
-
"**/*.tsx",
|
|
266
|
-
".expo/types/**/*.ts",
|
|
267
|
-
"expo-env.d.ts",
|
|
268
|
-
"./uniwind-types.d.ts"
|
|
269
|
-
]
|
|
270
|
-
};
|
|
271
|
-
await fs.writeJSON(path.join(dest, "tsconfig.json"), tsconfig, { spaces: 2 });
|
|
272
|
-
const expoEnv = `/// <reference types="expo" />
|
|
273
|
-
/// <reference types="expo-router" />
|
|
274
|
-
`;
|
|
275
|
-
await fs.writeFile(path.join(dest, "expo-env.d.ts"), expoEnv);
|
|
276
|
-
const srcDir = path.join(dest, "src");
|
|
277
|
-
const appDir = path.join(srcDir, "app");
|
|
278
|
-
await fs.ensureDir(appDir);
|
|
279
|
-
await fs.ensureDir(path.join(srcDir, "core"));
|
|
280
|
-
await fs.ensureDir(path.join(srcDir, "api"));
|
|
281
|
-
await fs.ensureDir(path.join(srcDir, "lib"));
|
|
282
|
-
await fs.ensureDir(path.join(srcDir, "types"));
|
|
283
|
-
await fs.ensureDir(path.join(srcDir, "components", "screens", "home"));
|
|
284
|
-
await fs.ensureDir(path.join(srcDir, "components", "screens", "profile"));
|
|
285
|
-
await fs.ensureDir(path.join(srcDir, "components", "core"));
|
|
286
|
-
const textCore = `import { Text as RNText, TextProps } from 'react-native';
|
|
287
|
-
import { twMerge } from 'tailwind-merge';
|
|
288
|
-
|
|
289
|
-
type TypographyVariant =
|
|
290
|
-
| 'title'
|
|
291
|
-
| 'subtitle'
|
|
292
|
-
| 'body'
|
|
293
|
-
| 'caption'
|
|
294
|
-
| 'button'
|
|
295
|
-
| 'display'
|
|
296
|
-
| 'caption-primary'
|
|
297
|
-
| 'body-primary'
|
|
298
|
-
| 'subtitle-primary';
|
|
299
|
-
|
|
300
|
-
interface TextComponentProps extends TextProps {
|
|
301
|
-
className?: string;
|
|
302
|
-
variant?: TypographyVariant;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const variantStyles: Record<TypographyVariant, string> = {
|
|
306
|
-
title: 'text-2xl font-bold',
|
|
307
|
-
subtitle: 'text-xl font-semibold',
|
|
308
|
-
'subtitle-primary': 'text-xl font-semibold text-primary',
|
|
309
|
-
body: 'text-base',
|
|
310
|
-
'body-primary': 'text-base text-primary',
|
|
311
|
-
caption: 'text-sm font-medium',
|
|
312
|
-
'caption-primary': 'text-sm text-primary font-medium',
|
|
313
|
-
button: 'text-xl text-primary font-semibold text-white text-center',
|
|
314
|
-
display: 'text-3x font-bold',
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
const Text = ({ variant = 'body', children, className, ...props }: TextComponentProps) => {
|
|
318
|
-
const textStyle = twMerge('text-black', variantStyles[variant], className);
|
|
319
|
-
return (
|
|
320
|
-
<RNText className={textStyle} {...props}>
|
|
321
|
-
{children}
|
|
322
|
-
</RNText>
|
|
323
|
-
);
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
export default Text;
|
|
327
|
-
`;
|
|
328
|
-
await fs.writeFile(path.join(srcDir, "components", "core", "text.tsx"), textCore);
|
|
329
|
-
const easJson = {
|
|
330
|
-
cli: { version: ">= 7.0.0" },
|
|
331
|
-
build: {
|
|
332
|
-
development: { android: { buildType: "apk" }, developmentClient: true, distribution: "internal" },
|
|
333
|
-
preview: { distribution: "internal" },
|
|
334
|
-
production: {}
|
|
335
|
-
},
|
|
336
|
-
submit: { production: {} }
|
|
337
|
-
};
|
|
338
|
-
await fs.writeJSON(path.join(dest, "eas.json"), easJson, { spaces: 2 });
|
|
339
|
-
const globalCss = `@import 'tailwindcss';
|
|
340
|
-
@import 'uniwind';
|
|
341
|
-
|
|
342
|
-
@layer theme {
|
|
343
|
-
:root {
|
|
344
|
-
@variant light {
|
|
345
|
-
--color-background: #ffffff;
|
|
346
|
-
--color-foreground: #111827;
|
|
347
|
-
--color-primary: #3b82f6;
|
|
348
|
-
}
|
|
349
|
-
@variant dark {
|
|
350
|
-
--color-background: #000000;
|
|
351
|
-
--color-foreground: #ffffff;
|
|
352
|
-
--color-primary: #60a5fa;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
`;
|
|
357
|
-
await fs.writeFile(path.join(dest, "global.css"), globalCss);
|
|
358
|
-
{
|
|
359
|
-
const rootLayout = `import { Stack } from 'expo-router';
|
|
360
|
-
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
361
|
-
import { KeyboardProvider } from 'react-native-keyboard-controller';
|
|
362
|
-
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
|
363
|
-
import { Toaster } from 'sonner-native';
|
|
364
|
-
import 'react-native-reanimated';
|
|
365
|
-
import '../../global.css';
|
|
366
|
-
|
|
367
|
-
export default function RootLayout() {
|
|
368
|
-
return (
|
|
369
|
-
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
370
|
-
<KeyboardProvider>
|
|
371
|
-
<BottomSheetModalProvider>
|
|
372
|
-
<Stack>
|
|
373
|
-
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
|
374
|
-
</Stack>
|
|
375
|
-
<Toaster />
|
|
376
|
-
</BottomSheetModalProvider>
|
|
377
|
-
</KeyboardProvider>
|
|
378
|
-
</GestureHandlerRootView>
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
`;
|
|
382
|
-
await fs.writeFile(path.join(appDir, "_layout.tsx"), rootLayout);
|
|
383
|
-
const tabsDir = path.join(appDir, "(tabs)");
|
|
384
|
-
await fs.ensureDir(tabsDir);
|
|
385
|
-
const layout = `import FontAwesome from '@expo/vector-icons/FontAwesome';
|
|
386
|
-
import { Tabs } from 'expo-router';
|
|
387
|
-
|
|
388
|
-
function TabBarIcon(props: { name: React.ComponentProps<typeof FontAwesome>['name']; color: string; }) {
|
|
389
|
-
return <FontAwesome size={28} style={{ marginBottom: -3 }} {...props} />;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
export default function TabLayout() {
|
|
393
|
-
return (
|
|
394
|
-
<Tabs screenOptions={{ headerShown: false }}>
|
|
395
|
-
<Tabs.Screen
|
|
396
|
-
name="index"
|
|
397
|
-
options={{
|
|
398
|
-
title: 'Home',
|
|
399
|
-
tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
|
|
400
|
-
}}
|
|
401
|
-
/>
|
|
402
|
-
<Tabs.Screen
|
|
403
|
-
name="two"
|
|
404
|
-
options={{
|
|
405
|
-
title: 'Explore',
|
|
406
|
-
tabBarIcon: ({ color }) => <TabBarIcon name="search" color={color} />,
|
|
407
|
-
}}
|
|
408
|
-
/>
|
|
409
|
-
</Tabs>
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
`;
|
|
413
|
-
await fs.writeFile(path.join(tabsDir, "_layout.tsx"), layout);
|
|
414
|
-
const tabOne = `import { View, Text } from 'react-native';
|
|
415
|
-
|
|
416
|
-
export default function Index() {
|
|
417
|
-
return (
|
|
418
|
-
<View className="flex-1 items-center justify-center bg-background">
|
|
419
|
-
<Text className="text-foreground text-xl">Tab One</Text>
|
|
420
|
-
</View>
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
`;
|
|
424
|
-
await fs.writeFile(path.join(tabsDir, "index.tsx"), tabOne);
|
|
425
|
-
const tabTwo = `import { View, Text } from 'react-native';
|
|
426
|
-
|
|
427
|
-
export default function Two() {
|
|
428
|
-
return (
|
|
429
|
-
<View className="flex-1 items-center justify-center bg-background">
|
|
430
|
-
<Text className="text-foreground text-xl">Tab Two</Text>
|
|
431
|
-
</View>
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
`;
|
|
435
|
-
await fs.writeFile(path.join(tabsDir, "two.tsx"), tabTwo);
|
|
436
|
-
const modal = `import { View, Text } from 'react-native';
|
|
437
|
-
|
|
438
|
-
export default function Modal() {
|
|
439
|
-
return (
|
|
440
|
-
<View className="flex-1 items-center justify-center bg-background">
|
|
441
|
-
<Text className="text-foreground text-xl">Modal screen</Text>
|
|
442
|
-
</View>
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
`;
|
|
446
|
-
await fs.writeFile(path.join(appDir, "modal.tsx"), modal);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
async function applyTemplateOverlay({ dest, type, template, category, variant }) {
|
|
450
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
451
|
-
const defaultSrc = path.join(__dirname, "..", "templates", "default");
|
|
452
|
-
if (await fs.pathExists(defaultSrc)) {
|
|
453
|
-
await fs.copy(defaultSrc, dest, { overwrite: false, errorOnExist: false });
|
|
454
|
-
}
|
|
455
|
-
let src = null;
|
|
456
|
-
if (type === "app-brief" || type === "mini-app-brief") {
|
|
457
|
-
if (category && variant) {
|
|
458
|
-
src = path.join(__dirname, "..", "templates", type, category, variant);
|
|
459
|
-
}
|
|
460
|
-
} else if (type && template) {
|
|
461
|
-
src = path.join(__dirname, "..", "templates", type, template);
|
|
462
|
-
} else if (type === "default") {
|
|
463
|
-
src = path.join(__dirname, "..", "templates", "default");
|
|
464
|
-
}
|
|
465
|
-
if (src && await fs.pathExists(src)) {
|
|
466
|
-
await fs.copy(src, dest, { overwrite: true });
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
function resolveExtraDeps({ type, template, category, variant }) {
|
|
470
|
-
let key = null;
|
|
471
|
-
if (type === "boilerplate" && template) key = `${type}/${template}`;
|
|
472
|
-
if ((type === "app-brief" || type === "mini-app-brief") && category && variant) key = `${type}/${category}/${variant}`;
|
|
473
|
-
const spec = key ? TEMPLATE_MAP[key] : void 0;
|
|
474
|
-
return spec?.extraDeps ?? [];
|
|
475
|
-
}
|
|
476
|
-
async function installDeps(pm, cwd, _extraDeps) {
|
|
198
|
+
async function installDeps(pm, cwd) {
|
|
477
199
|
const cmd = pm === "bun" ? "bun" : pm;
|
|
478
200
|
const args = pm === "bun" ? ["install"] : ["install"];
|
|
479
201
|
await execa(cmd, args, { cwd, stdio: "inherit" });
|
package/package.json
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expo": {
|
|
3
|
+
"name": "Newcandies App",
|
|
4
|
+
"slug": "newcandies-app",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"orientation": "portrait",
|
|
7
|
+
"icon": "./assets/images/icon.png",
|
|
8
|
+
"scheme": "newcandies-app",
|
|
9
|
+
"userInterfaceStyle": "automatic",
|
|
10
|
+
"newArchEnabled": true,
|
|
11
|
+
"splash": {
|
|
12
|
+
"image": "./assets/images/splash-icon.png",
|
|
13
|
+
"resizeMode": "contain",
|
|
14
|
+
"backgroundColor": "#ffffff"
|
|
15
|
+
},
|
|
16
|
+
"ios": {
|
|
17
|
+
"supportsTablet": true,
|
|
18
|
+
"bundleIdentifier": "com.example.newcandiesapp"
|
|
19
|
+
},
|
|
20
|
+
"android": {
|
|
21
|
+
"adaptiveIcon": {
|
|
22
|
+
"foregroundImage": "./assets/images/adaptive-icon.png",
|
|
23
|
+
"backgroundColor": "#ffffff"
|
|
24
|
+
},
|
|
25
|
+
"edgeToEdgeEnabled": true,
|
|
26
|
+
"predictiveBackGestureEnabled": false,
|
|
27
|
+
"package": "com.example.newcandiesapp"
|
|
28
|
+
},
|
|
29
|
+
"web": {
|
|
30
|
+
"bundler": "metro",
|
|
31
|
+
"output": "static",
|
|
32
|
+
"favicon": "./assets/images/favicon.png"
|
|
33
|
+
},
|
|
34
|
+
"plugins": ["expo-router"],
|
|
35
|
+
"experiments": {
|
|
36
|
+
"typedRoutes": true
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cli": { "version": ">= 7.0.0" },
|
|
3
|
+
"build": {
|
|
4
|
+
"development": { "android": { "buildType": "apk" }, "developmentClient": true, "distribution": "internal" },
|
|
5
|
+
"preview": { "distribution": "internal" },
|
|
6
|
+
"production": {}
|
|
7
|
+
},
|
|
8
|
+
"submit": { "production": {} }
|
|
9
|
+
}
|
|
10
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
@import 'uniwind';
|
|
3
|
+
|
|
4
|
+
@layer theme {
|
|
5
|
+
:root {
|
|
6
|
+
@variant light {
|
|
7
|
+
--color-background: #ffffff;
|
|
8
|
+
--color-foreground: #111827;
|
|
9
|
+
--color-primary: #3b82f6;
|
|
10
|
+
}
|
|
11
|
+
@variant dark {
|
|
12
|
+
--color-background: #000000;
|
|
13
|
+
--color-foreground: #ffffff;
|
|
14
|
+
--color-primary: #60a5fa;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { getDefaultConfig } = require('expo/metro-config');
|
|
2
|
+
const { withUniwindConfig } = require('uniwind/metro');
|
|
3
|
+
|
|
4
|
+
const config = getDefaultConfig(__dirname);
|
|
5
|
+
|
|
6
|
+
module.exports = withUniwindConfig(config, {
|
|
7
|
+
cssEntryFile: './global.css',
|
|
8
|
+
dtsFile: './uniwind-types.d.ts',
|
|
9
|
+
});
|
|
10
|
+
|