waypoint-expo-template 3.0.0 → 4.0.0

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/AGENTS.md CHANGED
@@ -1,3 +1,3 @@
1
1
  # Expo HAS CHANGED
2
2
 
3
- Read the exact versioned docs at https://docs.expo.dev/versions/v57.0.0/ before writing any code.
3
+ Read the exact versioned docs at https://docs.expo.dev/versions/v58.0.0/ before writing any code.
@@ -0,0 +1,13 @@
1
+ import { Stack } from 'expo-router';
2
+ import { StatusBar } from 'expo-status-bar';
3
+
4
+ // The root layout: every route renders inside this stack (waypoint ADR-039
5
+ // amendment §1 — every screen is a route, every navigation is a link).
6
+ export default function RootLayout() {
7
+ return (
8
+ <>
9
+ <Stack />
10
+ <StatusBar style="auto" />
11
+ </>
12
+ );
13
+ }
@@ -1,11 +1,9 @@
1
- import { StatusBar } from 'expo-status-bar';
2
1
  import { StyleSheet, Text, View } from 'react-native';
3
2
 
4
- export default function App() {
3
+ export default function Home() {
5
4
  return (
6
5
  <View style={styles.container}>
7
- <Text>Open up App.tsx to start working on your app!</Text>
8
- <StatusBar style="auto" />
6
+ <Text>Open up app/index.tsx to start working on your app!</Text>
9
7
  </View>
10
8
  );
11
9
  }
package/app.json CHANGED
@@ -19,7 +19,16 @@
19
19
  "predictiveBackGestureEnabled": false
20
20
  },
21
21
  "web": {
22
+ "bundler": "metro",
23
+ "output": "single",
22
24
  "favicon": "./assets/favicon.png"
25
+ },
26
+ "scheme": "waypointapp",
27
+ "plugins": [
28
+ "expo-router"
29
+ ],
30
+ "experiments": {
31
+ "typedRoutes": true
23
32
  }
24
33
  }
25
34
  }
package/package.json CHANGED
@@ -1,22 +1,33 @@
1
1
  {
2
2
  "name": "waypoint-expo-template",
3
- "version": "3.0.0",
4
- "main": "index.ts",
3
+ "version": "4.0.0",
4
+ "main": "expo-router/entry",
5
5
  "dependencies": {
6
- "expo": "~57.0.21",
7
- "expo-status-bar": "~57.0.1",
8
- "react": "19.2.3",
9
- "react-native": "0.86.3"
6
+ "expo": "~58.0.16",
7
+ "expo-constants": "~58.0.12",
8
+ "expo-linking": "~58.0.13",
9
+ "expo-router": "~58.0.24",
10
+ "expo-status-bar": "~58.0.3",
11
+ "react": "19.3.0",
12
+ "react-dom": "19.3.0",
13
+ "react-native": "0.88.0",
14
+ "react-native-safe-area-context": "~5.10.1",
15
+ "react-native-screens": "~4.30.0",
16
+ "react-native-web": "~0.21.4"
10
17
  },
11
18
  "devDependencies": {
12
- "@types/react": "~19.2.2",
19
+ "@types/react": "~19.3.0",
13
20
  "typescript": "~6.0.3"
14
21
  },
15
22
  "scripts": {
16
23
  "start": "expo start",
17
24
  "android": "expo start --android",
18
25
  "ios": "expo start --ios",
19
- "web": "expo start --web"
26
+ "web": "expo start --web",
27
+ "pwa:enable": "node scripts/enable-pwa.mjs",
28
+ "build:web": "expo export -p web && npx workbox-cli generateSW workbox-config.js",
29
+ "pwa:icons": "node scripts/enable-pwa.mjs",
30
+ "typecheck": "expo customize tsconfig.json && tsc --noEmit"
20
31
  },
21
- "description": "Expo app template for Waypoint app previews \u2014 create-expo-app blank-typescript, unmodified."
32
+ "description": "Expo app template for Waypoint app previews \u2014 create-expo-app blank-typescript (Expo SDK 58) with Expo Router: every screen a route, typed routes on."
22
33
  }
@@ -0,0 +1,58 @@
1
+ <!DOCTYPE html>
2
+ <html lang="%LANG_ISO_CODE%">
3
+
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
8
+ <title>%WEB_TITLE%</title>
9
+ <!-- The `react-native-web` recommended style reset: https://necolas.github.io/react-native-web/docs/setup/#root-element -->
10
+ <style id="expo-reset">
11
+ /* These styles make the body full-height */
12
+ html,
13
+ body {
14
+ height: 100%;
15
+ }
16
+
17
+ /* These styles disable body scrolling if you are using <ScrollView> */
18
+ body {
19
+ overflow: hidden;
20
+ }
21
+
22
+ /* These styles make the root element full-height */
23
+ #root {
24
+ display: flex;
25
+ height: 100%;
26
+ flex: 1;
27
+ }
28
+ </style>
29
+ <link rel="manifest" href="/manifest.json" />
30
+ <meta name="theme-color" content="#000000" />
31
+ <script>
32
+ if ("serviceWorker" in navigator) {
33
+ window.addEventListener("load", () => {
34
+ fetch("/sw.js", { method: "HEAD" })
35
+ .then((res) => {
36
+ const contentType = res.headers.get("content-type");
37
+ if (res.status === 200 && contentType && contentType.includes("javascript")) {
38
+ navigator.serviceWorker.register("/sw.js")
39
+ .then((r) => console.log("SW registered:", r.scope))
40
+ .catch((e) => console.error("SW failed:", e));
41
+ }
42
+ })
43
+ .catch(() => { });
44
+ });
45
+ }
46
+ </script>
47
+ </head>
48
+
49
+ <body>
50
+ <!-- Use static rendering with Expo Router to support running without JavaScript. -->
51
+ <noscript>
52
+ You need to enable JavaScript to run this app.
53
+ </noscript>
54
+ <!-- The root element for your Expo app. -->
55
+ <div id="root"></div>
56
+ </body>
57
+
58
+ </html>
Binary file
Binary file
@@ -0,0 +1,27 @@
1
+ {
2
+ "short_name": "waypoint-exp",
3
+ "name": "waypoint-expo-template",
4
+ "icons": [
5
+ {
6
+ "src": "favicon.ico",
7
+ "sizes": "64x64 32x32 24x24 16x16",
8
+ "type": "image/x-icon"
9
+ },
10
+ {
11
+ "src": "logo192.png",
12
+ "type": "image/png",
13
+ "sizes": "192x192",
14
+ "purpose": "any maskable"
15
+ },
16
+ {
17
+ "src": "logo512.png",
18
+ "type": "image/png",
19
+ "sizes": "512x512",
20
+ "purpose": "any maskable"
21
+ }
22
+ ],
23
+ "start_url": ".",
24
+ "display": "standalone",
25
+ "theme_color": "#000000",
26
+ "background_color": "#ffffff"
27
+ }
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * enable-pwa.mjs — Scaffolds full PWA support into any Expo project.
5
+ *
6
+ * Reads app.json, generates icons, creates all PWA files from templates.
7
+ * Idempotent: safe to re-run, skips existing files.
8
+ *
9
+ * Usage: node scripts/enable-pwa.mjs
10
+ * npm run build:web
11
+ */
12
+
13
+ import { resolve, dirname } from "path";
14
+ import {
15
+ existsSync,
16
+ mkdirSync,
17
+ readFileSync,
18
+ writeFileSync,
19
+ unlinkSync,
20
+ } from "fs";
21
+ import { fileURLToPath } from "url";
22
+ import { execSync } from "child_process";
23
+
24
+ const __dirname = dirname(fileURLToPath(import.meta.url));
25
+ const ROOT = resolve(__dirname, "..");
26
+
27
+ const log = (m) => console.log(`[enable-pwa] ${m}`);
28
+ const warn = (m) => console.warn(`[enable-pwa] WARNING: ${m}`);
29
+ const error = (m) => {
30
+ console.error(`[enable-pwa] ERROR: ${m}`);
31
+ process.exit(1);
32
+ };
33
+
34
+ // ── Step 1: Read app.json ───────────────────────────────────────────────────
35
+
36
+ const appJsonPath = resolve(ROOT, "app.json");
37
+ if (!existsSync(appJsonPath)) error(`app.json not found at ${appJsonPath}`);
38
+
39
+ const appJson = JSON.parse(readFileSync(appJsonPath, "utf-8"));
40
+ const expo = appJson.expo;
41
+ if (!expo) error("No expo config found in app.json");
42
+
43
+ const name = expo.name || expo.slug || "Expo App";
44
+ const shortName =
45
+ expo.shortName || (name.length > 12 ? name.slice(0, 12) : name);
46
+ const display = expo.web?.display || "standalone";
47
+ const themeColor = expo.web?.themeColor || "#000000";
48
+ const bgColor = expo.web?.backgroundColor || "#ffffff";
49
+
50
+ log(`Project: ${name}`);
51
+
52
+ // ── Step 2: Find source icon ────────────────────────────────────────────────
53
+
54
+ const iconCandidates = [
55
+ expo.icon,
56
+ expo.android?.adaptiveIcon?.foregroundImage,
57
+ "assets/icon.png",
58
+ "assets/icon.jpg",
59
+ ].filter(Boolean);
60
+
61
+ let iconPath = null;
62
+ for (const c of iconCandidates) {
63
+ const p = resolve(ROOT, c);
64
+ if (existsSync(p)) {
65
+ iconPath = p;
66
+ break;
67
+ }
68
+ }
69
+ if (!iconPath)
70
+ error(
71
+ `No icon found. Checked: ${iconCandidates.join(", ")}\n` +
72
+ "Set expo.icon in app.json or place an icon at assets/icon.png"
73
+ );
74
+ log(`Source icon: ${iconPath}`);
75
+
76
+ // ── Step 3: Generate PWA icons (192, 512) ───────────────────────────────────
77
+
78
+ let Jimp;
79
+ try {
80
+ Jimp = (await import("jimp-compact")).default;
81
+ } catch {
82
+ error("jimp-compact not found. Run from an Expo project with it installed.");
83
+ }
84
+
85
+ const publicDir = resolve(ROOT, "public");
86
+ mkdirSync(publicDir, { recursive: true });
87
+
88
+ const img = await Jimp.read(iconPath);
89
+ for (const size of [192, 512]) {
90
+ const out = resolve(publicDir, `logo${size}.png`);
91
+ await new Promise((res, rej) => {
92
+ img
93
+ .clone()
94
+ .resize(size, size)
95
+ .write(out, (err) => (err ? rej(err) : res()));
96
+ });
97
+ log(`Generated logo${size}.png`);
98
+ }
99
+
100
+ // ── Step 4: Create public/manifest.json ─────────────────────────────────────
101
+
102
+ const manifestPath = resolve(publicDir, "manifest.json");
103
+ if (!existsSync(manifestPath)) {
104
+ const manifest = {
105
+ short_name: shortName,
106
+ name,
107
+ icons: [
108
+ { src: "favicon.ico", sizes: "64x64 32x32 24x24 16x16", type: "image/x-icon" },
109
+ { src: "logo192.png", type: "image/png", sizes: "192x192", purpose: "any maskable" },
110
+ { src: "logo512.png", type: "image/png", sizes: "512x512", purpose: "any maskable" },
111
+ ],
112
+ start_url: ".",
113
+ display,
114
+ theme_color: themeColor,
115
+ background_color: bgColor,
116
+ };
117
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
118
+ log("Created public/manifest.json");
119
+ } else {
120
+ log("manifest.json exists, skipping");
121
+ }
122
+
123
+ // ── Step 5: Create public/index.html ────────────────────────────────────────
124
+
125
+ const htmlPath = resolve(publicDir, "index.html");
126
+
127
+ if (existsSync(htmlPath)) {
128
+ // Check if already PWA-enabled
129
+ const existing = readFileSync(htmlPath, "utf-8");
130
+ if (existing.includes('rel="manifest"')) {
131
+ log("index.html already PWA-enabled, skipping");
132
+ } else {
133
+ // Inject PWA tags before </head>
134
+ const injection = [
135
+ `<link rel="manifest" href="/manifest.json" />`,
136
+ `<meta name="theme-color" content="${themeColor}" />`,
137
+ `<script>`,
138
+ ` if ("serviceWorker" in navigator) {`,
139
+ ` window.addEventListener("load", () => {`,
140
+ ` fetch("/sw.js", { method: "HEAD" })`,
141
+ ` .then((res) => {`,
142
+ ` const contentType = res.headers.get("content-type");`,
143
+ ` if (res.status === 200 && contentType && contentType.includes("javascript")) {`,
144
+ ` navigator.serviceWorker.register("/sw.js")`,
145
+ ` .then((r) => console.log("SW registered:", r.scope))`,
146
+ ` .catch((e) => console.error("SW failed:", e));`,
147
+ ` }`,
148
+ ` })`,
149
+ ` .catch(() => {});`,
150
+ ` });`,
151
+ ` }`,
152
+ `</script>`,
153
+ ].join("\n");
154
+ writeFileSync(
155
+ htmlPath,
156
+ existing.replace("</head>", `${injection}\n </head>`)
157
+ );
158
+ log("Injected PWA tags into existing index.html");
159
+ }
160
+ } else {
161
+ // Generate via expo customize, then inject PWA tags
162
+ log("Generating index.html via expo customize...");
163
+ try {
164
+ execSync("npx expo customize public/index.html", {
165
+ cwd: ROOT,
166
+ stdio: "pipe",
167
+ timeout: 30000,
168
+ });
169
+ } catch {
170
+ warn("expo customize failed, creating minimal index.html");
171
+ writeFileSync(
172
+ htmlPath,
173
+ `<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1" />\n <title>${name}</title>\n</head>\n<body>\n <div id="root"></div>\n</body>\n</html>\n`
174
+ );
175
+ }
176
+
177
+ // Now inject PWA tags
178
+ const html = readFileSync(htmlPath, "utf-8");
179
+ if (!html.includes('rel="manifest"')) {
180
+ const injection = [
181
+ `<link rel="manifest" href="/manifest.json" />`,
182
+ `<meta name="theme-color" content="${themeColor}" />`,
183
+ `<script>`,
184
+ ` if ("serviceWorker" in navigator) {`,
185
+ ` window.addEventListener("load", () => {`,
186
+ ` fetch("/sw.js", { method: "HEAD" })`,
187
+ ` .then((res) => {`,
188
+ ` const contentType = res.headers.get("content-type");`,
189
+ ` if (res.status === 200 && contentType && contentType.includes("javascript")) {`,
190
+ ` navigator.serviceWorker.register("/sw.js")`,
191
+ ` .then((r) => console.log("SW registered:", r.scope))`,
192
+ ` .catch((e) => console.error("SW failed:", e));`,
193
+ ` }`,
194
+ ` })`,
195
+ ` .catch(() => {});`,
196
+ ` });`,
197
+ ` }`,
198
+ `</script>`,
199
+ ].join("\n");
200
+ writeFileSync(
201
+ htmlPath,
202
+ html.replace("</head>", `${injection}\n </head>`)
203
+ );
204
+ }
205
+ log("Created public/index.html with PWA support");
206
+ }
207
+
208
+ // ── Step 6: Create workbox-config.js ────────────────────────────────────────
209
+
210
+ const wbPath = resolve(ROOT, "workbox-config.js");
211
+ if (!existsSync(wbPath)) {
212
+ writeFileSync(
213
+ wbPath,
214
+ `// Workbox config — generated by enable-pwa.mjs
215
+ module.exports = {
216
+ globDirectory: "dist",
217
+ globPatterns: ["**/*.{js,html,ttf,ico,json,png,woff,woff2}"],
218
+ swDest: "dist/sw.js",
219
+ // SPA: serve index.html for any navigation not matching a precached file.
220
+ navigateFallback: "/index.html",
221
+ navigateFallbackDenylist: [
222
+ /^\\/_expo\\//, // JS bundles
223
+ /^\\/manifest\\.json$/,
224
+ /^\\/sw\\.js$/,
225
+ ],
226
+ cleanupOutdatedCaches: true,
227
+ runtimeCaching: [
228
+ {
229
+ urlPattern: /^https?.*/,
230
+ handler: "NetworkFirst",
231
+ options: {
232
+ cacheName: "offlineCache",
233
+ expiration: { maxEntries: 200, maxAgeSeconds: 86400 },
234
+ },
235
+ },
236
+ ],
237
+ };
238
+ `
239
+ );
240
+ log("Created workbox-config.js");
241
+ } else {
242
+ log("workbox-config.js exists, skipping");
243
+ }
244
+
245
+ // ── Step 7: Update package.json ─────────────────────────────────────────────
246
+
247
+ const pkgPath = resolve(ROOT, "package.json");
248
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
249
+ pkg.scripts = pkg.scripts || {};
250
+
251
+ const scripts = {
252
+ "pwa:enable": "node scripts/enable-pwa.mjs",
253
+ "build:web": "npm run pwa:icons && expo export -p web && npx workbox-cli generateSW workbox-config.js",
254
+ "pwa:icons": "node scripts/enable-pwa.mjs",
255
+ };
256
+
257
+ let changed = false;
258
+ for (const [k, v] of Object.entries(scripts)) {
259
+ if (!pkg.scripts[k]) {
260
+ pkg.scripts[k] = v;
261
+ changed = true;
262
+ }
263
+ }
264
+ if (changed) {
265
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
266
+ log("Updated package.json scripts");
267
+ }
268
+
269
+ // ── Done ────────────────────────────────────────────────────────────────────
270
+
271
+ log("PWA support enabled!\n");
272
+ console.log(" npm run build:web # Build with service worker");
273
+ console.log(" npx expo start --web # Dev server with PWA");
package/tsconfig.json CHANGED
@@ -2,5 +2,14 @@
2
2
  "extends": "expo/tsconfig.base",
3
3
  "compilerOptions": {
4
4
  "strict": true
5
- }
5
+ },
6
+ "include": [
7
+ "**/*.ts",
8
+ "**/*.tsx",
9
+ ".expo/types/**/*.ts",
10
+ "expo-env.d.ts"
11
+ ],
12
+ "exclude": [
13
+ "node_modules"
14
+ ]
6
15
  }
@@ -0,0 +1,24 @@
1
+ // Workbox config — generated by enable-pwa.mjs
2
+ module.exports = {
3
+ globDirectory: "dist",
4
+ globPatterns: ["**/*.{js,html,ttf,ico,json,png,woff,woff2}"],
5
+ swDest: "dist/sw.js",
6
+ // SPA: serve index.html for any navigation not matching a precached file.
7
+ navigateFallback: "/index.html",
8
+ navigateFallbackDenylist: [
9
+ /^\/_expo\//, // JS bundles
10
+ /^\/manifest\.json$/,
11
+ /^\/sw\.js$/,
12
+ ],
13
+ cleanupOutdatedCaches: true,
14
+ runtimeCaching: [
15
+ {
16
+ urlPattern: /^https?.*/,
17
+ handler: "NetworkFirst",
18
+ options: {
19
+ cacheName: "offlineCache",
20
+ expiration: { maxEntries: 200, maxAgeSeconds: 86400 },
21
+ },
22
+ },
23
+ ],
24
+ };
package/index.ts DELETED
@@ -1,8 +0,0 @@
1
- import { registerRootComponent } from 'expo';
2
-
3
- import App from './App';
4
-
5
- // registerRootComponent calls AppRegistry.registerComponent('main', () => App);
6
- // It also ensures that whether you load the app in Expo Go or in a native build,
7
- // the environment is set up appropriately
8
- registerRootComponent(App);