@ramonclaudio/create-vexpo 0.1.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/README.md +50 -0
- package/dist/index.js +183 -0
- package/dist/templates/default/.eas/workflows/asc-events.yml +84 -0
- package/dist/templates/default/.eas/workflows/deploy-production.yml +129 -0
- package/dist/templates/default/.eas/workflows/development-builds.yml +19 -0
- package/dist/templates/default/.eas/workflows/e2e-tests.yml +42 -0
- package/dist/templates/default/.eas/workflows/pr-preview.yml +98 -0
- package/dist/templates/default/.eas/workflows/release.yml +44 -0
- package/dist/templates/default/.eas/workflows/rollback.yml +86 -0
- package/dist/templates/default/.eas/workflows/rollout.yml +84 -0
- package/dist/templates/default/.eas/workflows/rotate-apple-jwt.yml +42 -0
- package/dist/templates/default/.eas/workflows/testflight.yml +57 -0
- package/dist/templates/default/.github/workflows/check.yml +28 -0
- package/dist/templates/default/.maestro/launch.yaml +18 -0
- package/dist/templates/default/AGENTS.md +79 -0
- package/dist/templates/default/DESIGN.md +331 -0
- package/dist/templates/default/LICENSE +21 -0
- package/dist/templates/default/README.md +153 -0
- package/dist/templates/default/SETUP.md +618 -0
- package/dist/templates/default/__tests__/convex/constants.test.ts +49 -0
- package/dist/templates/default/__tests__/convex/validators.test.ts +23 -0
- package/dist/templates/default/__tests__/convex/webhook.test.ts +343 -0
- package/dist/templates/default/__tests__/lib/deep-link.test.ts +67 -0
- package/dist/templates/default/_easignore +22 -0
- package/dist/templates/default/_editorconfig +9 -0
- package/dist/templates/default/_env.example +34 -0
- package/dist/templates/default/_fingerprintignore +24 -0
- package/dist/templates/default/_gitattributes +7 -0
- package/dist/templates/default/_gitignore +69 -0
- package/dist/templates/default/_oxfmtrc.json +3 -0
- package/dist/templates/default/_oxlintrc.json +34 -0
- package/dist/templates/default/app/(app)/(tabs)/(home)/index.tsx +50 -0
- package/dist/templates/default/app/(app)/(tabs)/(home,search)/_layout.tsx +44 -0
- package/dist/templates/default/app/(app)/(tabs)/(search)/index.tsx +247 -0
- package/dist/templates/default/app/(app)/(tabs)/_layout.tsx +77 -0
- package/dist/templates/default/app/(app)/(tabs)/settings/_layout.tsx +37 -0
- package/dist/templates/default/app/(app)/(tabs)/settings/index.tsx +362 -0
- package/dist/templates/default/app/(app)/(tabs)/settings/preferences.tsx +184 -0
- package/dist/templates/default/app/(app)/_layout.tsx +73 -0
- package/dist/templates/default/app/(app)/debug.tsx +389 -0
- package/dist/templates/default/app/(app)/help.tsx +254 -0
- package/dist/templates/default/app/(app)/linked.tsx +116 -0
- package/dist/templates/default/app/(app)/privacy.tsx +159 -0
- package/dist/templates/default/app/(app)/profile.tsx +915 -0
- package/dist/templates/default/app/(app)/sessions.tsx +191 -0
- package/dist/templates/default/app/(app)/welcome.tsx +140 -0
- package/dist/templates/default/app/(auth)/_layout.tsx +31 -0
- package/dist/templates/default/app/(auth)/forgot-password.tsx +168 -0
- package/dist/templates/default/app/(auth)/reset-password.tsx +314 -0
- package/dist/templates/default/app/(auth)/sign-in.tsx +453 -0
- package/dist/templates/default/app/(auth)/sign-up.tsx +563 -0
- package/dist/templates/default/app/+native-intent.tsx +14 -0
- package/dist/templates/default/app/+not-found.tsx +51 -0
- package/dist/templates/default/app/_layout.tsx +102 -0
- package/dist/templates/default/app-store/screenshots/.gitkeep +0 -0
- package/dist/templates/default/app-store/screenshots/README.md +13 -0
- package/dist/templates/default/app.config.ts +201 -0
- package/dist/templates/default/app.json +11 -0
- package/dist/templates/default/assets/brand-icon-dark.png +0 -0
- package/dist/templates/default/assets/brand-icon-light.png +0 -0
- package/dist/templates/default/assets/fonts/Geist-Black.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-BlackItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Bold.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-BoldItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ExtraBold.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ExtraBoldItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ExtraLight.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ExtraLightItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Italic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Light.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-LightItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Medium.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-MediumItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Regular.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-SemiBold.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-SemiBoldItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Thin.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ThinItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Variable-Italic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Variable.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-Bold.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-BoldItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-Italic.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-Medium.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-MediumItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-Regular.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistPixel-Square.ttf +0 -0
- package/dist/templates/default/assets/icon.png +0 -0
- package/dist/templates/default/assets/sounds/notification.wav +0 -0
- package/dist/templates/default/assets/splash-image-dark.png +0 -0
- package/dist/templates/default/assets/splash-image-light.png +0 -0
- package/dist/templates/default/bun.lock +1860 -0
- package/dist/templates/default/components/auth/otp-verification.tsx +255 -0
- package/dist/templates/default/components/auth/password-field.tsx +121 -0
- package/dist/templates/default/components/auth/segmented-toggle.tsx +47 -0
- package/dist/templates/default/components/ui/convex-error.tsx +32 -0
- package/dist/templates/default/components/ui/error-boundary.tsx +57 -0
- package/dist/templates/default/components/ui/loading-screen.tsx +31 -0
- package/dist/templates/default/components/ui/material.tsx +94 -0
- package/dist/templates/default/components/ui/offline-banner.tsx +58 -0
- package/dist/templates/default/components/ui/prominent-button.tsx +71 -0
- package/dist/templates/default/components/ui/skeleton.tsx +107 -0
- package/dist/templates/default/components/ui/status-text.tsx +49 -0
- package/dist/templates/default/components/ui/update-banner.tsx +82 -0
- package/dist/templates/default/constants/layout.ts +102 -0
- package/dist/templates/default/constants/theme.ts +401 -0
- package/dist/templates/default/constants/ui.ts +77 -0
- package/dist/templates/default/convex/_generated/api.d.ts +77 -0
- package/dist/templates/default/convex/_generated/api.js +23 -0
- package/dist/templates/default/convex/_generated/dataModel.d.ts +60 -0
- package/dist/templates/default/convex/_generated/server.d.ts +143 -0
- package/dist/templates/default/convex/_generated/server.js +93 -0
- package/dist/templates/default/convex/admin.ts +102 -0
- package/dist/templates/default/convex/auth.config.ts +6 -0
- package/dist/templates/default/convex/auth.ts +335 -0
- package/dist/templates/default/convex/constants.ts +46 -0
- package/dist/templates/default/convex/convex.config.ts +11 -0
- package/dist/templates/default/convex/crons.ts +42 -0
- package/dist/templates/default/convex/email.ts +109 -0
- package/dist/templates/default/convex/env.ts +31 -0
- package/dist/templates/default/convex/errors.ts +33 -0
- package/dist/templates/default/convex/functions.ts +54 -0
- package/dist/templates/default/convex/http.ts +176 -0
- package/dist/templates/default/convex/log.ts +81 -0
- package/dist/templates/default/convex/pushTokens.ts +114 -0
- package/dist/templates/default/convex/rateLimit.ts +92 -0
- package/dist/templates/default/convex/schema.ts +28 -0
- package/dist/templates/default/convex/tsconfig.json +18 -0
- package/dist/templates/default/convex/users.ts +279 -0
- package/dist/templates/default/convex/validators.ts +74 -0
- package/dist/templates/default/convex/webhook.ts +193 -0
- package/dist/templates/default/convex.json +6 -0
- package/dist/templates/default/eas.json +56 -0
- package/dist/templates/default/fingerprint.config.js +9 -0
- package/dist/templates/default/hooks/use-debounce.ts +20 -0
- package/dist/templates/default/hooks/use-deep-link.ts +43 -0
- package/dist/templates/default/hooks/use-navigation-tracking.ts +15 -0
- package/dist/templates/default/hooks/use-network.ts +11 -0
- package/dist/templates/default/hooks/use-notifications.ts +107 -0
- package/dist/templates/default/hooks/use-onboarding.ts +15 -0
- package/dist/templates/default/hooks/use-reduced-motion.ts +11 -0
- package/dist/templates/default/hooks/use-theme.ts +53 -0
- package/dist/templates/default/hooks/use-updates.ts +86 -0
- package/dist/templates/default/lib/a11y.ts +5 -0
- package/dist/templates/default/lib/app.ts +14 -0
- package/dist/templates/default/lib/assets.ts +17 -0
- package/dist/templates/default/lib/auth-client.ts +21 -0
- package/dist/templates/default/lib/convex-auth.tsx +79 -0
- package/dist/templates/default/lib/deep-link.ts +71 -0
- package/dist/templates/default/lib/dev-menu.ts +119 -0
- package/dist/templates/default/lib/device.ts +40 -0
- package/dist/templates/default/lib/dynamic-font.ts +49 -0
- package/dist/templates/default/lib/env.ts +10 -0
- package/dist/templates/default/lib/haptics.ts +24 -0
- package/dist/templates/default/lib/notifications.ts +276 -0
- package/dist/templates/default/lib/preferences.ts +45 -0
- package/dist/templates/default/lib/schemas.ts +137 -0
- package/dist/templates/default/lib/storage.ts +47 -0
- package/dist/templates/default/lib/updates.ts +107 -0
- package/dist/templates/default/metro.config.js +14 -0
- package/dist/templates/default/package.json +129 -0
- package/dist/templates/default/patches/PR-368.patch +91 -0
- package/dist/templates/default/patches/convex-dev-better-auth-0.12.2.tgz +0 -0
- package/dist/templates/default/plugins/README.md +9 -0
- package/dist/templates/default/plugins/with-auto-signing.js +45 -0
- package/dist/templates/default/plugins/with-pod-deployment-target.js +35 -0
- package/dist/templates/default/scripts/README.md +36 -0
- package/dist/templates/default/scripts/_run.mjs +77 -0
- package/dist/templates/default/scripts/clean.ts +543 -0
- package/dist/templates/default/scripts/rotate-apple-jwt.mjs +80 -0
- package/dist/templates/default/store.config.json +58 -0
- package/dist/templates/default/tsconfig.json +13 -0
- package/dist/templates/default/vitest.config.ts +21 -0
- package/package.json +69 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# scripts
|
|
2
|
+
|
|
3
|
+
Build and maintenance scripts. Setup orchestration lives in the published `vexpo` CLI (run via `bunx vexpo`), not here.
|
|
4
|
+
|
|
5
|
+
## What's in this directory
|
|
6
|
+
|
|
7
|
+
| Script | What it does |
|
|
8
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| `clean.ts` | Trash + reinstall. `--metro` for cache-only nuke (Metro/Babel/Haste). `--state` also wipes `.setup-state.json`. |
|
|
10
|
+
| `rotate-apple-jwt.mjs` | Re-signs the Apple Sign In `client_secret` JWT from env vars only. Used by `.eas/workflows/rotate-apple-jwt.yml` every 90 days. |
|
|
11
|
+
| `_run.mjs` | Runtime selector for `clean.ts`. Picks `bun` if available, falls back to `tsx`. Not used by the CLI. |
|
|
12
|
+
|
|
13
|
+
Anything else (preflight checks, env validation, version bumps) lives in the `vexpo` CLI or in `eas-cli` directly.
|
|
14
|
+
|
|
15
|
+
## Setup orchestration
|
|
16
|
+
|
|
17
|
+
Use the `vexpo` CLI:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bunx vexpo lite # dev-mode setup (Convex + Better Auth only)
|
|
21
|
+
bunx vexpo full # full provisioning to TestFlight-ready
|
|
22
|
+
bunx vexpo doctor # cross-source drift detection
|
|
23
|
+
bunx vexpo env push # sync from .env.local + .env.prod to Convex/EAS
|
|
24
|
+
bunx vexpo apple asc-key # validate ASC API key
|
|
25
|
+
bunx vexpo apple services-id # attach SIWA capability to App ID
|
|
26
|
+
bunx vexpo apple jwt # sign client_secret JWT, push to Convex
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Version bumps run through `eas build:version:set` / `eas build:version:sync` (`appVersionSource: "remote"` in `eas.json` puts EAS in charge of the version).
|
|
30
|
+
|
|
31
|
+
The CLI itself ships from [`vexpo` on npm](https://www.npmjs.com/package/vexpo). Source lives at [`github.com/ramonclaudio/vexpo`](https://github.com/ramonclaudio/vexpo).
|
|
32
|
+
|
|
33
|
+
## Conventions
|
|
34
|
+
|
|
35
|
+
- All deletions go through `trash`. Recoverable from macOS Trash.
|
|
36
|
+
- Scripts here are intentionally minimal. The heavy logic is in the CLI.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Runtime-agnostic launcher for vexpo's TypeScript setup scripts.
|
|
4
|
+
*
|
|
5
|
+
* Picks the first available runtime that handles full TypeScript syntax:
|
|
6
|
+
*
|
|
7
|
+
* 1. bun. native TS, fastest startup
|
|
8
|
+
* 2. tsx (devDep). esbuild-based TS runner, works under any node 18+
|
|
9
|
+
*
|
|
10
|
+
* Note: we don't use node's `--experimental-strip-types` because it's
|
|
11
|
+
* strip-only. it doesn't transform syntax (parameter properties, enums,
|
|
12
|
+
* namespaces, etc all error out). bun and tsx handle full TS.
|
|
13
|
+
*
|
|
14
|
+
* Then re-execs the target script with the selected runtime, forwarding
|
|
15
|
+
* argv and stdio. Exit code mirrors the child.
|
|
16
|
+
*
|
|
17
|
+
* Usage (from package.json scripts):
|
|
18
|
+
* node scripts/_run.mjs scripts/setup.ts [args...]
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
22
|
+
import { existsSync } from "node:fs";
|
|
23
|
+
import { resolve, dirname } from "node:path";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
|
|
26
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const REPO = resolve(HERE, "..");
|
|
28
|
+
|
|
29
|
+
const [, , target, ...rawRest] = process.argv;
|
|
30
|
+
if (!target) {
|
|
31
|
+
console.error("usage: _run.mjs <script.ts> [args...]");
|
|
32
|
+
process.exit(2);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// pnpm passes a literal `--` separator before forwarded args. npm and yarn
|
|
36
|
+
// strip it. Drop a leading `--` so all PMs behave the same.
|
|
37
|
+
const rest = rawRest[0] === "--" ? rawRest.slice(1) : rawRest;
|
|
38
|
+
|
|
39
|
+
function which(bin) {
|
|
40
|
+
const r = spawnSync(process.platform === "win32" ? "where" : "which", [bin], {
|
|
41
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
42
|
+
encoding: "utf8",
|
|
43
|
+
});
|
|
44
|
+
if (r.status !== 0) return null;
|
|
45
|
+
return r.stdout.trim().split("\n")[0] || null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function pickRuntime() {
|
|
49
|
+
// Already running under bun? Use it directly (fastest).
|
|
50
|
+
if (process.versions.bun) return { cmd: process.execPath, args: [target] };
|
|
51
|
+
|
|
52
|
+
// bun on PATH
|
|
53
|
+
const bun = which("bun");
|
|
54
|
+
if (bun) return { cmd: bun, args: [target] };
|
|
55
|
+
|
|
56
|
+
// tsx fallback (any node 18+)
|
|
57
|
+
const tsx = resolve(REPO, "node_modules", ".bin", "tsx");
|
|
58
|
+
if (existsSync(tsx)) return { cmd: tsx, args: [target] };
|
|
59
|
+
|
|
60
|
+
// tsx via npx as last resort
|
|
61
|
+
const npx = which("npx");
|
|
62
|
+
if (npx) return { cmd: npx, args: ["tsx", target] };
|
|
63
|
+
|
|
64
|
+
console.error(`vexpo full needs bun or tsx to run TypeScript.`);
|
|
65
|
+
console.error(` install bun: curl -fsSL https://bun.sh/install | bash`);
|
|
66
|
+
console.error(` or run: npm install (vexpo ships tsx as a devDep)`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const runtime = pickRuntime();
|
|
71
|
+
const child = spawn(runtime.cmd, [...runtime.args, ...rest], {
|
|
72
|
+
stdio: "inherit",
|
|
73
|
+
cwd: REPO,
|
|
74
|
+
});
|
|
75
|
+
child.on("exit", (code, signal) => {
|
|
76
|
+
process.exit(code ?? (signal ? 1 : 0));
|
|
77
|
+
});
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vexpo clean script.
|
|
3
|
+
*
|
|
4
|
+
* Wipes every regenerable cache and build artifact:
|
|
5
|
+
* - Project artifacts: node_modules, bun.lock, ios/, .expo/, dist/, convex/_generated/, tsconfig.tsbuildinfo, coverage/, .vitest-cache/, expo-env.d.ts, bun-error.*, *.log
|
|
6
|
+
* - .eas/ per-project state (keeps .eas/workflows/)
|
|
7
|
+
* - .DS_Store files repo-wide
|
|
8
|
+
* - $TMPDIR caches: metro-*, haste-map-*, react-*, node-compile-cache, expo-*, RN*
|
|
9
|
+
* - System caches: ~/Library/Caches/CocoaPods, ~/.expo
|
|
10
|
+
* - Xcode build outputs: ~/Library/Developer/Xcode/DerivedData/<project>-*
|
|
11
|
+
*
|
|
12
|
+
* Never wiped (user data / secrets):
|
|
13
|
+
* - .env / .env.* (auth values)
|
|
14
|
+
* - .p8 / .p12 / AuthKey_* / SubscriptionKey_* (Apple keys)
|
|
15
|
+
* - store.config.json (rebrand work; setup recreates from .example if missing)
|
|
16
|
+
* - .vexpo-manual-setup/ / .rebrand-backup/
|
|
17
|
+
* - .setup-state.json (opt-in via --state)
|
|
18
|
+
*
|
|
19
|
+
* Then reinstalls deps via the detected package manager.
|
|
20
|
+
*
|
|
21
|
+
* Uses macOS `trash` for every delete so anything wiped is recoverable.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* bun run clean full wipe + install
|
|
25
|
+
* bun run clean --metro just Metro/Haste/Babel caches (fast, no reinstall)
|
|
26
|
+
* bun run clean --state also wipe .setup-state.json (next setup re-probes everything)
|
|
27
|
+
* bun run clean --no-install wipe everything but skip the reinstall
|
|
28
|
+
* bun run clean --help
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
32
|
+
import { access, readdir, readFile, stat } from "node:fs/promises";
|
|
33
|
+
import { homedir } from "node:os";
|
|
34
|
+
import { dirname, resolve } from "node:path";
|
|
35
|
+
import { fileURLToPath } from "node:url";
|
|
36
|
+
import { parseArgs } from "node:util";
|
|
37
|
+
|
|
38
|
+
type StdioOption = "inherit" | "pipe" | "ignore";
|
|
39
|
+
|
|
40
|
+
type SpawnOpts = {
|
|
41
|
+
stdio?: StdioOption[];
|
|
42
|
+
stdin?: StdioOption;
|
|
43
|
+
stdout?: StdioOption;
|
|
44
|
+
stderr?: StdioOption;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function spawn(argv: readonly string[], opts: SpawnOpts = {}): { exited: Promise<number> } {
|
|
48
|
+
const stdio = opts.stdio ?? [
|
|
49
|
+
opts.stdin ?? "inherit",
|
|
50
|
+
opts.stdout ?? "inherit",
|
|
51
|
+
opts.stderr ?? "inherit",
|
|
52
|
+
];
|
|
53
|
+
const proc = nodeSpawn(argv[0]!, argv.slice(1), { stdio });
|
|
54
|
+
return {
|
|
55
|
+
exited: new Promise<number>((resolve) => {
|
|
56
|
+
proc.on("close", (code) => resolve(code ?? 1));
|
|
57
|
+
// ENOENT (command not found) emits 'error' without 'close'. Treat as
|
|
58
|
+
// the standard shell "not found" exit so callers can `if (code === 0)`.
|
|
59
|
+
proc.on("error", () => resolve(127));
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Find PIDs whose full command line matches `pattern` (extended regex, via
|
|
68
|
+
* `pgrep -f`). Excludes our own PID and PPID so the script never kills its
|
|
69
|
+
* own bash parent or itself.
|
|
70
|
+
*/
|
|
71
|
+
async function pgrepF(pattern: string): Promise<number[]> {
|
|
72
|
+
return new Promise((resolve) => {
|
|
73
|
+
const proc = nodeSpawn("pgrep", ["-f", pattern], {
|
|
74
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
75
|
+
});
|
|
76
|
+
let buf = "";
|
|
77
|
+
proc.stdout?.on("data", (c) => (buf += c.toString()));
|
|
78
|
+
proc.on("error", () => resolve([]));
|
|
79
|
+
proc.on("close", () => {
|
|
80
|
+
const self = process.pid;
|
|
81
|
+
const parent = typeof process.ppid === "number" ? process.ppid : -1;
|
|
82
|
+
const pids = buf
|
|
83
|
+
.split("\n")
|
|
84
|
+
.filter(Boolean)
|
|
85
|
+
.map((s) => Number.parseInt(s, 10))
|
|
86
|
+
.filter((n) => Number.isFinite(n) && n !== self && n !== parent);
|
|
87
|
+
resolve(pids);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function trySignal(pids: readonly number[], signal: "TERM" | "KILL"): Promise<void> {
|
|
93
|
+
if (pids.length === 0) return;
|
|
94
|
+
await spawn(["kill", `-${signal}`, ...pids.map(String)], {
|
|
95
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
96
|
+
}).exited;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function fileExists(p: string): Promise<boolean> {
|
|
100
|
+
try {
|
|
101
|
+
await access(p);
|
|
102
|
+
return true;
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type PM = "bun" | "pnpm" | "yarn" | "npm";
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Capture which PM ran this script BEFORE any wipes. Two signals:
|
|
112
|
+
* 1. `npm_execpath`. every modern PM (npm/bun/pnpm/yarn) sets this to its
|
|
113
|
+
* own binary path when running scripts. Most reliable.
|
|
114
|
+
* 2. Lockfile presence. fallback when running outside `<pm> run` (e.g.
|
|
115
|
+
* direct `node scripts/clean.ts`). Read while the lockfile still exists.
|
|
116
|
+
*/
|
|
117
|
+
async function detectPackageManager(): Promise<PM> {
|
|
118
|
+
const execpath = (process.env.npm_execpath ?? "").toLowerCase();
|
|
119
|
+
if (execpath.includes("bun")) return "bun";
|
|
120
|
+
if (execpath.includes("pnpm")) return "pnpm";
|
|
121
|
+
if (execpath.includes("yarn")) return "yarn";
|
|
122
|
+
if (execpath.includes("npm")) return "npm";
|
|
123
|
+
if (await fileExists("bun.lock")) return "bun";
|
|
124
|
+
if (await fileExists("pnpm-lock.yaml")) return "pnpm";
|
|
125
|
+
if (await fileExists("yarn.lock")) return "yarn";
|
|
126
|
+
return "npm";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function installCmdFor(pm: PM): string {
|
|
130
|
+
return `${pm} install`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
134
|
+
process.chdir(REPO_ROOT);
|
|
135
|
+
|
|
136
|
+
// ─── Output ──────────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
const RESET = "\x1b[0m";
|
|
139
|
+
const BOLD = "\x1b[1m";
|
|
140
|
+
const DIM = "\x1b[2m";
|
|
141
|
+
function ansiHex(hex: string): string {
|
|
142
|
+
const m = /^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i.exec(hex);
|
|
143
|
+
if (!m) return "";
|
|
144
|
+
return `\x1b[38;2;${parseInt(m[1], 16)};${parseInt(m[2], 16)};${parseInt(m[3], 16)}m`;
|
|
145
|
+
}
|
|
146
|
+
const GREEN = ansiHex("#22c55e");
|
|
147
|
+
const YELLOW = ansiHex("#f59e0b");
|
|
148
|
+
const RED = ansiHex("#ef4444");
|
|
149
|
+
const VIOLET = ansiHex("#a78bfa");
|
|
150
|
+
|
|
151
|
+
const line = (s = "") => process.stderr.write(s + "\n");
|
|
152
|
+
const ok = (msg: string) => line(` ${GREEN}ok${RESET} ${msg}`);
|
|
153
|
+
const nop = (msg: string) => line(` ${DIM}-- ${msg}${RESET}`);
|
|
154
|
+
const yep = (msg: string) => line(` ${YELLOW}!!${RESET} ${msg}`);
|
|
155
|
+
const bad = (msg: string) => line(` ${RED}xx${RESET} ${RED}${msg}${RESET}`);
|
|
156
|
+
|
|
157
|
+
function stringWidth(s: string): number {
|
|
158
|
+
return [...s].length;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function section(title: string): void {
|
|
162
|
+
const w = process.stderr.columns ?? process.stdout.columns ?? 80;
|
|
163
|
+
const fill = "─".repeat(Math.max(0, w - stringWidth(title) - 3));
|
|
164
|
+
line(`\n${BOLD}${VIOLET}${title}${RESET} ${DIM}${fill}${RESET}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ─── Args ────────────────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
const HELP = `${BOLD}vexpo clean${RESET}
|
|
170
|
+
|
|
171
|
+
${BOLD}Usage:${RESET}
|
|
172
|
+
${DIM}bun run clean${RESET} full wipe + bun install
|
|
173
|
+
${DIM}bun run clean --metro${RESET} just Metro/Haste/Babel caches
|
|
174
|
+
${DIM}bun run clean --state${RESET} also wipe .setup-state.json
|
|
175
|
+
${DIM}bun run clean --no-install${RESET} wipe everything but skip reinstall
|
|
176
|
+
${DIM}bun run clean --help${RESET}
|
|
177
|
+
|
|
178
|
+
The full wipe removes node_modules, lockfile, ios/, .expo/, dist/,
|
|
179
|
+
convex/_generated/, tsbuildinfo, coverage/, .vitest-cache/,
|
|
180
|
+
expo-env.d.ts, bun-error.*, *.log, .eas/ (except workflows/),
|
|
181
|
+
all .DS_Store files, $TMPDIR Metro/Haste/React/expo/RN caches,
|
|
182
|
+
~/Library/Caches/CocoaPods, ~/.expo, and the Xcode DerivedData
|
|
183
|
+
subfolder for this project. Never touches .env files, Apple keys,
|
|
184
|
+
store.config.json, .vexpo-manual-setup/, or .rebrand-backup/.
|
|
185
|
+
|
|
186
|
+
${BOLD}--state${RESET} additionally wipes .setup-state.json so the next
|
|
187
|
+
${DIM}bun run setup${RESET} re-probes every phase against external services
|
|
188
|
+
(slower, but the cure when state has drifted from reality).
|
|
189
|
+
|
|
190
|
+
Bundlers (Metro, expo CLI, react-native start, Watchman) are stopped
|
|
191
|
+
automatically before the wipe so macOS ${DIM}trash${RESET} can't silently skip files
|
|
192
|
+
held open. ${BOLD}convex dev${RESET} is left alone (it's your data layer, not a
|
|
193
|
+
bundler); restart it manually if it misbehaves after a full wipe.
|
|
194
|
+
`;
|
|
195
|
+
|
|
196
|
+
let args: { metro?: boolean; state?: boolean; "no-install"?: boolean; help?: boolean };
|
|
197
|
+
try {
|
|
198
|
+
args = parseArgs({
|
|
199
|
+
args: process.argv.slice(2),
|
|
200
|
+
options: {
|
|
201
|
+
metro: { type: "boolean", default: false },
|
|
202
|
+
state: { type: "boolean", default: false },
|
|
203
|
+
"no-install": { type: "boolean", default: false },
|
|
204
|
+
help: { type: "boolean", short: "h", default: false },
|
|
205
|
+
},
|
|
206
|
+
strict: true,
|
|
207
|
+
}).values;
|
|
208
|
+
} catch (err) {
|
|
209
|
+
bad(err instanceof Error ? err.message : String(err));
|
|
210
|
+
process.exit(2);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (args.help) {
|
|
214
|
+
line(HELP);
|
|
215
|
+
process.exit(0);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
async function pathExists(p: string): Promise<boolean> {
|
|
221
|
+
try {
|
|
222
|
+
await stat(p);
|
|
223
|
+
return true;
|
|
224
|
+
} catch {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Pass paths to `trash` (recoverable). Skips paths that don't exist so the
|
|
231
|
+
* macOS `trash` CLI doesn't error out on missing entries.
|
|
232
|
+
*/
|
|
233
|
+
async function trashPaths(paths: string[]): Promise<void> {
|
|
234
|
+
const existing: string[] = [];
|
|
235
|
+
for (const p of paths) {
|
|
236
|
+
if (await pathExists(p)) existing.push(p);
|
|
237
|
+
}
|
|
238
|
+
if (existing.length === 0) return;
|
|
239
|
+
const proc = spawn(["trash", ...existing], {
|
|
240
|
+
stdin: "ignore",
|
|
241
|
+
stdout: "ignore",
|
|
242
|
+
stderr: "ignore",
|
|
243
|
+
});
|
|
244
|
+
await proc.exited;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function expandGlob(dir: string, pattern: string): Promise<string[]> {
|
|
248
|
+
if (!(await pathExists(dir))) return [];
|
|
249
|
+
// Convert simple glob (only * supported) to regex. Sufficient for our patterns.
|
|
250
|
+
const re = new RegExp(
|
|
251
|
+
"^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$",
|
|
252
|
+
);
|
|
253
|
+
const entries = await readdir(dir);
|
|
254
|
+
return entries.filter((e) => re.test(e)).map((e) => `${dir}/${e}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ─── Targets ─────────────────────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
const REPO = REPO_ROOT;
|
|
260
|
+
const TMPDIR = process.env.TMPDIR?.replace(/\/$/, "") ?? "/tmp";
|
|
261
|
+
const HOME = homedir();
|
|
262
|
+
|
|
263
|
+
async function readPkgName(): Promise<string> {
|
|
264
|
+
try {
|
|
265
|
+
const pkg: unknown = JSON.parse(await readFile(`${REPO}/package.json`, "utf8"));
|
|
266
|
+
if (typeof pkg === "object" && pkg !== null && "name" in pkg) {
|
|
267
|
+
const { name } = pkg;
|
|
268
|
+
if (typeof name === "string") return name;
|
|
269
|
+
}
|
|
270
|
+
} catch {}
|
|
271
|
+
return "vexpo";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const PROJECT_TARGETS = [
|
|
275
|
+
"node_modules",
|
|
276
|
+
"bun.lock",
|
|
277
|
+
"ios",
|
|
278
|
+
".expo",
|
|
279
|
+
"dist",
|
|
280
|
+
"convex/_generated",
|
|
281
|
+
"tsconfig.tsbuildinfo",
|
|
282
|
+
"coverage",
|
|
283
|
+
".vitest-cache",
|
|
284
|
+
"expo-env.d.ts",
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
// Globs evaluated at REPO root. bun-error.* and *.log are cheap to wipe and
|
|
288
|
+
// almost never wanted across runs.
|
|
289
|
+
const PROJECT_GLOBS = ["bun-error.*", "*.log"];
|
|
290
|
+
|
|
291
|
+
const TMP_GLOBS = ["metro-*", "haste-map-*", "react-*", "node-compile-cache", "expo-*", "RN*"];
|
|
292
|
+
|
|
293
|
+
// ─── Steps ───────────────────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Stop bundlers before wiping their caches. macOS `trash` silently skips
|
|
297
|
+
* files held open by a running process, so caches survive the wipe and the
|
|
298
|
+
* bundler restarts onto stale state. Killing first prevents that.
|
|
299
|
+
*
|
|
300
|
+
* Bundlers are killed automatically. `convex dev` is the user's data layer,
|
|
301
|
+
* not a bundler. left alone, with a warning.
|
|
302
|
+
*/
|
|
303
|
+
async function stepStopBundlers(): Promise<void> {
|
|
304
|
+
section("Stop bundlers");
|
|
305
|
+
|
|
306
|
+
// Patterns are pgrep -f extended regex over the full command line.
|
|
307
|
+
// Order: kill the parent CLI first so it can tear down its child Metro.
|
|
308
|
+
const targets: { pattern: string; name: string }[] = [
|
|
309
|
+
{ pattern: "node .*\\.bin/expo (run:|start)", name: "expo CLI" },
|
|
310
|
+
{ pattern: "node .*@expo/cli/build/bin/cli", name: "expo CLI (forked)" },
|
|
311
|
+
{ pattern: "node .*metro/src/cli\\.js", name: "Metro" },
|
|
312
|
+
{ pattern: "node .*react-native start", name: "react-native start" },
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
let killed = 0;
|
|
316
|
+
for (const { pattern, name } of targets) {
|
|
317
|
+
const pids = await pgrepF(pattern);
|
|
318
|
+
if (pids.length === 0) continue;
|
|
319
|
+
await trySignal(pids, "TERM");
|
|
320
|
+
ok(`stopped ${name} (${pids.length} ${pids.length === 1 ? "process" : "processes"})`);
|
|
321
|
+
killed += pids.length;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Watchman has its own clean shutdown. No-op if not installed (exit 127).
|
|
325
|
+
const wm = await spawn(["watchman", "shutdown-server"], {
|
|
326
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
327
|
+
}).exited;
|
|
328
|
+
if (wm === 0) {
|
|
329
|
+
ok("stopped Watchman");
|
|
330
|
+
killed += 1;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (killed === 0) {
|
|
334
|
+
nop("no bundlers running");
|
|
335
|
+
} else {
|
|
336
|
+
// Drain SIGTERM, then SIGKILL stragglers so the wipe can't race them.
|
|
337
|
+
await sleep(500);
|
|
338
|
+
for (const { pattern } of targets) {
|
|
339
|
+
const pids = await pgrepF(pattern);
|
|
340
|
+
if (pids.length > 0) await trySignal(pids, "KILL");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const convex = await pgrepF("\\.bin/convex dev");
|
|
345
|
+
if (convex.length > 0) {
|
|
346
|
+
yep("convex dev is still running. Restart it after the wipe if it misbehaves.");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function stepMetroCachesOnly(): Promise<void> {
|
|
351
|
+
section("Metro caches");
|
|
352
|
+
const matches: string[] = [];
|
|
353
|
+
for (const pattern of ["metro-*", "haste-map-*", "node-compile-cache"]) {
|
|
354
|
+
matches.push(...(await expandGlob(TMPDIR, pattern)));
|
|
355
|
+
}
|
|
356
|
+
if (matches.length === 0) {
|
|
357
|
+
nop("nothing to wipe under $TMPDIR");
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
await trashPaths(matches);
|
|
361
|
+
ok(`trashed ${matches.length} cache director${matches.length === 1 ? "y" : "ies"}`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function stepProjectArtifacts(): Promise<void> {
|
|
365
|
+
section("Project artifacts");
|
|
366
|
+
const targets = PROJECT_TARGETS.map((t) => `${REPO}/${t}`);
|
|
367
|
+
for (const pattern of PROJECT_GLOBS) {
|
|
368
|
+
targets.push(...(await expandGlob(REPO, pattern)));
|
|
369
|
+
}
|
|
370
|
+
const existing: string[] = [];
|
|
371
|
+
for (const t of targets) {
|
|
372
|
+
if (await pathExists(t)) existing.push(t);
|
|
373
|
+
}
|
|
374
|
+
if (existing.length === 0) {
|
|
375
|
+
nop("nothing to wipe");
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
await trashPaths(existing);
|
|
379
|
+
for (const t of existing) ok(`trashed ${t.replace(REPO + "/", "")}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Wipe `.eas/` per-project CLI state but keep `.eas/workflows/` (tracked YAML).
|
|
384
|
+
* EAS regenerates everything else on its next invocation.
|
|
385
|
+
*/
|
|
386
|
+
async function stepEasState(): Promise<void> {
|
|
387
|
+
section(".eas state");
|
|
388
|
+
const easDir = `${REPO}/.eas`;
|
|
389
|
+
if (!(await pathExists(easDir))) {
|
|
390
|
+
nop(".eas/ not present");
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const entries = await readdir(easDir);
|
|
394
|
+
const targets = entries.filter((name) => name !== "workflows").map((name) => `${easDir}/${name}`);
|
|
395
|
+
if (targets.length === 0) {
|
|
396
|
+
nop("only .eas/workflows/ present (kept)");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
await trashPaths(targets);
|
|
400
|
+
ok(
|
|
401
|
+
`trashed ${targets.length} .eas/ ${targets.length === 1 ? "entry" : "entries"} (kept workflows/)`,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function stepDsStores(): Promise<void> {
|
|
406
|
+
section("macOS .DS_Store");
|
|
407
|
+
const { stdout } = await new Promise<{ stdout: string }>((resolve) => {
|
|
408
|
+
const proc = nodeSpawn(
|
|
409
|
+
"find",
|
|
410
|
+
[REPO, "-name", ".DS_Store", "-not", "-path", "*/node_modules/*"],
|
|
411
|
+
{ stdio: ["ignore", "pipe", "ignore"] },
|
|
412
|
+
);
|
|
413
|
+
let buf = "";
|
|
414
|
+
proc.stdout?.on("data", (c) => (buf += c.toString()));
|
|
415
|
+
proc.on("close", () => resolve({ stdout: buf }));
|
|
416
|
+
});
|
|
417
|
+
const matches = stdout.split("\n").filter(Boolean);
|
|
418
|
+
if (matches.length === 0) {
|
|
419
|
+
nop("none found");
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
await trashPaths(matches);
|
|
423
|
+
ok(`trashed ${matches.length} .DS_Store ${matches.length === 1 ? "file" : "files"}`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function stepTmpdirCaches(): Promise<void> {
|
|
427
|
+
section("$TMPDIR caches");
|
|
428
|
+
const matches: string[] = [];
|
|
429
|
+
for (const pattern of TMP_GLOBS) {
|
|
430
|
+
matches.push(...(await expandGlob(TMPDIR, pattern)));
|
|
431
|
+
}
|
|
432
|
+
if (matches.length === 0) {
|
|
433
|
+
nop("nothing to wipe under $TMPDIR");
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
await trashPaths(matches);
|
|
437
|
+
ok(`trashed ${matches.length} cache entr${matches.length === 1 ? "y" : "ies"} under $TMPDIR`);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function stepCocoaPodsCache(): Promise<void> {
|
|
441
|
+
section("CocoaPods cache");
|
|
442
|
+
const path = `${HOME}/Library/Caches/CocoaPods`;
|
|
443
|
+
if (!(await pathExists(path))) {
|
|
444
|
+
nop("not present");
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
await trashPaths([path]);
|
|
448
|
+
ok("trashed ~/Library/Caches/CocoaPods");
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function stepXcodeDerivedData(pkgName: string): Promise<void> {
|
|
452
|
+
section("Xcode DerivedData");
|
|
453
|
+
const root = `${HOME}/Library/Developer/Xcode/DerivedData`;
|
|
454
|
+
if (!(await pathExists(root))) {
|
|
455
|
+
nop("DerivedData not present");
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
// Match folders that start with the project's pkg name OR the iOS
|
|
459
|
+
// bundle's display name. We filter by leading prefix so we never touch
|
|
460
|
+
// other projects' caches.
|
|
461
|
+
const matches = [
|
|
462
|
+
...(await expandGlob(root, `${pkgName}-*`)),
|
|
463
|
+
...(await expandGlob(root, "Vexpo-*")),
|
|
464
|
+
];
|
|
465
|
+
if (matches.length === 0) {
|
|
466
|
+
nop("no matching DerivedData entries");
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
await trashPaths(matches);
|
|
470
|
+
ok(`trashed ${matches.length} DerivedData ${matches.length === 1 ? "entry" : "entries"}`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function stepExpoCache(): Promise<void> {
|
|
474
|
+
section("Expo CLI cache");
|
|
475
|
+
const path = `${HOME}/.expo`;
|
|
476
|
+
if (!(await pathExists(path))) {
|
|
477
|
+
nop("~/.expo not present");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
// .expo holds the user-level Expo cache (devices.json, telemetry, sdk
|
|
481
|
+
// metadata). Safe to wipe; Expo regenerates on next CLI invocation.
|
|
482
|
+
await trashPaths([path]);
|
|
483
|
+
ok("trashed ~/.expo");
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
async function stepSetupState(): Promise<void> {
|
|
487
|
+
section("Setup state");
|
|
488
|
+
const path = `${REPO}/.setup-state.json`;
|
|
489
|
+
if (!(await pathExists(path))) {
|
|
490
|
+
nop(".setup-state.json not present");
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
await trashPaths([path]);
|
|
494
|
+
ok("trashed .setup-state.json (next `bun run setup` re-probes every phase)");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async function stepInstall(pm: PM): Promise<void> {
|
|
498
|
+
section("Reinstall");
|
|
499
|
+
const cmd = installCmdFor(pm).split(" ");
|
|
500
|
+
const proc = spawn(cmd, { stdio: ["inherit", "inherit", "inherit"] });
|
|
501
|
+
const code = await proc.exited;
|
|
502
|
+
if (code !== 0) throw new Error(`${cmd.join(" ")} exited with code ${code}`);
|
|
503
|
+
ok(cmd.join(" "));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ─── Entry ───────────────────────────────────────────────────────────────────
|
|
507
|
+
|
|
508
|
+
// Wrapped in an async IIFE so the file works under both ESM (top-level await
|
|
509
|
+
// supported) and CJS-via-tsx (no top-level await).
|
|
510
|
+
void (async () => {
|
|
511
|
+
const startedAt = performance.now();
|
|
512
|
+
try {
|
|
513
|
+
if (args.metro) {
|
|
514
|
+
await stepStopBundlers();
|
|
515
|
+
await stepMetroCachesOnly();
|
|
516
|
+
} else {
|
|
517
|
+
// Capture PM BEFORE any wipes; stepProjectArtifacts trashes the lockfile.
|
|
518
|
+
const pm = await detectPackageManager();
|
|
519
|
+
const pkgName = await readPkgName();
|
|
520
|
+
await stepStopBundlers();
|
|
521
|
+
await stepProjectArtifacts();
|
|
522
|
+
await stepEasState();
|
|
523
|
+
await stepDsStores();
|
|
524
|
+
await stepTmpdirCaches();
|
|
525
|
+
await stepCocoaPodsCache();
|
|
526
|
+
await stepXcodeDerivedData(pkgName);
|
|
527
|
+
await stepExpoCache();
|
|
528
|
+
if (args.state) await stepSetupState();
|
|
529
|
+
if (!args["no-install"]) {
|
|
530
|
+
await stepInstall(pm);
|
|
531
|
+
} else {
|
|
532
|
+
yep(`--no-install passed; skipping ${pm} install`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const elapsed = ((performance.now() - startedAt) / 1000).toFixed(2);
|
|
536
|
+
line(`\n ${GREEN}ok${RESET} clean complete in ${elapsed}s\n`);
|
|
537
|
+
} catch (err) {
|
|
538
|
+
line();
|
|
539
|
+
if (err instanceof Error) bad(err.message);
|
|
540
|
+
else bad(String(err));
|
|
541
|
+
process.exit(1);
|
|
542
|
+
}
|
|
543
|
+
})();
|