codingpixel-expo-app 0.1.1 → 0.1.3
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/README.md
CHANGED
|
@@ -7,10 +7,13 @@ Opinionated Expo SDK 54+ app scaffolder. Mirrors the [MyRoster](https://github.c
|
|
|
7
7
|
```bash
|
|
8
8
|
npx codingpixel-expo-app my-app
|
|
9
9
|
cd my-app
|
|
10
|
-
|
|
11
|
-
yarn ios # or: npm run ios / yarn android / npm run android
|
|
10
|
+
yarn ios # or: yarn android (npm run ios / npm run android also work)
|
|
12
11
|
```
|
|
13
12
|
|
|
13
|
+
`yarn ios` / `yarn android` map to `expo run:ios` / `expo run:android` — these BUILD + install the custom dev-client + launch in one shot (3-10 min the first time, fast incremental after). No separate `expo prebuild` step needed.
|
|
14
|
+
|
|
15
|
+
For subsequent runs after the dev-client is installed, use `yarn start` (= `expo start --dev-client`) for faster iteration.
|
|
16
|
+
|
|
14
17
|
The bin name is `codingpixel-expo` (used after global install). Invoke through `npx codingpixel-expo-app` (the package name) for one-shot runs.
|
|
15
18
|
|
|
16
19
|
## Not Expo Go-compatible
|
|
@@ -23,14 +26,13 @@ This template ships native modules that Expo Go can't load:
|
|
|
23
26
|
- `react-native-keyboard-controller`
|
|
24
27
|
- `@gorhom/bottom-sheet` (when bottom-sheet support enabled)
|
|
25
28
|
|
|
26
|
-
You **must** prebuild +
|
|
29
|
+
You **must** run a custom dev-client. `yarn ios` / `yarn android` (= `expo run:ios` / `expo run:android`) handle prebuild + native build + dev-client install in one command:
|
|
27
30
|
|
|
28
31
|
```bash
|
|
29
|
-
npx expo prebuild
|
|
30
32
|
yarn ios # or yarn android / npm run ios / npm run android
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
Subsequent runs use the dev-client + bundler (`yarn start`).
|
|
35
|
+
Subsequent runs use the dev-client + bundler (`yarn start` = `expo start --dev-client`).
|
|
34
36
|
|
|
35
37
|
## Fonts
|
|
36
38
|
|
|
@@ -48,12 +50,14 @@ If your generated app's `tsconfig.json` already has a `@/*` mapping, the CLI pre
|
|
|
48
50
|
|
|
49
51
|
iOS:
|
|
50
52
|
- Xcode + CocoaPods installed (`sudo gem install cocoapods` if missing).
|
|
51
|
-
- `
|
|
53
|
+
- `yarn ios` (or `npm run ios`) — generates `ios/` dir, runs CocoaPods, builds the dev-client, installs in simulator, launches.
|
|
52
54
|
|
|
53
55
|
Android:
|
|
54
56
|
- Android SDK + emulator running (or a USB-attached device with debugging enabled).
|
|
55
57
|
- `JAVA_HOME` pointing at JDK 17.
|
|
56
|
-
- `
|
|
58
|
+
- `yarn android` (or `npm run android`) — generates `android/` dir, builds the dev-client APK, installs, launches.
|
|
59
|
+
|
|
60
|
+
Bundle identifier defaults to `com.<app-name-no-dashes>` (e.g. `com.myapp`). Edit `app.json` `expo.ios.bundleIdentifier` + `expo.android.package` before submitting to App Store / Play Store.
|
|
57
61
|
|
|
58
62
|
## Expo SDK compatibility
|
|
59
63
|
|
package/dist/index.js
CHANGED
|
@@ -107,10 +107,13 @@ async function main() {
|
|
|
107
107
|
log.success(`Project ready at ${target.dir}`);
|
|
108
108
|
log.raw("");
|
|
109
109
|
log.raw(` cd ${path.relative(process.cwd(), target.dir) || target.name}`);
|
|
110
|
-
log.raw(`
|
|
111
|
-
log.raw(` ${cmdPm}
|
|
110
|
+
log.raw(` ${cmdPm} ios # one-time: builds + installs custom dev-client + launches`);
|
|
111
|
+
log.raw(` ${cmdPm} android # same for android (needs emulator/device + Android SDK)`);
|
|
112
112
|
log.raw("");
|
|
113
|
-
log.
|
|
113
|
+
log.raw(` ${cmdPm} start # subsequent runs: faster — connects to installed dev-client`);
|
|
114
|
+
log.raw("");
|
|
115
|
+
log.info("First run takes 3-10 min (native build). After that, `start` is fast. " +
|
|
116
|
+
"iOS needs Xcode + CocoaPods; Android needs SDK + emulator. Not Expo-Go-compatible.");
|
|
114
117
|
}
|
|
115
118
|
main().catch((err) => {
|
|
116
119
|
log.error(err instanceof Error ? err.message : String(err));
|
package/dist/patch.js
CHANGED
|
@@ -19,6 +19,28 @@ export function slugify(name) {
|
|
|
19
19
|
.replace(/^-+|-+$/g, "")
|
|
20
20
|
.toLowerCase() || "app");
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Build a reverse-DNS bundle identifier segment from the app name.
|
|
24
|
+
*
|
|
25
|
+
* Android `package` + iOS `bundleIdentifier` constraints:
|
|
26
|
+
* - Lowercase letters / digits only per segment.
|
|
27
|
+
* - No dashes, dots, or other punctuation INSIDE a segment.
|
|
28
|
+
* - Each segment must START with a letter (no leading digits).
|
|
29
|
+
* - At least two segments separated by dots.
|
|
30
|
+
*
|
|
31
|
+
* `my-test-app` → `mytestapp` → final ID `com.codingpixel.mytestapp`.
|
|
32
|
+
* `1pp` → `app1pp` (leading-digit guard).
|
|
33
|
+
*/
|
|
34
|
+
export function bundleIdSegment(name) {
|
|
35
|
+
let seg = slugify(name).replace(/-/g, "");
|
|
36
|
+
if (!/^[a-z]/.test(seg))
|
|
37
|
+
seg = `app${seg}`;
|
|
38
|
+
return seg || "app";
|
|
39
|
+
}
|
|
40
|
+
/** Compose the full bundle identifier — `com.<safeName>` (no app-author namespace). */
|
|
41
|
+
export function bundleIdFor(name) {
|
|
42
|
+
return `com.${bundleIdSegment(name)}`;
|
|
43
|
+
}
|
|
22
44
|
function readJson(p) {
|
|
23
45
|
return JSON.parse(fs.readFileSync(p, "utf8"));
|
|
24
46
|
}
|
|
@@ -46,6 +68,16 @@ export function patchAppJson(target, name, _answers) {
|
|
|
46
68
|
json.expo.name = name;
|
|
47
69
|
json.expo.slug = slugify(name);
|
|
48
70
|
json.expo.scheme = slugify(name);
|
|
71
|
+
// android.package + ios.bundleIdentifier — required to launch the app on
|
|
72
|
+
// either platform (Expo CLI errors with "Required property ... is not found"
|
|
73
|
+
// otherwise). Preserve any user-set value; only fill when missing.
|
|
74
|
+
const bundleId = bundleIdFor(name);
|
|
75
|
+
json.expo.ios ??= {};
|
|
76
|
+
if (!json.expo.ios.bundleIdentifier)
|
|
77
|
+
json.expo.ios.bundleIdentifier = bundleId;
|
|
78
|
+
json.expo.android ??= {};
|
|
79
|
+
if (!json.expo.android.package)
|
|
80
|
+
json.expo.android.package = bundleId;
|
|
49
81
|
json.expo.plugins ??= [];
|
|
50
82
|
if (!json.expo.plugins.some((e) => nameOf(e) === "expo-router")) {
|
|
51
83
|
json.expo.plugins.push("expo-router");
|
|
@@ -67,6 +99,9 @@ export function patchAppJsonPlugins(target, answers) {
|
|
|
67
99
|
"expo-image-picker",
|
|
68
100
|
{
|
|
69
101
|
photosPermission: "The app accesses your photos to let you share them with your friends.",
|
|
102
|
+
cameraPermission: "The app accesses your camera to let you take photos to share.",
|
|
103
|
+
// microphone optional — only set if app records video. Add manually:
|
|
104
|
+
// "microphonePermission": "...",
|
|
70
105
|
},
|
|
71
106
|
];
|
|
72
107
|
if (!json.expo.plugins.some((e) => nameOf(e) === nameOf(entry))) {
|
|
@@ -129,17 +164,36 @@ export function patchPackageJsonScripts(target) {
|
|
|
129
164
|
const p = path.join(target, "package.json");
|
|
130
165
|
const pkg = readJson(p);
|
|
131
166
|
pkg.scripts ??= {};
|
|
167
|
+
// Use `run:android` / `run:ios` so first invocation builds + installs the
|
|
168
|
+
// custom dev-client (we ship `expo-dev-client` + `react-native-mmkv`, both
|
|
169
|
+
// Expo-Go-incompatible). `expo start --android` would error with "No
|
|
170
|
+
// development build for this project is installed" otherwise.
|
|
171
|
+
// `start` uses `--dev-client` so subsequent runs (after first build)
|
|
172
|
+
// connect to the installed client correctly.
|
|
132
173
|
const want = {
|
|
133
|
-
start: "expo start",
|
|
134
|
-
android: "expo
|
|
135
|
-
ios: "expo
|
|
174
|
+
start: "expo start --dev-client",
|
|
175
|
+
android: "expo run:android",
|
|
176
|
+
ios: "expo run:ios",
|
|
136
177
|
web: "expo start --web",
|
|
137
178
|
lint: "expo lint",
|
|
179
|
+
prebuild: "expo prebuild",
|
|
138
180
|
};
|
|
139
181
|
for (const [k, v] of Object.entries(want)) {
|
|
140
182
|
if (!(k in pkg.scripts))
|
|
141
183
|
pkg.scripts[k] = v;
|
|
142
184
|
}
|
|
185
|
+
// Force-update if upstream defaults from create-expo-app are present (so
|
|
186
|
+
// existing scaffolds re-running the CLI also get the corrected scripts).
|
|
187
|
+
const stale = {
|
|
188
|
+
"expo start": "expo start --dev-client",
|
|
189
|
+
"expo start --android": "expo run:android",
|
|
190
|
+
"expo start --ios": "expo run:ios",
|
|
191
|
+
};
|
|
192
|
+
for (const [scriptName, current] of Object.entries(pkg.scripts)) {
|
|
193
|
+
if (scriptName in want && stale[current] === want[scriptName]) {
|
|
194
|
+
pkg.scripts[scriptName] = want[scriptName];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
143
197
|
writeJson(p, pkg);
|
|
144
198
|
}
|
|
145
199
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
// react-native-mmkv 3.x+ exports `MMKV` as a class (was `createMMKV()` factory
|
|
2
|
+
// in older versions). MyRoster's source predates the API change.
|
|
3
|
+
import { MMKV } from "react-native-mmkv";
|
|
2
4
|
|
|
3
|
-
const storage =
|
|
5
|
+
const storage = new MMKV();
|
|
4
6
|
|
|
5
7
|
export const reduxStorage = {
|
|
6
8
|
setItem: (key: string, value: string) => {
|
|
@@ -12,7 +14,7 @@ export const reduxStorage = {
|
|
|
12
14
|
return Promise.resolve(value);
|
|
13
15
|
},
|
|
14
16
|
removeItem: (key: string) => {
|
|
15
|
-
storage.
|
|
17
|
+
storage.delete(key);
|
|
16
18
|
return Promise.resolve();
|
|
17
19
|
},
|
|
18
20
|
};
|
|
@@ -1,24 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
let customInsets = { ...insets };
|
|
11
|
-
if (ANDROID) {
|
|
12
|
-
customInsets = { ...insets, bottom: insets?.bottom + RF(12) };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<SafeAreaInsetsContext.Provider value={customInsets}>
|
|
17
|
-
{children}
|
|
18
|
-
</SafeAreaInsetsContext.Provider>
|
|
19
|
-
);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const useSafeArea = () => {
|
|
23
|
-
return useContext(SafeAreaInsetsContext);
|
|
24
|
-
};
|
|
1
|
+
// V5 plan dropped MyRoster's `SafeAreaInsetsProvider` (Android-padding tweak)
|
|
2
|
+
// from `_layout.tsx` — relying on stock `react-native-safe-area-context` is
|
|
3
|
+
// cleaner and avoids a context that was never wrapped around the tree.
|
|
4
|
+
//
|
|
5
|
+
// `useSafeArea` is preserved as a thin re-export so existing component code
|
|
6
|
+
// (AppWrapper, AppButton, etc.) keeps compiling without edits.
|
|
7
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
8
|
+
|
|
9
|
+
export const useSafeArea = useSafeAreaInsets;
|