umpordez 1.0.2 → 1.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/.claude/settings.local.json +33 -1
- package/README.md +160 -52
- package/cli.mjs +75 -0
- package/create.mjs +376 -50
- package/icons.mjs +257 -0
- package/package.json +4 -3
- package/template/.claude/settings.json +49 -0
- package/template/CLAUDE.md +162 -108
- package/template/README.md +153 -85
- package/template/dev.sh +62 -25
- package/template/doc/index.html +638 -0
- package/template/install.sh +76 -19
- package/template/mobile/{{PROJECT_SLUG}}/App.tsx +75 -0
- package/template/mobile/{{PROJECT_SLUG}}/CLAUDE.md +168 -0
- package/template/mobile/{{PROJECT_SLUG}}/README.md +149 -0
- package/template/mobile/{{PROJECT_SLUG}}/app.json +78 -0
- package/template/mobile/{{PROJECT_SLUG}}/assets/README.md +32 -0
- package/template/mobile/{{PROJECT_SLUG}}/babel.config.js +10 -0
- package/template/mobile/{{PROJECT_SLUG}}/eas.json +36 -0
- package/template/mobile/{{PROJECT_SLUG}}/eslint.config.js +18 -0
- package/template/mobile/{{PROJECT_SLUG}}/index.ts +28 -0
- package/template/mobile/{{PROJECT_SLUG}}/metro.config.js +5 -0
- package/template/mobile/{{PROJECT_SLUG}}/native/README.md +106 -0
- package/template/mobile/{{PROJECT_SLUG}}/native/android/AppNotificationReceiver.kt +65 -0
- package/template/mobile/{{PROJECT_SLUG}}/native/android/AppNotificationsModule.kt +219 -0
- package/template/mobile/{{PROJECT_SLUG}}/native/android/AppNotificationsPackage.kt +27 -0
- package/template/mobile/{{PROJECT_SLUG}}/native/cookbook/hello-android/HelloModule.kt +58 -0
- package/template/mobile/{{PROJECT_SLUG}}/native/cookbook/hello-android/HelloPackage.kt +28 -0
- package/template/mobile/{{PROJECT_SLUG}}/native/cookbook/hello-ios/HelloModule.m +25 -0
- package/template/mobile/{{PROJECT_SLUG}}/native/cookbook/hello-ios/HelloModule.swift +61 -0
- package/template/mobile/{{PROJECT_SLUG}}/native/cookbook/live-activity/AppLiveActivityAttributes.swift +25 -0
- package/template/mobile/{{PROJECT_SLUG}}/native/cookbook/live-activity/README.md +73 -0
- package/template/mobile/{{PROJECT_SLUG}}/native/ios/AppNotificationsModule.m +28 -0
- package/template/mobile/{{PROJECT_SLUG}}/native/ios/AppNotificationsModule.swift +168 -0
- package/template/mobile/{{PROJECT_SLUG}}/package.json +56 -0
- package/template/mobile/{{PROJECT_SLUG}}/plugins/withAndroidHealthConnect.js +79 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/components/AnimatedSplash.tsx +216 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/components/LanguagePicker.tsx +224 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/components/PremiumGate.tsx +123 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/components/ToastConfig.tsx +100 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/config.ts +60 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/contexts/AuthContext.tsx +595 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/contexts/ThemeContext.tsx +67 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/i18n/index.ts +88 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/i18n/locales/en.ts +274 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/i18n/locales/pt-BR.ts +458 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/pages/auth/AuthLanding.tsx +530 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/pages/auth/ForgotPassword.tsx +219 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/pages/auth/Login.tsx +274 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/pages/auth/Signup.tsx +291 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/pages/main/Action.tsx +113 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/pages/main/Explore.tsx +137 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/pages/main/Home.tsx +127 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/pages/main/Library.tsx +208 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/pages/main/Profile.tsx +440 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/pages/onboarding/FirstAction.tsx +168 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/pages/onboarding/Onboarding.tsx +297 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/pages/onboarding/Welcome.tsx +227 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/routes/index.tsx +410 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/routes/types.ts +31 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/api.ts +72 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/backup/export.ts +79 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/backup/index.ts +131 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/content/assetCache.ts +74 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/content/index.ts +65 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/content/signing.ts +44 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/content/sync.ts +147 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/db/index.ts +157 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/db/migrations.ts +84 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/db/prefs.ts +84 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/fetch-api.ts +90 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/health/index.ts +238 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/iap/index.ts +409 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/services/notifications/index.ts +129 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/theme/colors.ts +61 -0
- package/template/mobile/{{PROJECT_SLUG}}/tsconfig.json +18 -0
- package/template/scripts/README.md +37 -0
- package/template/scripts/bump-version.sh +105 -0
- package/template/scripts/extract-play-key.js +64 -0
- package/template/scripts/generate-icons.sh +50 -0
- package/template/scripts/install-native.sh +169 -0
- package/template/scripts/prebuild.sh +28 -0
- package/template/scripts/release-android.sh +79 -0
- package/template/scripts/run-ios.sh +89 -0
- package/template/seed.sh +3 -2
- package/template/server/.env.sample +26 -1
- package/template/server/CLAUDE.md +135 -0
- package/template/server/apps/api/app.ts +28 -4
- package/template/server/apps/api/routes/account.ts +2 -2
- package/template/server/apps/api/routes/admin.ts +2 -2
- package/template/server/apps/api/routes/auth.ts +266 -6
- package/template/server/apps/api/routes/backup.ts +134 -0
- package/template/server/apps/api/routes/content.ts +142 -0
- package/template/server/apps/api/routes/files.ts +6 -2
- package/template/server/apps/api/routes/health.ts +55 -0
- package/template/server/apps/api/routes/iap.ts +159 -0
- package/template/server/apps/api/routes/index.ts +11 -4
- package/template/server/apps/shared/middlewares/demand-account-access.ts +9 -1
- package/template/server/apps/shared/middlewares/try-set-user-by-token.ts +25 -4
- package/template/server/apps/site-api/app.ts +19 -5
- package/template/server/content/catalog.json +17 -0
- package/template/server/core/content.ts +69 -0
- package/template/server/core/email.ts +3 -5
- package/template/server/core/iap-verify.ts +66 -0
- package/template/server/core/knexfile.ts +6 -6
- package/template/server/core/logger.ts +34 -12
- package/template/server/core/models/auth.ts +151 -1
- package/template/server/core/models/user.ts +72 -4
- package/template/server/core/oauth.ts +277 -0
- package/template/server/core/s3.ts +78 -1
- package/template/server/dirname.ts +14 -0
- package/template/server/knex.sh +20 -2
- package/template/server/migrations/20260208000003_add-mobile-auth.ts +66 -0
- package/template/server/package.json +7 -5
- package/template/server/tests/core/content.test.ts +19 -0
- package/template/server/tests/core/context.test.ts +12 -0
- package/template/server/tests/core/db.test.ts +10 -0
- package/template/server/tests/core/oauth.test.ts +19 -0
- package/template/server/tests/test.sh +10 -0
- package/template/ui/admin/CLAUDE.md +101 -0
- package/template/ui/admin/index.html +10 -0
- package/template/ui/site/.env.sample +5 -0
- package/template/ui/site/CLAUDE.md +102 -0
- package/template/ui/site/app.ts +73 -18
- package/template/ui/site/i18n/index.ts +167 -0
- package/template/ui/site/i18n/locales/en.ts +119 -0
- package/template/ui/site/i18n/locales/pt-BR.ts +182 -0
- package/template/ui/site/package.json +1 -0
- package/template/ui/site/views/pages/home.ejs +60 -118
- package/template/ui/site/views/pages/not-found.ejs +22 -0
- package/template/ui/site/views/partials/footer.ejs +10 -10
- package/template/ui/site/views/partials/header.ejs +52 -22
- package/template-variables.mjs +59 -0
- package/update.mjs +555 -0
- package/template/server/migrations/20260208000001_seed-admin.ts +0 -42
|
@@ -14,7 +14,39 @@
|
|
|
14
14
|
"WebFetch(domain:github.com)",
|
|
15
15
|
"WebFetch(domain:raw.githubusercontent.com)",
|
|
16
16
|
"Bash(gh api:*)",
|
|
17
|
-
"WebFetch(domain:api.github.com)"
|
|
17
|
+
"WebFetch(domain:api.github.com)",
|
|
18
|
+
"Bash(git checkout *)",
|
|
19
|
+
"Bash(cp /Users/ligeiro/dev/tranqs/app/mobile/tranqs/assets/icon.png /Users/ligeiro/dev/umpordez-cli/template/mobile/{{PROJECT_SLUG}}/assets/icon.png)",
|
|
20
|
+
"Bash(cp /Users/ligeiro/dev/tranqs/app/mobile/tranqs/assets/adaptive-icon.png /Users/ligeiro/dev/umpordez-cli/template/mobile/{{PROJECT_SLUG}}/assets/adaptive-icon.png)",
|
|
21
|
+
"Bash(cp /Users/ligeiro/dev/tranqs/app/mobile/tranqs/assets/splash-icon.png /Users/ligeiro/dev/umpordez-cli/template/mobile/{{PROJECT_SLUG}}/assets/splash-icon.png)",
|
|
22
|
+
"Bash(cp /Users/ligeiro/dev/tranqs/app/mobile/tranqs/assets/favicon.png /Users/ligeiro/dev/umpordez-cli/template/mobile/{{PROJECT_SLUG}}/assets/favicon.png)",
|
|
23
|
+
"Bash(git add *)",
|
|
24
|
+
"Bash(git commit -m ' *)",
|
|
25
|
+
"Bash(npm install *)",
|
|
26
|
+
"Read(//private/tmp/**)",
|
|
27
|
+
"Bash(rm -rf icon-test)",
|
|
28
|
+
"Bash(mkdir icon-test *)",
|
|
29
|
+
"Read(//tmp/**)",
|
|
30
|
+
"Bash(git commit *)",
|
|
31
|
+
"Bash(mv /Users/ligeiro/dev/umpordez-cli/template/mobile/{{PROJECT_SLUG}}/scripts/*.sh /Users/ligeiro/dev/umpordez-cli/template/scripts/)",
|
|
32
|
+
"Bash(rmdir /Users/ligeiro/dev/umpordez-cli/template/mobile/{{PROJECT_SLUG}}/scripts)",
|
|
33
|
+
"Read(//Users/ligeiro/dev/umpordez-aulass/**)",
|
|
34
|
+
"Bash(npx tsc *)",
|
|
35
|
+
"Bash(sed -i '' 's|^import React, { |import { |' App.tsx src/components/*.tsx src/contexts/*.tsx src/pages/auth/*.tsx src/pages/main/*.tsx)",
|
|
36
|
+
"Bash(sed -i '' 's|^import React from .react.;||' App.tsx src/components/*.tsx src/contexts/*.tsx src/pages/auth/*.tsx src/pages/main/*.tsx)",
|
|
37
|
+
"Bash(sed -i '' 's|^import React, { ReactNode }|import { ReactNode }|' src/contexts/*.tsx)",
|
|
38
|
+
"Bash(grep -l \"^import React\" App.tsx src/components/*.tsx src/contexts/*.tsx src/pages/auth/*.tsx src/pages/main/*.tsx)",
|
|
39
|
+
"Bash(npm run *)",
|
|
40
|
+
"Bash(npx expo *)",
|
|
41
|
+
"Bash(bash scripts/install-native.sh)",
|
|
42
|
+
"Bash(npm test *)",
|
|
43
|
+
"Bash(rm -rf /tmp/icon-test)",
|
|
44
|
+
"Bash(mkdir /tmp/icon-test)",
|
|
45
|
+
"Bash(md5 /Users/ligeiro/dev/umpordez-cli/template/mobile/{{PROJECT_SLUG}}/assets/icon.png /Users/ligeiro/dev/tranqs/app/mobile/tranqs/assets/icon.png)",
|
|
46
|
+
"Bash(md5 /tmp/umptest/everything/mobile/testapp/assets/icon.png /Users/ligeiro/dev/tranqs/app/mobile/tranqs/assets/icon.png)",
|
|
47
|
+
"Bash(git merge *)",
|
|
48
|
+
"Bash(git tag *)",
|
|
49
|
+
"Bash(npm pack *)"
|
|
18
50
|
]
|
|
19
51
|
}
|
|
20
52
|
}
|
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# umpordez
|
|
2
2
|
|
|
3
|
-
SaaS starter kit generator.
|
|
3
|
+
SaaS starter kit generator. Server, admin SPA, public site (i18n + SEO),
|
|
4
|
+
mobile app — all wired up, single command.
|
|
4
5
|
|
|
5
6
|
---
|
|
6
7
|
|
|
@@ -12,45 +13,70 @@ access... and then you still haven't written a single line of your
|
|
|
12
13
|
actual product.
|
|
13
14
|
|
|
14
15
|
I've built this same architecture across multiple production apps
|
|
15
|
-
(different domains, same bones). At some point I got tired of
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
(different domains, same bones). At some point I got tired of copying
|
|
17
|
+
between repos, adapting folder names, and forgetting to update that
|
|
18
|
+
one hardcoded port somewhere.
|
|
18
19
|
|
|
19
|
-
So I made a CLI that generates the whole thing. One command, answer
|
|
20
|
-
few questions, and you get a production-ready multi-tenant SaaS
|
|
21
|
-
|
|
20
|
+
So I made a CLI that generates the whole thing. One command, answer
|
|
21
|
+
a few questions, and you get a production-ready multi-tenant SaaS
|
|
22
|
+
with **server + admin + site + mobile** already talking to each
|
|
23
|
+
other.
|
|
22
24
|
|
|
23
25
|
**No magic, no hidden abstractions.** The generated code is yours;
|
|
24
|
-
plain TypeScript, plain React, plain SQL. Read it,
|
|
26
|
+
plain TypeScript, plain React, plain SQL, plain Expo. Read it,
|
|
27
|
+
change it, own it.
|
|
25
28
|
|
|
26
29
|
## What you get
|
|
27
30
|
|
|
28
31
|
```
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
<project>/
|
|
33
|
+
├── server/ Admin API + Site API + console + DB
|
|
34
|
+
│ (Express + TypeScript + PostgreSQL +
|
|
35
|
+
│ Knex + Zod)
|
|
36
|
+
├── ui/admin/ React SPA (Vite + shadcn/ui + Tailwind)
|
|
37
|
+
├── ui/site/ Public site (Express + EJS + Tailwind +
|
|
38
|
+
│ i18n with SEO best practices)
|
|
39
|
+
├── mobile/<slug>/ Expo + React Native + TypeScript
|
|
40
|
+
│ with auth, offline SQLite, content
|
|
41
|
+
│ sync, IAP, push (custom-native),
|
|
42
|
+
│ Apple Health + Health Connect
|
|
43
|
+
├── scripts/ All helpers in one place (mobile, deploy,
|
|
44
|
+
│ version bumping, icon generation)
|
|
45
|
+
├── doc/index.html HTML docs themed in your primary color
|
|
46
|
+
├── .claude/ Claude Code permission allowlist
|
|
47
|
+
└── CLAUDE.md (× 5) Per-app rules for AI agents
|
|
39
48
|
```
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- **Multi-tenant
|
|
44
|
-
|
|
45
|
-
- **Multi-API
|
|
46
|
-
|
|
47
|
-
- **
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- **
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
## Architecture (the bones)
|
|
51
|
+
|
|
52
|
+
- **Multi-tenant** — users → user_in_accounts → accounts → domain
|
|
53
|
+
entities. Roles: admin / owner / manager / user.
|
|
54
|
+
- **Multi-API** — separate Express servers sharing one `core/`
|
|
55
|
+
layer. Add more APIs by copying `apps/api/`.
|
|
56
|
+
- **Hybrid auth** — httpOnly JWT cookies for web, Bearer tokens for
|
|
57
|
+
mobile, refresh-token rotation single-flighted to prevent false
|
|
58
|
+
logouts on concurrent refresh.
|
|
59
|
+
- **Apple Sign-In + Google OAuth** pre-wired. Apple via JWKS verify
|
|
60
|
+
(zero-dep), Google via server-side flow that keeps the
|
|
61
|
+
`client_secret` out of the app bundle.
|
|
62
|
+
- **Free-first mobile** — anyone can use the app without sign-in.
|
|
63
|
+
Auth reached via Profile or via PremiumGate's onUpgrade.
|
|
64
|
+
- **Offline-first SQLite** with USER vs SHARED table classes,
|
|
65
|
+
cloud backup/restore via S3 (two-phase upload survives
|
|
66
|
+
mid-failure).
|
|
67
|
+
- **Content sync** — versioned catalog + HMAC-signed presigned
|
|
68
|
+
asset URLs cached for 55 min, server out of the streaming path.
|
|
69
|
+
- **i18n done right** — pt-BR default + en, every string via
|
|
70
|
+
`t()`. Site uses URL prefixes (`/` vs `/en/`) for SEO with full
|
|
71
|
+
hreflang + canonical + sitemap.xml.
|
|
72
|
+
- **Custom-native push** with cold-start tap dispatch (the bit
|
|
73
|
+
expo-notifications can't do reliably).
|
|
74
|
+
- **Context DI** — fresh `Context` per request, all models
|
|
75
|
+
instantiated, no singletons, no shared state.
|
|
76
|
+
- **Routes go through models** — never knex from a route. Path
|
|
77
|
+
aliases (`#core/*`, `#shared/*`, `@/*`) over relative paths.
|
|
78
|
+
- **PostgreSQL with raw SQL migrations** — because ORMs lie to you
|
|
79
|
+
eventually.
|
|
54
80
|
|
|
55
81
|
## Install
|
|
56
82
|
|
|
@@ -66,22 +92,70 @@ npm install -g umpordez
|
|
|
66
92
|
umpordez
|
|
67
93
|
```
|
|
68
94
|
|
|
69
|
-
|
|
70
|
-
|
|
95
|
+
It asks **what to generate** first:
|
|
96
|
+
|
|
97
|
+
| Preset | Includes |
|
|
98
|
+
|---|---|
|
|
99
|
+
| everything | server + admin UI + site + mobile |
|
|
100
|
+
| web-only | server + admin UI + site (no mobile) |
|
|
101
|
+
| mobile-and-server | server + mobile (no UI) |
|
|
102
|
+
| just-mobile | mobile only (assumes external API) |
|
|
103
|
+
|
|
104
|
+
Then a few questions (name, domain, ports, primary color, mobile
|
|
105
|
+
bundle ID if applicable). After scaffold:
|
|
71
106
|
|
|
72
107
|
```bash
|
|
73
108
|
cd my-project
|
|
74
|
-
./install.sh
|
|
75
|
-
./
|
|
76
|
-
./
|
|
109
|
+
./install.sh # install all node deps + seed .env files
|
|
110
|
+
./install.sh --native # also expo prebuild + pod install + native
|
|
111
|
+
./seed.sh # create db + run migrations + seed admin
|
|
112
|
+
./dev.sh # start every service (mobile included by default)
|
|
113
|
+
./dev.sh --no-mobile # skip Metro this run
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Update an existing project
|
|
117
|
+
|
|
118
|
+
When the umpordez template ships fixes / new files / refactors, pull
|
|
119
|
+
them into your project:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
umpordez update <path> # walk template + project, prompt per file
|
|
123
|
+
umpordez update <path> --yes # accept all auto-updates without prompting
|
|
77
124
|
```
|
|
78
125
|
|
|
126
|
+
How it knows what's safe to auto-update: at scaffold time, the CLI
|
|
127
|
+
writes `.umpordez/manifest.json` with sha256 of every file as it was
|
|
128
|
+
generated. On update:
|
|
129
|
+
|
|
130
|
+
- File untouched since scaffold → auto-updates if template changed
|
|
131
|
+
- File user-edited → shows a diff and asks (y / N / d=full diff /
|
|
132
|
+
a=accept all)
|
|
133
|
+
- Generated assets (icons, splash, favicon) regenerate from the
|
|
134
|
+
manifest's stored primary color when missing
|
|
135
|
+
|
|
136
|
+
Random secrets (JWT, content HMAC) are persisted in the manifest
|
|
137
|
+
so updates don't mint fresh ones and break sessions / signed URLs
|
|
138
|
+
already issued by the running app.
|
|
139
|
+
|
|
140
|
+
### Generate icons
|
|
141
|
+
|
|
142
|
+
The mobile preset auto-generates a branded icon set at scaffold
|
|
143
|
+
time from your primary color + first letter of the project name. To
|
|
144
|
+
re-run later (e.g. after dropping in a real logo PNG):
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
umpordez icons --primary '#02e027' --letter U --out ./assets
|
|
148
|
+
umpordez icons --from logo.png --primary '#02e027' --out ./assets
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Outputs: `icon.png`, `adaptive-icon.png`, `splash-icon.png`,
|
|
152
|
+
`favicon.png`, `appstore.png`, `playstore.png`.
|
|
153
|
+
|
|
79
154
|
### Build for production
|
|
80
155
|
|
|
81
|
-
The build system is split in two
|
|
82
|
-
expensive and should run
|
|
83
|
-
|
|
84
|
-
stuff).
|
|
156
|
+
The build system is split in two on purpose: compilation is
|
|
157
|
+
expensive and should run locally; dependency installation needs to
|
|
158
|
+
happen on the target machine (native bindings, OS-specific stuff).
|
|
85
159
|
|
|
86
160
|
```bash
|
|
87
161
|
# Step 1: compile locally, push artifacts to builds repo
|
|
@@ -91,27 +165,61 @@ umpordez build ../app ../builds
|
|
|
91
165
|
umpordez build-deps ../builds
|
|
92
166
|
```
|
|
93
167
|
|
|
94
|
-
|
|
95
|
-
your `.env`, start the services, done.
|
|
96
|
-
|
|
97
|
-
### Other commands
|
|
168
|
+
### Mobile release
|
|
98
169
|
|
|
99
170
|
```bash
|
|
100
|
-
|
|
101
|
-
|
|
171
|
+
./scripts/bump-version.sh patch # bumps every version-holding file atomically
|
|
172
|
+
./scripts/release-android.sh # EAS local build → submit to Play (internal track)
|
|
173
|
+
# iOS: open ios/<slug>.xcworkspace and Archive, or use eas build
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### All commands
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
umpordez create a new project
|
|
180
|
+
umpordez create same
|
|
181
|
+
umpordez build <app> <builds> compile for production
|
|
182
|
+
umpordez build-deps <builds> install prod deps on target machine
|
|
183
|
+
umpordez icons [--from logo.png] generate the mobile icon set
|
|
184
|
+
umpordez update <path> [--yes] pull template changes into a project
|
|
185
|
+
umpordez --help show this help
|
|
186
|
+
umpordez --version check version
|
|
102
187
|
```
|
|
103
188
|
|
|
104
189
|
## Tech stack
|
|
105
190
|
|
|
106
|
-
| Layer
|
|
107
|
-
|
|
191
|
+
| Layer | Tech |
|
|
192
|
+
|---|---|
|
|
108
193
|
| Backend | TypeScript, Express, PostgreSQL, Knex.js, Zod |
|
|
109
|
-
| Admin UI | React 18, Vite, Tailwind, shadcn/ui (Radix), React Query v5 |
|
|
110
|
-
| Public site| Express + EJS + Tailwind |
|
|
111
|
-
|
|
|
112
|
-
|
|
|
194
|
+
| Admin UI | React 18, Vite, Tailwind, shadcn/ui (Radix), React Query v5, React Hook Form |
|
|
195
|
+
| Public site| Express + EJS + Tailwind + i18next (with hreflang/canonical/sitemap) |
|
|
196
|
+
| Mobile | Expo, React Native, TypeScript, expo-sqlite, react-native-iap, react-native-health, custom-native push |
|
|
197
|
+
| Auth | httpOnly JWT cookies (web) + Bearer + refresh rotation (mobile), Apple + Google OAuth |
|
|
198
|
+
| Uploads | AWS S3 with signed URLs |
|
|
113
199
|
| Email | Nodemailer + HTML templates |
|
|
114
200
|
|
|
201
|
+
## What changed in 1.1
|
|
202
|
+
|
|
203
|
+
The big one. v1.0.x was server + web only. v1.1 adds:
|
|
204
|
+
|
|
205
|
+
- **Mobile preset** — full Expo + RN app with auth, offline DB,
|
|
206
|
+
cloud backup, content sync, IAP, push, health.
|
|
207
|
+
- **CLI presets** — pick what you want generated up front.
|
|
208
|
+
- **`umpordez update`** — pull template changes into existing
|
|
209
|
+
projects without losing your custom code.
|
|
210
|
+
- **`umpordez icons`** — branded icon generator.
|
|
211
|
+
- **i18n** — site (URL-prefixed, SEO-best-practices) and mobile
|
|
212
|
+
(Profile picker with country flags).
|
|
213
|
+
- **Custom-native push** with cold-start tap dispatch (Swift +
|
|
214
|
+
Kotlin), Apple Sign-In + Google server-side OAuth.
|
|
215
|
+
- **Per-app `CLAUDE.md`** files + `.claude/settings.json` with a
|
|
216
|
+
permission allowlist for routine commands.
|
|
217
|
+
- **Tests scaffold** in `server/tests/` (KISS — node:test + tsx).
|
|
218
|
+
- **Production hardening** — TZ=UTC, fatal handlers,
|
|
219
|
+
trust-proxy lifted from agendalize.
|
|
220
|
+
|
|
221
|
+
Existing projects pick up everything via `umpordez update <path>`.
|
|
222
|
+
|
|
115
223
|
## License
|
|
116
224
|
|
|
117
225
|
MIT; do whatever you want with it.
|
package/cli.mjs
CHANGED
|
@@ -6,6 +6,8 @@ import { createRequire } from 'node:module';
|
|
|
6
6
|
|
|
7
7
|
import { createProject } from './create.mjs';
|
|
8
8
|
import { buildProject, buildDeps } from './build.mjs';
|
|
9
|
+
import { generateIcons } from './icons.mjs';
|
|
10
|
+
import { updateProject } from './update.mjs';
|
|
9
11
|
|
|
10
12
|
const require = createRequire(import.meta.url);
|
|
11
13
|
const { version } = require('./package.json');
|
|
@@ -33,6 +35,8 @@ function showHelp() {
|
|
|
33
35
|
console.log(` umpordez create ${dim('Create a new project')}`);
|
|
34
36
|
console.log(` umpordez build ${dim('<app> <builds>')} ${dim('Build for production')}`);
|
|
35
37
|
console.log(` umpordez build-deps ${dim('<builds>')} ${dim('Install deps in build')}`);
|
|
38
|
+
console.log(` umpordez icons ${dim('[--from logo.png]')} ${dim('Generate mobile icons')}`);
|
|
39
|
+
console.log(` umpordez update ${dim('<path> [--yes]')} ${dim('Pull template updates into a project')}`);
|
|
36
40
|
console.log(` umpordez --help ${dim('Show this help')}`);
|
|
37
41
|
console.log(` umpordez --version ${dim('Show version')}`);
|
|
38
42
|
console.log('');
|
|
@@ -71,6 +75,62 @@ async function runBuild(args) {
|
|
|
71
75
|
await buildProject(appDir, buildDir);
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
function parseFlags(args) {
|
|
79
|
+
const flags = {};
|
|
80
|
+
for (let i = 0; i < args.length; i++) {
|
|
81
|
+
const a = args[i];
|
|
82
|
+
if (!a.startsWith('--')) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const key = a.slice(2);
|
|
86
|
+
const next = args[i + 1];
|
|
87
|
+
if (next === undefined || next.startsWith('--')) {
|
|
88
|
+
flags[key] = true;
|
|
89
|
+
} else {
|
|
90
|
+
flags[key] = next;
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return flags;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function runIcons(args) {
|
|
98
|
+
const flags = parseFlags(args.slice(1));
|
|
99
|
+
const out = flags.out ?? './assets';
|
|
100
|
+
const primary = flags.primary ?? '#2563eb';
|
|
101
|
+
const letter = flags.letter ?? 'A';
|
|
102
|
+
const sourcePath = typeof flags.from === 'string' ? flags.from : null;
|
|
103
|
+
|
|
104
|
+
if (!flags.primary) {
|
|
105
|
+
console.log(dim(`No --primary given, using default ${primary}`));
|
|
106
|
+
}
|
|
107
|
+
if (!flags.letter && !sourcePath) {
|
|
108
|
+
console.log(dim(`No --letter given, using default ${letter}`));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(green(' Generating icons'));
|
|
113
|
+
console.log(dim(` Output: ${out}`));
|
|
114
|
+
console.log(dim(` Primary: ${primary}`));
|
|
115
|
+
if (sourcePath) {
|
|
116
|
+
console.log(dim(` Source: ${sourcePath}`));
|
|
117
|
+
} else {
|
|
118
|
+
console.log(dim(` Letter: ${letter}`));
|
|
119
|
+
}
|
|
120
|
+
console.log('');
|
|
121
|
+
|
|
122
|
+
const written = await generateIcons({
|
|
123
|
+
outDir: out,
|
|
124
|
+
primaryColor: primary,
|
|
125
|
+
letter: letter.charAt(0).toUpperCase(),
|
|
126
|
+
sourcePath
|
|
127
|
+
});
|
|
128
|
+
for (const p of written) {
|
|
129
|
+
console.log(` ${green('✓')} ${p}`);
|
|
130
|
+
}
|
|
131
|
+
console.log('');
|
|
132
|
+
}
|
|
133
|
+
|
|
74
134
|
async function runBuildDeps(args) {
|
|
75
135
|
const buildDir = args[1];
|
|
76
136
|
|
|
@@ -97,6 +157,21 @@ if (!command || command === 'create') {
|
|
|
97
157
|
await runBuild(args);
|
|
98
158
|
} else if (command === 'build-deps') {
|
|
99
159
|
await runBuildDeps(args);
|
|
160
|
+
} else if (command === 'icons') {
|
|
161
|
+
await runIcons(args);
|
|
162
|
+
} else if (command === 'update') {
|
|
163
|
+
const flags = parseFlags(args.slice(1));
|
|
164
|
+
const projectPath = args[1] && !args[1].startsWith('--')
|
|
165
|
+
? args[1]
|
|
166
|
+
: '.';
|
|
167
|
+
if (!projectPath) {
|
|
168
|
+
console.log(red('Usage: umpordez update <path> [--yes]'));
|
|
169
|
+
console.log(dim(
|
|
170
|
+
' --yes Accept all auto-updates without prompting.'
|
|
171
|
+
));
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
await updateProject(projectPath, { yes: !!flags.yes });
|
|
100
175
|
} else if (command === '--help' || command === '-h' || command === 'help') {
|
|
101
176
|
showHelp();
|
|
102
177
|
} else if (command === '--version' || command === '-v') {
|