newcandies 0.1.40 → 0.1.42

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 CHANGED
@@ -10,7 +10,7 @@ import { fileURLToPath } from "url";
10
10
  import { execa } from "execa";
11
11
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
12
12
  var REGISTRY_PATH = path.join(__dirname, "..", "templates", "registry.json");
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);
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>", "package manager (pnpm for boilerplates, bun for app-briefs)").option("--no-install").option("--no-prebuild").parse(process.argv);
14
14
  var opts = program.opts();
15
15
  function deepMerge(target, source) {
16
16
  for (const key in source) {
@@ -25,84 +25,108 @@ async function main() {
25
25
  console.clear();
26
26
  p.intro(pc.bgMagenta(pc.black(" newcandies ")));
27
27
  const registry = await fs.readJSON(REGISTRY_PATH);
28
- const project = await p.group({
29
- name: () => p.text({
30
- message: "Project name?",
31
- placeholder: "my-newcandies-app",
32
- validate: (v) => !v ? "Enter a name" : /[^a-zA-Z0-9_-]/.test(v) ? "Use letters, numbers, _ or -" : void 0
33
- }),
34
- type: async () => {
35
- if (opts.type) return opts.type;
36
- return p.select({
37
- message: "Pick type",
38
- options: [
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)" }
44
- ]
45
- });
46
- },
47
- template: async ({ results }) => {
48
- const type = results.type;
49
- if (type === "default" || type === "app-briefs" || type === "mini-app-briefs") return null;
50
- if (opts.template) return opts.template;
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
- });
56
- },
57
- category: async ({ results }) => {
58
- const type = results.type;
59
- if (type !== "app-briefs" && type !== "mini-app-briefs") return null;
60
- if (opts.category) return opts.category;
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
- });
66
- },
67
- variant: async ({ results }) => {
68
- const type = results.type;
69
- if (type !== "app-briefs" && type !== "mini-app-briefs") return null;
70
- if (opts.variant) return opts.variant;
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
- });
77
- },
78
- install: async () => {
79
- if (opts.yes !== void 0) return !!opts.yes;
80
- if (opts.install === false) return false;
81
- return p.confirm({ message: "Install dependencies?", initialValue: true });
28
+ const project = await p.group(
29
+ {
30
+ name: () => p.text({
31
+ message: "Project name?",
32
+ placeholder: "my-newcandies-app",
33
+ validate: (v) => !v ? "Enter a name" : /[^a-zA-Z0-9_-]/.test(v) ? "Use letters, numbers, _ or -" : void 0
34
+ }),
35
+ type: async () => {
36
+ if (opts.type) return opts.type;
37
+ return p.select({
38
+ message: "Pick type",
39
+ options: [
40
+ { value: "boilerplates", label: "Boilerplate (Full Projects)" },
41
+ { value: "mini-boilerplates", label: "Mini-boilerplate" },
42
+ { value: "app-briefs", label: "App-brief (2 levels)" },
43
+ { value: "mini-app-briefs", label: "Mini-app-brief (2 levels)" },
44
+ { value: "default", label: "Default (Base)" }
45
+ ]
46
+ });
47
+ },
48
+ template: async ({ results }) => {
49
+ const type = results.type;
50
+ if (type === "default" || type === "app-briefs" || type === "mini-app-briefs")
51
+ return null;
52
+ if (opts.template) return opts.template;
53
+ const items = registry[type];
54
+ return p.select({
55
+ message: "Pick template",
56
+ options: items.map((i) => ({
57
+ value: i.name,
58
+ label: `${i.name} - ${i.description}`
59
+ }))
60
+ });
61
+ },
62
+ category: async ({ results }) => {
63
+ const type = results.type;
64
+ if (type !== "app-briefs" && type !== "mini-app-briefs") return null;
65
+ if (opts.category) return opts.category;
66
+ const cats = registry[type];
67
+ return p.select({
68
+ message: "Pick category",
69
+ options: cats.map((c) => ({ value: c.category, label: c.category }))
70
+ });
71
+ },
72
+ variant: async ({ results }) => {
73
+ const type = results.type;
74
+ if (type !== "app-briefs" && type !== "mini-app-briefs") return null;
75
+ if (opts.variant) return opts.variant;
76
+ const cats = registry[type];
77
+ const cat = cats.find((c) => c.category === results.category);
78
+ return p.select({
79
+ message: "Pick variant",
80
+ options: cat.variants.map((v) => ({
81
+ value: v.name,
82
+ label: `${v.name} - ${v.description}`
83
+ }))
84
+ });
85
+ },
86
+ install: async () => {
87
+ if (opts.yes !== void 0) return !!opts.yes;
88
+ if (opts.install === false) return false;
89
+ return p.confirm({
90
+ message: "Install dependencies?",
91
+ initialValue: true
92
+ });
93
+ },
94
+ pm: async ({ results }) => {
95
+ const type = results.type;
96
+ if (type === "boilerplates" || type === "mini-boilerplates") {
97
+ if (opts.pm) return opts.pm;
98
+ return p.select({
99
+ message: "Package manager?",
100
+ options: [{ value: "pnpm", label: "pnpm" }],
101
+ initialValue: "pnpm"
102
+ });
103
+ }
104
+ if (opts.pm) return opts.pm;
105
+ return p.select({
106
+ message: "Package manager?",
107
+ options: [{ value: "bun", label: "bun" }],
108
+ initialValue: "bun"
109
+ });
110
+ }
82
111
  },
83
- pm: async () => {
84
- if (opts.pm) return opts.pm;
85
- return p.select({
86
- message: "Package manager?",
87
- options: [
88
- { value: "pnpm", label: "pnpm" },
89
- { value: "npm", label: "npm" },
90
- { value: "yarn", label: "yarn" },
91
- { value: "bun", label: "bun" }
92
- ],
93
- initialValue: "pnpm"
94
- });
112
+ {
113
+ onCancel: () => {
114
+ p.cancel("Aborted");
115
+ process.exit(0);
116
+ }
95
117
  }
96
- }, { onCancel: () => {
97
- p.cancel("Aborted");
98
- process.exit(0);
99
- } });
118
+ );
100
119
  const dest = path.resolve(process.cwd(), project.name);
101
120
  const s = p.spinner();
102
121
  s.start("Scaffolding");
103
122
  let selectedItem = null;
104
123
  if (project.type === "default") {
105
- selectedItem = { name: "default", path: "", description: "Default", strategy: "base" };
124
+ selectedItem = {
125
+ name: "default",
126
+ path: "",
127
+ description: "Default",
128
+ strategy: "base"
129
+ };
106
130
  } else if (project.type === "boilerplates" || project.type === "mini-boilerplates") {
107
131
  const list = registry[project.type];
108
132
  selectedItem = list.find((i) => i.name === project.template) || null;
@@ -130,24 +154,51 @@ async function main() {
130
154
  if (await fs.pathExists(appPath)) baseApp = await fs.readJSON(appPath);
131
155
  }
132
156
  if (selectedItem.path) {
133
- const templatePath = path.join(__dirname, "..", "templates", selectedItem.path);
157
+ const templatePath = path.join(
158
+ __dirname,
159
+ "..",
160
+ "templates",
161
+ selectedItem.path
162
+ );
134
163
  if (await fs.pathExists(templatePath)) {
135
164
  await fs.copy(templatePath, dest, { overwrite: true });
136
165
  }
137
166
  }
138
167
  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");
168
+ const templatePkgPath = path.join(
169
+ __dirname,
170
+ "..",
171
+ "templates",
172
+ selectedItem.path,
173
+ "package.json"
174
+ );
175
+ const templateAppPath = path.join(
176
+ __dirname,
177
+ "..",
178
+ "templates",
179
+ selectedItem.path,
180
+ "app.json"
181
+ );
141
182
  if (await fs.pathExists(templatePkgPath)) {
142
183
  const tmplPkg = await fs.readJSON(templatePkgPath);
143
184
  const isFullPackageJson = tmplPkg.scripts || tmplPkg.main || tmplPkg.version;
144
185
  if (isFullPackageJson) {
145
- await fs.writeJSON(path.join(dest, "package.json"), tmplPkg, { spaces: 2 });
186
+ await fs.writeJSON(path.join(dest, "package.json"), tmplPkg, {
187
+ spaces: 2
188
+ });
146
189
  } else {
147
- basePkg.dependencies = { ...basePkg.dependencies, ...tmplPkg.dependencies };
148
- basePkg.devDependencies = { ...basePkg.devDependencies, ...tmplPkg.devDependencies };
190
+ basePkg.dependencies = {
191
+ ...basePkg.dependencies,
192
+ ...tmplPkg.dependencies
193
+ };
194
+ basePkg.devDependencies = {
195
+ ...basePkg.devDependencies,
196
+ ...tmplPkg.devDependencies
197
+ };
149
198
  basePkg.scripts = { ...basePkg.scripts, ...tmplPkg.scripts };
150
- await fs.writeJSON(path.join(dest, "package.json"), basePkg, { spaces: 2 });
199
+ await fs.writeJSON(path.join(dest, "package.json"), basePkg, {
200
+ spaces: 2
201
+ });
151
202
  }
152
203
  }
153
204
  if (await fs.pathExists(templateAppPath) && baseApp) {
@@ -160,7 +211,9 @@ async function main() {
160
211
  for (const file of destFiles) {
161
212
  if (file === "env" || file.startsWith("env.") || file === "npmrc") {
162
213
  const newName = "." + file;
163
- await fs.move(path.join(dest, file), path.join(dest, newName), { overwrite: true });
214
+ await fs.move(path.join(dest, file), path.join(dest, newName), {
215
+ overwrite: true
216
+ });
164
217
  }
165
218
  }
166
219
  const pkgPath = path.join(dest, "package.json");
@@ -191,14 +244,18 @@ async function main() {
191
244
  const sp = p.spinner();
192
245
  sp.start("Generating ios/android (expo prebuild)");
193
246
  try {
194
- await execa("npx", ["expo", "prebuild"], { cwd: dest, stdio: "inherit", env: { ...process.env, CI: "1" } });
247
+ await execa("npx", ["expo", "prebuild"], {
248
+ cwd: dest,
249
+ stdio: "inherit",
250
+ env: { ...process.env, CI: "1" }
251
+ });
195
252
  sp.stop("Native projects generated");
196
253
  } catch (e) {
197
254
  sp.stop("Prebuild failed (check logs)");
198
255
  }
199
256
  }
200
257
  }
201
- const runCmd = (cmd) => ` ${project.pm} ${project.pm === "npm" ? "run " : ""}${cmd}`;
258
+ const runCmd = (cmd) => ` ${project.pm} ${cmd}`;
202
259
  const nextSteps = [
203
260
  pc.green("Next steps:"),
204
261
  ` cd ${project.name}`,
@@ -209,14 +266,8 @@ async function main() {
209
266
  p.outro(nextSteps.filter(Boolean).join("\n"));
210
267
  }
211
268
  async function installDeps(pm, cwd) {
212
- const cmd = pm === "bun" ? "bun" : pm;
213
- let args = ["install"];
214
- if (pm === "npm") {
215
- args.push("--no-audit", "--no-fund", "--loglevel=error");
216
- } else if (pm === "pnpm") {
217
- args.push("--no-optional");
218
- }
219
- await execa(cmd, args, { cwd, stdio: "pipe" });
269
+ const args = ["install"];
270
+ await execa(pm, args, { cwd, stdio: "pipe" });
220
271
  }
221
272
  main().catch((e) => {
222
273
  p.log.error(e?.message ?? String(e));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newcandies",
3
- "version": "0.1.40",
3
+ "version": "0.1.42",
4
4
  "description": "Scaffold Expo Router + Uniwind React Native apps with layered templates.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,11 @@
1
+ import { Text, View } from 'react-native';
2
+
3
+ const Home = () => {
4
+ return (
5
+ <View>
6
+ <Text>Home</Text>
7
+ </View>
8
+ );
9
+ };
10
+
11
+ export default Home;
@@ -1,264 +0,0 @@
1
- import Ionicons from '@expo/vector-icons/Ionicons';
2
- import BottomSheet, {
3
- BottomSheetBackdrop,
4
- BottomSheetBackdropProps,
5
- BottomSheetView,
6
- } from '@gorhom/bottom-sheet';
7
- import { Calendar, fromDateId, toDateId, useDateRange } from '@marceloterreiro/flash-calendar';
8
- import { nanoid } from 'nanoid/non-secure';
9
- import { toast } from 'sonner-native';
10
-
11
- import { useQuery } from '@tanstack/react-query';
12
- import {
13
- addMonths,
14
- differenceInDays,
15
- isBefore,
16
- isSameMonth,
17
- startOfMonth,
18
- subMonths,
19
- } from 'date-fns';
20
- import { router, useLocalSearchParams } from 'expo-router';
21
- import { SquircleButton } from 'expo-squircle-view';
22
- import { useCallback, useMemo, useRef, useState } from 'react';
23
- import { Pressable, ScrollView, View } from 'react-native';
24
- import Container from '~/components/container';
25
- import Header from '~/components/header';
26
- import LoadingIndicator from '~/components/loading-indicator';
27
- import AmenitiesList from '~/components/property/amenities-list';
28
- import PropertyImage from '~/components/property/property-image';
29
- import Text from '~/components/text';
30
- import { client } from '~/core/api/client';
31
- import { today } from '~/core/constants';
32
- import useShoppingCartStore from '~/core/store';
33
- import { calendarTheme } from '~/core/theme/calendar-theme';
34
- import { PRIMARY } from '~/core/theme/colors';
35
-
36
- type Props = {};
37
- const Property = ({}: Props) => {
38
- const { id } = useLocalSearchParams();
39
-
40
- const { data: property, isLoading } = useQuery<Property>({
41
- queryKey: ['property' + id],
42
- queryFn: async () => {
43
- const { data } = await client.get(`/properties/${id}`);
44
- return data.property;
45
- },
46
- });
47
-
48
- const { addItem } = useShoppingCartStore();
49
-
50
- const { calendarActiveDateRanges, onCalendarDayPress } = useDateRange();
51
-
52
- console.log({ calendarActiveDateRanges });
53
-
54
- const bottomSheetRef = useRef<BottomSheet>(null);
55
-
56
- const snapPoints = useMemo(() => ['60%'], []);
57
-
58
- const renderBackdrop = useCallback((props: BottomSheetBackdropProps) => {
59
- return (
60
- <BottomSheetBackdrop
61
- {...props}
62
- disappearsOnIndex={-1}
63
- appearsOnIndex={0}
64
- pressBehavior={'close'}
65
- />
66
- );
67
- }, []);
68
-
69
- const calculateDays = () => {
70
- if (!calendarActiveDateRanges[0]?.startId) return 0;
71
- if (!calendarActiveDateRanges[0]?.endId) return 1;
72
-
73
- const startDate = new Date(calendarActiveDateRanges[0].startId);
74
- const endDate = new Date(calendarActiveDateRanges[0].endId);
75
-
76
- return differenceInDays(endDate, startDate) + 1;
77
- };
78
-
79
- const hasSelectedDates = Boolean(calendarActiveDateRanges[0]?.startId);
80
- const [calendarMonthId, setCalendarMonthId] = useState(today);
81
-
82
- const nextMonth = () => {
83
- const month = addMonths(calendarMonthId, 1);
84
- setCalendarMonthId(toDateId(month));
85
- };
86
-
87
- const currentDisplayMonth = fromDateId(calendarMonthId);
88
- const canGoBack =
89
- !isSameMonth(currentDisplayMonth, today) && !isBefore(currentDisplayMonth, startOfMonth(today));
90
-
91
- const prevMonth = () => {
92
- if (canGoBack) {
93
- const month = subMonths(calendarMonthId, 1);
94
- setCalendarMonthId(toDateId(month));
95
- }
96
- };
97
- if (isLoading || !property) {
98
- return <LoadingIndicator />;
99
- }
100
- const days = calculateDays();
101
- const totalPrice = days * property.price_per_night;
102
-
103
- return (
104
- <Container>
105
- <Header title="Property" />
106
- <ScrollView className="flex-1 bg-gray-100 p-4">
107
- <PropertyImage
108
- imageUrl={property?.images[1]}
109
- isFavorite={property?.is_favorite}
110
- rating={5}
111
- />
112
- <View className="flex flex-row items-center justify-between">
113
- <Text variant="subtitle-primary" className="mt-4">
114
- {property.name}
115
- </Text>
116
- <View className="flex flex-row items-center justify-center">
117
- <Ionicons name="pricetag" size={12} color={PRIMARY} />
118
- <Text variant="body-primary" className="ml-2">
119
- ${property.price_per_night} per night
120
- </Text>
121
- </View>
122
- </View>
123
- <View className="flex flex-row items-center">
124
- <Ionicons name="location" size={16} color={PRIMARY} />
125
- <Text variant="body-primary" className="">
126
- {property.city}, {property.country}
127
- </Text>
128
- </View>
129
- <Text variant="body" className="mt-1 text-gray-700">
130
- {property.description}
131
- </Text>
132
- <AmenitiesList amenities={property.amenities} />
133
- </ScrollView>
134
-
135
- <BottomSheet
136
- ref={bottomSheetRef}
137
- snapPoints={snapPoints}
138
- backdropComponent={renderBackdrop}
139
- index={-1}
140
- enablePanDownToClose={true}
141
- enableDynamicSizing={false}>
142
- <BottomSheetView style={{ flex: 1 }}>
143
- <View className="my-4 flex flex-row items-center justify-between px-4">
144
- <View className="flex flex-row items-center justify-center">
145
- <Ionicons name="wallet" color={PRIMARY} size={24} />
146
- <Text variant="subtitle" className="mx-4">
147
- Price : ${hasSelectedDates ? totalPrice : property.price_per_night}
148
- {!hasSelectedDates && ' per night'}
149
- </Text>
150
- </View>
151
- </View>
152
- <BottomSheetView style={{ flex: 1, paddingHorizontal: 4, position: 'relative' }}>
153
- <View className="mt-20 flex flex-row justify-between">
154
- <Pressable onPress={prevMonth} disabled={!canGoBack}>
155
- <Ionicons name="arrow-back" size={24} color={canGoBack ? PRIMARY : 'gray'} />
156
- </Pressable>
157
- <Pressable onPress={nextMonth}>
158
- <Ionicons name="arrow-forward" size={24} color={PRIMARY} />
159
- </Pressable>
160
- </View>
161
-
162
- <Calendar
163
- calendarMonthId={calendarMonthId}
164
- calendarActiveDateRanges={calendarActiveDateRanges}
165
- calendarMinDateId={today}
166
- onCalendarDayPress={onCalendarDayPress}
167
- theme={calendarTheme}
168
- />
169
- <SquircleButton
170
- backgroundColor={PRIMARY}
171
- cornerSmoothing={100}
172
- onPress={() => {
173
- bottomSheetRef.current?.close();
174
-
175
- if (!hasSelectedDates || !calendarActiveDateRanges[0]?.startId) {
176
- console.log('error: please select dates');
177
- return;
178
- }
179
-
180
- const cartItem: ICartItem = {
181
- id: 'cart' + nanoid(),
182
- image: property.images[0],
183
- name: property.name,
184
- product: property.id,
185
- price_per_night: property.price_per_night,
186
- quantity: 1,
187
- startDate: calendarActiveDateRanges[0].startId,
188
- endDate:
189
- calendarActiveDateRanges[0]?.endId ?? calendarActiveDateRanges[0].startId,
190
- days: calculateDays(),
191
- };
192
- addItem(cartItem);
193
- bottomSheetRef.current?.close();
194
- }}
195
- preserveSmoothing
196
- className="m-8 flex flex-row items-center justify-center px-4 "
197
- style={{
198
- paddingVertical: 16,
199
- position: 'absolute',
200
- bottom: -120,
201
- left: 0,
202
- right: 0,
203
- }}
204
- borderRadius={24}>
205
- <Ionicons name="checkmark-circle" size={20} color={'white'} />
206
- <Text variant="button" className="mx-2 text-center">
207
- Confirm
208
- </Text>
209
- </SquircleButton>
210
- </BottomSheetView>
211
- </BottomSheetView>
212
- </BottomSheet>
213
-
214
- <View className="bottom-0 left-0 right-0 -z-10 mx-4 mt-auto flex flex-row items-center justify-center py-2">
215
- {hasSelectedDates ? (
216
- <Pressable
217
- className="mr-4"
218
- onPress={() => {
219
- bottomSheetRef.current?.expand();
220
- }}>
221
- <View className="flex flex-row items-center">
222
- <Ionicons name="pricetag" color={PRIMARY} size={16} />
223
- <Text variant="body-primary" className="text-center">
224
- ${totalPrice}
225
- </Text>
226
- </View>
227
- <Text variant="caption" className="text-center underline">
228
- {days === 1 ? '1 Night' : `${days} nights`}
229
- </Text>
230
- </Pressable>
231
- ) : (
232
- <Pressable
233
- className="mr-4 flex flex-row items-center"
234
- onPress={() => {
235
- bottomSheetRef.current?.expand();
236
- }}>
237
- <Ionicons name="calendar-outline" size={24} color={PRIMARY} />
238
- <Text variant="body-primary" className="ml-2 text-center underline">
239
- Select dates
240
- </Text>
241
- </Pressable>
242
- )}
243
- <SquircleButton
244
- onPress={() => {
245
- toast.success('Property added to cart');
246
- router.push('/checkout');
247
- }}
248
- className="flex-grow"
249
- backgroundColor={PRIMARY}
250
- borderRadius={16}
251
- style={{
252
- paddingVertical: 16,
253
- marginVertical: 4,
254
- }}>
255
- <Text variant="button" className="text-center">
256
- Book Now
257
- </Text>
258
- </SquircleButton>
259
- </View>
260
- </Container>
261
- );
262
- };
263
-
264
- export default Property;