umpordez 0.0.2 → 1.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.
Files changed (151) hide show
  1. package/.claude/settings.local.json +16 -0
  2. package/build.mjs +409 -0
  3. package/cli.mjs +87 -109
  4. package/create.mjs +334 -0
  5. package/package.json +2 -7
  6. package/template/.claude/settings.local.json +20 -0
  7. package/template/.editorconfig +11 -0
  8. package/template/.github/copilot-instructions.md +186 -0
  9. package/template/.nvimrc +7 -0
  10. package/template/.vscode/extensions.json +9 -0
  11. package/template/.vscode/settings.json +28 -0
  12. package/template/AGENTS.md +305 -0
  13. package/template/CLAUDE.md +140 -0
  14. package/template/architecture.md +518 -0
  15. package/template/code.md +211 -0
  16. package/template/dev.sh +45 -0
  17. package/template/install.sh +47 -0
  18. package/template/misc/nginx.conf +151 -0
  19. package/template/misc/systemd/{{PROJECT_SLUG}}-api.service +31 -0
  20. package/template/misc/systemd/{{PROJECT_SLUG}}-site-api.service +31 -0
  21. package/template/misc/systemd/{{PROJECT_SLUG}}-site.service +30 -0
  22. package/template/seed.sh +23 -0
  23. package/template/server/.env.sample +40 -0
  24. package/template/server/.prettierrc +4 -0
  25. package/template/server/apps/api/app.ts +42 -0
  26. package/template/server/apps/api/routes/account.ts +98 -0
  27. package/template/server/apps/api/routes/admin.ts +80 -0
  28. package/template/server/apps/api/routes/auth.ts +222 -0
  29. package/template/server/apps/api/routes/files.ts +89 -0
  30. package/template/server/apps/api/routes/index.ts +19 -0
  31. package/template/server/apps/api/routes/user.ts +17 -0
  32. package/template/server/apps/shared/middlewares/demand-account-access.ts +52 -0
  33. package/template/server/apps/shared/middlewares/demand-admin-user.ts +25 -0
  34. package/template/server/apps/shared/middlewares/demand-user.ts +17 -0
  35. package/template/server/apps/shared/middlewares/error-handler.ts +28 -0
  36. package/template/server/apps/shared/middlewares/request-logger.ts +40 -0
  37. package/template/server/apps/shared/middlewares/try-set-user-by-token.ts +26 -0
  38. package/template/server/apps/shared/utils.ts +71 -0
  39. package/template/server/apps/site-api/app.ts +42 -0
  40. package/template/server/apps/site-api/routes/index.ts +10 -0
  41. package/template/server/apps/site-api/routes/public.ts +25 -0
  42. package/template/server/console/index.ts +26 -0
  43. package/template/server/console/runner.ts +67 -0
  44. package/template/server/console/tasks/example.ts +29 -0
  45. package/template/server/console/tasks/seed-admin.ts +71 -0
  46. package/template/server/core/context.ts +49 -0
  47. package/template/server/core/db.ts +12 -0
  48. package/template/server/core/email.ts +87 -0
  49. package/template/server/core/errors.ts +44 -0
  50. package/template/server/core/knexfile.ts +28 -0
  51. package/template/server/core/logger.ts +60 -0
  52. package/template/server/core/models/account.ts +319 -0
  53. package/template/server/core/models/auth.ts +317 -0
  54. package/template/server/core/models/base.ts +19 -0
  55. package/template/server/core/models/user.ts +343 -0
  56. package/template/server/core/s3.ts +183 -0
  57. package/template/server/emails/_styles.css +5 -0
  58. package/template/server/emails/_template.html +28 -0
  59. package/template/server/emails/accountWelcome.html +4 -0
  60. package/template/server/emails/forgetPassword.html +5 -0
  61. package/template/server/eslint.config.js +16 -0
  62. package/template/server/knex.sh +4 -0
  63. package/template/server/migrations/20260208000000_initial-schema.ts +56 -0
  64. package/template/server/migrations/20260208000001_seed-admin.ts +42 -0
  65. package/template/server/migrations/20260208000002_add-user-avatar.ts +13 -0
  66. package/template/server/package.json +58 -0
  67. package/template/server/tsconfig.json +27 -0
  68. package/template/server/types/api.ts +20 -0
  69. package/template/server/types/express.d.ts +35 -0
  70. package/template/ui/admin/.prettierrc +4 -0
  71. package/template/ui/admin/components.json +20 -0
  72. package/template/ui/admin/eslint.config.js +16 -0
  73. package/template/ui/admin/index.html +12 -0
  74. package/template/ui/admin/package.json +57 -0
  75. package/template/ui/admin/postcss.config.js +6 -0
  76. package/template/ui/admin/src/app.tsx +13 -0
  77. package/template/ui/admin/src/components/nav-user.tsx +95 -0
  78. package/template/ui/admin/src/components/theme-provider.tsx +65 -0
  79. package/template/ui/admin/src/components/theme-toggle.tsx +17 -0
  80. package/template/ui/admin/src/components/ui/badge.tsx +36 -0
  81. package/template/ui/admin/src/components/ui/button.tsx +56 -0
  82. package/template/ui/admin/src/components/ui/calendar.tsx +68 -0
  83. package/template/ui/admin/src/components/ui/card.tsx +79 -0
  84. package/template/ui/admin/src/components/ui/checkbox.tsx +28 -0
  85. package/template/ui/admin/src/components/ui/date-picker.tsx +78 -0
  86. package/template/ui/admin/src/components/ui/date-range-picker.tsx +208 -0
  87. package/template/ui/admin/src/components/ui/dialog.tsx +121 -0
  88. package/template/ui/admin/src/components/ui/dropdown-menu.tsx +200 -0
  89. package/template/ui/admin/src/components/ui/input.tsx +22 -0
  90. package/template/ui/admin/src/components/ui/label.tsx +24 -0
  91. package/template/ui/admin/src/components/ui/popover.tsx +29 -0
  92. package/template/ui/admin/src/components/ui/select.tsx +158 -0
  93. package/template/ui/admin/src/components/ui/separator.tsx +29 -0
  94. package/template/ui/admin/src/components/ui/sheet.tsx +136 -0
  95. package/template/ui/admin/src/components/ui/sidebar.tsx +759 -0
  96. package/template/ui/admin/src/components/ui/skeleton.tsx +15 -0
  97. package/template/ui/admin/src/components/ui/sonner.tsx +26 -0
  98. package/template/ui/admin/src/components/ui/switch.tsx +27 -0
  99. package/template/ui/admin/src/components/ui/table.tsx +117 -0
  100. package/template/ui/admin/src/components/ui/tabs.tsx +53 -0
  101. package/template/ui/admin/src/components/ui/textarea.tsx +22 -0
  102. package/template/ui/admin/src/components/ui/tooltip.tsx +28 -0
  103. package/template/ui/admin/src/hooks/queries/use-accounts.ts +68 -0
  104. package/template/ui/admin/src/hooks/queries/use-auth.ts +61 -0
  105. package/template/ui/admin/src/hooks/queries/use-dashboard.ts +70 -0
  106. package/template/ui/admin/src/hooks/queries/use-members.ts +90 -0
  107. package/template/ui/admin/src/hooks/queries/use-users.ts +99 -0
  108. package/template/ui/admin/src/hooks/use-confirm.tsx +92 -0
  109. package/template/ui/admin/src/hooks/use-mobile.tsx +22 -0
  110. package/template/ui/admin/src/hooks/use-sidebar.tsx +4 -0
  111. package/template/ui/admin/src/hooks/use-user.tsx +86 -0
  112. package/template/ui/admin/src/index.css +22 -0
  113. package/template/ui/admin/src/layouts/account.tsx +278 -0
  114. package/template/ui/admin/src/layouts/admin.tsx +182 -0
  115. package/template/ui/admin/src/layouts/public.tsx +9 -0
  116. package/template/ui/admin/src/layouts/user.tsx +107 -0
  117. package/template/ui/admin/src/lib/config.ts +9 -0
  118. package/template/ui/admin/src/lib/fetch-api.ts +101 -0
  119. package/template/ui/admin/src/lib/query-client.ts +12 -0
  120. package/template/ui/admin/src/lib/query-keys.ts +44 -0
  121. package/template/ui/admin/src/lib/utils.ts +24 -0
  122. package/template/ui/admin/src/main.tsx +17 -0
  123. package/template/ui/admin/src/pages/account/dashboard.tsx +128 -0
  124. package/template/ui/admin/src/pages/account/members.tsx +414 -0
  125. package/template/ui/admin/src/pages/account/settings.tsx +210 -0
  126. package/template/ui/admin/src/pages/admin/accounts.tsx +164 -0
  127. package/template/ui/admin/src/pages/admin/dashboard.tsx +243 -0
  128. package/template/ui/admin/src/pages/admin/users.tsx +395 -0
  129. package/template/ui/admin/src/pages/public/error.tsx +24 -0
  130. package/template/ui/admin/src/pages/public/forgot-password.tsx +74 -0
  131. package/template/ui/admin/src/pages/public/login.tsx +102 -0
  132. package/template/ui/admin/src/pages/public/reset-password.tsx +106 -0
  133. package/template/ui/admin/src/pages/public/signup.tsx +118 -0
  134. package/template/ui/admin/src/pages/user/profile.tsx +313 -0
  135. package/template/ui/admin/src/pages/user/select-account.tsx +114 -0
  136. package/template/ui/admin/src/router.tsx +71 -0
  137. package/template/ui/admin/tailwind.config.ts +71 -0
  138. package/template/ui/admin/tsconfig.json +24 -0
  139. package/template/ui/admin/vite.config.ts +15 -0
  140. package/template/ui/site/.env.sample +3 -0
  141. package/template/ui/site/.prettierrc +4 -0
  142. package/template/ui/site/app.ts +50 -0
  143. package/template/ui/site/eslint.config.js +16 -0
  144. package/template/ui/site/package.json +33 -0
  145. package/template/ui/site/styles/input.css +3 -0
  146. package/template/ui/site/tailwind.config.js +8 -0
  147. package/template/ui/site/tsconfig.json +15 -0
  148. package/template/ui/site/views/pages/home.ejs +305 -0
  149. package/template/ui/site/views/partials/footer.ejs +44 -0
  150. package/template/ui/site/views/partials/header.ejs +83 -0
  151. package/template-variables.mjs +186 -0
@@ -0,0 +1,16 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(chmod:*)",
5
+ "Bash(node -e:*)",
6
+ "Bash(grep:*)",
7
+ "Bash(find:*)",
8
+ "Bash(xargs cat:*)",
9
+ "Bash(node:*)",
10
+ "Bash(echo:*)",
11
+ "WebFetch(domain:umpordez.com)",
12
+ "Bash(curl:*)",
13
+ "Bash(python3:*)"
14
+ ]
15
+ }
16
+ }
package/build.mjs ADDED
@@ -0,0 +1,409 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { execSync } from 'node:child_process';
5
+
6
+ // umpordez.com design tokens
7
+ const green = chalk.hex('#02e027');
8
+ const cyan = chalk.hex('#00fff9');
9
+ const red = chalk.hex('#ff3c00');
10
+ const dim = chalk.hex('#a0a0a0');
11
+
12
+ function run(cmd, cwd) {
13
+ execSync(cmd, { cwd, stdio: 'inherit' });
14
+ }
15
+
16
+ function runSilent(cmd, cwd) {
17
+ execSync(cmd, { cwd, stdio: 'ignore' });
18
+ }
19
+
20
+ // ─── build ───────────────────────────────────────────────────────────────────
21
+ // Run locally. Compiles TypeScript + Vite, moves artifacts to builds repo.
22
+ // Does NOT install node_modules — that's build-deps on the target machine.
23
+
24
+ export async function buildProject(appDir, buildDir) {
25
+ appDir = path.resolve(appDir);
26
+ buildDir = path.resolve(buildDir);
27
+
28
+ if (!fs.existsSync(appDir)) {
29
+ console.log(red(`App directory not found: ${appDir}`));
30
+ process.exit(1);
31
+ }
32
+
33
+ if (!fs.existsSync(buildDir)) {
34
+ console.log(red(`Build directory not found: ${buildDir}`));
35
+ console.log(dim('Create it first: mkdir -p ' + buildDir));
36
+ process.exit(1);
37
+ }
38
+
39
+ console.log('');
40
+ console.log(green.bold('=== umpordez build ==='));
41
+ console.log('');
42
+ console.log(` ${chalk.bold('Source:')} ${cyan(appDir)}`);
43
+ console.log(` ${chalk.bold('Output:')} ${cyan(buildDir)}`);
44
+ console.log('');
45
+
46
+ // Pull builds repo if git
47
+ if (fs.existsSync(path.join(buildDir, '.git'))) {
48
+ console.log(dim(' Pulling builds repo...'));
49
+ runSilent('git pull', buildDir);
50
+ }
51
+
52
+ // Bump build number
53
+ const buildNumberFile = path.join(buildDir, 'build-number');
54
+ let number = 0;
55
+ if (fs.existsSync(buildNumberFile)) {
56
+ number = parseInt(
57
+ fs.readFileSync(buildNumberFile, 'utf-8').trim(),
58
+ 10,
59
+ ) || 0;
60
+ }
61
+ const nextNumber = number + 1;
62
+
63
+ // ─── Admin UI ────────────────────────────────────────────────
64
+ // Static files (HTML/CSS/JS) — no node_modules needed at runtime
65
+
66
+ const adminDir = path.join(appDir, 'ui', 'admin');
67
+ if (fs.existsSync(adminDir)) {
68
+ console.log(green('[1/3]') + ' Building Admin UI...');
69
+
70
+ const distDir = path.join(adminDir, 'dist');
71
+ if (fs.existsSync(distDir)) {
72
+ fs.rmSync(distDir, { recursive: true });
73
+ }
74
+
75
+ run('npm run build', adminDir);
76
+
77
+ const buildAdminOut = path.join(buildDir, 'ui', 'admin');
78
+ if (fs.existsSync(buildAdminOut)) {
79
+ fs.rmSync(buildAdminOut, { recursive: true });
80
+ }
81
+ fs.mkdirSync(buildAdminOut, { recursive: true });
82
+
83
+ run(`rsync -av dist/* "${buildAdminOut}"`, adminDir);
84
+ }
85
+
86
+ // ─── Site UI ─────────────────────────────────────────────────
87
+ // Express + EJS — needs node_modules at runtime (installed by build-deps)
88
+
89
+ const siteDir = path.join(appDir, 'ui', 'site');
90
+ if (fs.existsSync(siteDir)) {
91
+ console.log(green('[2/3]') + ' Building Site UI...');
92
+
93
+ const siteBuildDir = path.join(siteDir, 'build');
94
+ if (fs.existsSync(siteBuildDir)) {
95
+ fs.rmSync(siteBuildDir, { recursive: true });
96
+ }
97
+
98
+ run('npm run build', siteDir);
99
+
100
+ const buildSiteOut = path.join(buildDir, 'ui', 'site');
101
+
102
+ // Preserve node_modules + .env from previous build-deps
103
+ const tmpModules = `/tmp/_umpordez_site_modules_${nextNumber}`;
104
+ const siteModules = path.join(buildSiteOut, 'node_modules');
105
+ if (fs.existsSync(siteModules)) {
106
+ fs.renameSync(siteModules, tmpModules);
107
+ }
108
+
109
+ const tmpEnv = `/tmp/_umpordez_site_env_${nextNumber}`;
110
+ const siteEnv = path.join(buildSiteOut, '.env');
111
+ if (fs.existsSync(siteEnv)) {
112
+ fs.renameSync(siteEnv, tmpEnv);
113
+ }
114
+
115
+ if (fs.existsSync(buildSiteOut)) {
116
+ fs.rmSync(buildSiteOut, { recursive: true });
117
+ }
118
+ fs.mkdirSync(buildSiteOut, { recursive: true });
119
+
120
+ run(`rsync -av build/ "${buildSiteOut}/"`, siteDir);
121
+
122
+ const viewsDir = path.join(siteDir, 'views');
123
+ if (fs.existsSync(viewsDir)) {
124
+ run(`rsync -av views "${buildSiteOut}/"`, siteDir);
125
+ }
126
+
127
+ const publicDir = path.join(siteDir, 'public');
128
+ if (fs.existsSync(publicDir)) {
129
+ run(`rsync -av public "${buildSiteOut}/"`, siteDir);
130
+ }
131
+
132
+ fs.copyFileSync(
133
+ path.join(siteDir, 'package.json'),
134
+ path.join(buildSiteOut, 'package.json'),
135
+ );
136
+
137
+ const lockFile = path.join(siteDir, 'package-lock.json');
138
+ if (fs.existsSync(lockFile)) {
139
+ fs.copyFileSync(
140
+ lockFile,
141
+ path.join(buildSiteOut, 'package-lock.json'),
142
+ );
143
+ }
144
+
145
+ // Restore .env and node_modules from previous build-deps
146
+ if (fs.existsSync(tmpEnv)) {
147
+ fs.renameSync(tmpEnv, path.join(buildSiteOut, '.env'));
148
+ }
149
+ if (fs.existsSync(tmpModules)) {
150
+ fs.renameSync(
151
+ tmpModules,
152
+ path.join(buildSiteOut, 'node_modules'),
153
+ );
154
+ }
155
+ }
156
+
157
+ // ─── Server ──────────────────────────────────────────────────
158
+ // Node.js — needs node_modules at runtime (installed by build-deps)
159
+
160
+ const serverDir = path.join(appDir, 'server');
161
+ if (fs.existsSync(serverDir)) {
162
+ console.log(green('[3/3]') + ' Building Server...');
163
+
164
+ const serverBuildDir = path.join(serverDir, 'build');
165
+ if (fs.existsSync(serverBuildDir)) {
166
+ fs.rmSync(serverBuildDir, { recursive: true });
167
+ }
168
+
169
+ run('npm run build', serverDir);
170
+
171
+ fs.copyFileSync(
172
+ path.join(serverDir, 'package.json'),
173
+ path.join(serverBuildDir, 'package.json'),
174
+ );
175
+
176
+ const serverLock = path.join(serverDir, 'package-lock.json');
177
+ if (fs.existsSync(serverLock)) {
178
+ fs.copyFileSync(
179
+ serverLock,
180
+ path.join(serverBuildDir, 'package-lock.json'),
181
+ );
182
+ }
183
+
184
+ // Copy email templates
185
+ const emailsDir = path.join(serverDir, 'emails');
186
+ if (fs.existsSync(emailsDir)) {
187
+ const buildEmails = path.join(serverBuildDir, 'emails');
188
+ fs.mkdirSync(buildEmails, { recursive: true });
189
+ for (const f of fs.readdirSync(emailsDir)) {
190
+ fs.copyFileSync(
191
+ path.join(emailsDir, f),
192
+ path.join(buildEmails, f),
193
+ );
194
+ }
195
+ }
196
+
197
+ // Remove .ts and .map from compiled migrations
198
+ const migrationsDir = path.join(serverBuildDir, 'migrations');
199
+ if (fs.existsSync(migrationsDir)) {
200
+ for (const f of fs.readdirSync(migrationsDir)) {
201
+ if (f.endsWith('.ts') || f.endsWith('.map')) {
202
+ fs.unlinkSync(path.join(migrationsDir, f));
203
+ }
204
+ }
205
+ }
206
+
207
+ const buildServerOut = path.join(buildDir, 'server');
208
+
209
+ // Preserve node_modules and .env from previous build-deps
210
+ const tmpServerModules = `/tmp/_umpordez_server_modules_${nextNumber}`;
211
+ const serverModules = path.join(buildServerOut, 'node_modules');
212
+ if (fs.existsSync(serverModules)) {
213
+ fs.renameSync(serverModules, tmpServerModules);
214
+ }
215
+
216
+ const tmpServerEnv = `/tmp/_umpordez_server_env_${nextNumber}`;
217
+ const serverEnv = path.join(buildServerOut, '.env');
218
+ if (fs.existsSync(serverEnv)) {
219
+ fs.renameSync(serverEnv, tmpServerEnv);
220
+ }
221
+
222
+ if (fs.existsSync(buildServerOut)) {
223
+ fs.rmSync(buildServerOut, { recursive: true });
224
+ }
225
+ fs.mkdirSync(buildServerOut, { recursive: true });
226
+
227
+ run(`rsync -av build/* "${buildServerOut}"`, serverDir);
228
+
229
+ // Write knex.sh for the build repo (local knex + .js knexfile)
230
+ const knexSh = '#!/bin/bash\n'
231
+ + 'node ./node_modules/knex/bin/cli.js \\\n'
232
+ + ' --knexfile core/knexfile.js \\\n'
233
+ + ' --migrations-directory ../migrations $@\n';
234
+ fs.writeFileSync(
235
+ path.join(buildServerOut, 'knex.sh'),
236
+ knexSh,
237
+ { mode: 0o755 }
238
+ );
239
+
240
+ // Restore node_modules and .env
241
+ if (fs.existsSync(tmpServerEnv)) {
242
+ fs.renameSync(tmpServerEnv, path.join(buildServerOut, '.env'));
243
+ }
244
+ if (fs.existsSync(tmpServerModules)) {
245
+ fs.renameSync(
246
+ tmpServerModules,
247
+ path.join(buildServerOut, 'node_modules')
248
+ );
249
+ }
250
+ }
251
+
252
+ // ─── Version & Git ───────────────────────────────────────────
253
+
254
+ fs.writeFileSync(buildNumberFile, String(nextNumber), 'utf-8');
255
+
256
+ if (fs.existsSync(path.join(buildDir, '.git'))) {
257
+ console.log(dim(' Committing build...'));
258
+
259
+ try {
260
+ run('git add .', buildDir);
261
+ run(
262
+ `git commit -m "build: #${nextNumber}"`,
263
+ buildDir,
264
+ );
265
+ } catch {
266
+ // nothing to commit
267
+ }
268
+
269
+ // Tag if doesn't exist
270
+ try {
271
+ execSync(
272
+ `git rev-parse "${nextNumber}"`,
273
+ { cwd: buildDir, stdio: 'ignore' },
274
+ );
275
+ } catch {
276
+ run(
277
+ `git tag -a "${nextNumber}" -m "#${nextNumber}"`,
278
+ buildDir,
279
+ );
280
+ run('git push origin --tags', buildDir);
281
+ }
282
+
283
+ run('git push', buildDir);
284
+ }
285
+
286
+ console.log('');
287
+ console.log(green.bold(`=== Build #${nextNumber} complete ===`));
288
+ console.log('');
289
+ console.log(` ${chalk.bold('Admin UI:')} ${cyan(path.join(buildDir, 'ui', 'admin'))}`);
290
+ console.log(` ${chalk.bold('Site UI:')} ${cyan(path.join(buildDir, 'ui', 'site'))}`);
291
+ console.log(` ${chalk.bold('Server:')} ${cyan(path.join(buildDir, 'server'))}`);
292
+ console.log('');
293
+ console.log(dim(' Next: run umpordez build-deps on the target machine'));
294
+ console.log('');
295
+ }
296
+
297
+ // ─── build-deps ──────────────────────────────────────────────────────────────
298
+ // Run on prod/staging. Installs production node_modules in the builds repo
299
+ // so native bindings match the target OS. After this, the repo is ready to run.
300
+
301
+ export async function buildDeps(buildDir) {
302
+ buildDir = path.resolve(buildDir);
303
+
304
+ if (!fs.existsSync(buildDir)) {
305
+ console.log(red(`Build directory not found: ${buildDir}`));
306
+ console.log(dim('Run umpordez build first.'));
307
+ process.exit(1);
308
+ }
309
+
310
+ console.log('');
311
+ console.log(green.bold('=== umpordez build-deps ==='));
312
+ console.log('');
313
+ console.log(` ${chalk.bold('Output:')} ${cyan(buildDir)}`);
314
+ console.log('');
315
+
316
+ // Pull builds repo if git
317
+ if (fs.existsSync(path.join(buildDir, '.git'))) {
318
+ console.log(dim(' Pulling builds repo...'));
319
+ runSilent('git pull', buildDir);
320
+ }
321
+
322
+ let step = 1;
323
+ const parts = [];
324
+
325
+ const buildServerOut = path.join(buildDir, 'server');
326
+ const buildSiteOut = path.join(buildDir, 'ui', 'site');
327
+
328
+ if (fs.existsSync(buildServerOut)) {
329
+ parts.push('server');
330
+ }
331
+ if (fs.existsSync(buildSiteOut)) {
332
+ parts.push('site');
333
+ }
334
+
335
+ if (parts.length === 0) {
336
+ console.log(red('No build output found.'));
337
+ console.log(dim('Run umpordez build first.'));
338
+ process.exit(1);
339
+ }
340
+
341
+ const total = parts.length;
342
+
343
+ // ─── Server deps ─────────────────────────────────────────────
344
+
345
+ if (parts.includes('server')) {
346
+ console.log(
347
+ green(`[${step}/${total}]`)
348
+ + ' Installing server dependencies...',
349
+ );
350
+
351
+ const serverModules = path.join(buildServerOut, 'node_modules');
352
+ if (fs.existsSync(serverModules)) {
353
+ fs.rmSync(serverModules, { recursive: true });
354
+ }
355
+
356
+ try {
357
+ run('npm ci --omit=dev', buildServerOut);
358
+ } catch {
359
+ run('npm install --omit=dev', buildServerOut);
360
+ }
361
+
362
+ step++;
363
+ }
364
+
365
+ // ─── Site UI deps ────────────────────────────────────────────
366
+
367
+ if (parts.includes('site')) {
368
+ console.log(
369
+ green(`[${step}/${total}]`)
370
+ + ' Installing site dependencies...',
371
+ );
372
+
373
+ const siteModules = path.join(buildSiteOut, 'node_modules');
374
+ if (fs.existsSync(siteModules)) {
375
+ fs.rmSync(siteModules, { recursive: true });
376
+ }
377
+
378
+ try {
379
+ run('npm ci --omit=dev', buildSiteOut);
380
+ } catch {
381
+ run('npm install --omit=dev', buildSiteOut);
382
+ }
383
+
384
+ step++;
385
+ }
386
+
387
+ // ─── Git commit ──────────────────────────────────────────────
388
+
389
+ if (fs.existsSync(path.join(buildDir, '.git'))) {
390
+ console.log(dim(' Committing dependencies...'));
391
+
392
+ try {
393
+ run('git add .', buildDir);
394
+ run(
395
+ 'git commit -m "deps: update node_modules"',
396
+ buildDir,
397
+ );
398
+ run('git push', buildDir);
399
+ } catch {
400
+ console.log(dim(' No dependency changes to commit.'));
401
+ }
402
+ }
403
+
404
+ console.log('');
405
+ console.log(green.bold('=== Dependencies ready ==='));
406
+ console.log('');
407
+ console.log(dim(' Add .env and start the services.'));
408
+ console.log('');
409
+ }
package/cli.mjs CHANGED
@@ -1,130 +1,108 @@
1
- import inquirer from 'inquirer';
1
+ #!/usr/bin/env node
2
+
2
3
  import chalk from 'chalk';
3
4
  import figlet from 'figlet';
5
+ import { createRequire } from 'node:module';
4
6
 
5
- import fs from 'node:fs';
6
-
7
- console.log(chalk.green(figlet.textSync('umpordez')));
8
- console.log('');
9
-
10
- function sample(arr) {
11
- return arr[Math.floor(Math.random() * arr.length)];
12
- }
7
+ import { createProject } from './create.mjs';
8
+ import { buildProject, buildDeps } from './build.mjs';
13
9
 
14
- async function saveUser(user) {
15
- await fs.promises.writeFile('_user.json', JSON.stringify(user));
10
+ const require = createRequire(import.meta.url);
11
+ const { version } = require('./package.json');
16
12
 
17
- }
13
+ // umpordez.com design tokens
14
+ const green = chalk.hex('#02e027');
15
+ const cyan = chalk.hex('#00fff9');
16
+ const red = chalk.hex('#ff3c00');
17
+ const dim = chalk.hex('#a0a0a0');
18
18
 
19
- async function getUser() {
20
- try {
21
- const data = await fs.promises.readFile('_user.json');
22
- return JSON.parse(data);
23
- } catch (ex) {
24
- console.error(ex);
25
- }
19
+ function showBanner() {
20
+ console.log('');
21
+ console.log(green(figlet.textSync('umpordez', { font: 'Small' })));
22
+ console.log(dim(' SaaS starter kit generator'));
23
+ console.log(dim(' TypeScript + React + PostgreSQL + multi-tenant'));
24
+ console.log(cyan(' youtube.com/ligeiro'));
25
+ console.log('');
26
26
  }
27
27
 
28
- let user = await getUser();
28
+ function showHelp() {
29
+ showBanner();
29
30
 
30
- if (user?.id) {
31
- console.log(`Logged in as /${user.login} \o/`);
31
+ console.log(green.bold('Usage:'));
32
+ console.log(` umpordez ${dim('Create a new project')}`);
33
+ console.log(` umpordez create ${dim('Create a new project')}`);
34
+ console.log(` umpordez build ${dim('<app> <builds>')} ${dim('Build for production')}`);
35
+ console.log(` umpordez build-deps ${dim('<builds>')} ${dim('Install deps in build')}`);
36
+ console.log(` umpordez --help ${dim('Show this help')}`);
37
+ console.log(` umpordez --version ${dim('Show version')}`);
38
+ console.log('');
39
+ console.log(green.bold('What you get:'));
40
+ console.log(` ${green('>')} Multi-tenant auth (cookie-based JWT)`);
41
+ console.log(` ${green('>')} Admin API + Site API (Express)`);
42
+ console.log(` ${green('>')} React admin panel (Vite + shadcn/ui)`);
43
+ console.log(` ${green('>')} PostgreSQL + Knex.js migrations`);
44
+ console.log(` ${green('>')} S3 file uploads with signed URLs`);
45
+ console.log(` ${green('>')} EJS-based public site`);
46
+ console.log(` ${green('>')} Console task runner`);
47
+ console.log(` ${green('>')} Role-based access (admin/owner/manager/user)`);
48
+ console.log(` ${green('>')} Production build system with versioning`);
32
49
  console.log('');
33
50
  }
34
51
 
35
- const API_URL = `http://localhost:9000/rest`;
36
-
37
- async function tryAuthenticate(token) {
38
- const res = await fetch(API_URL, {
39
- headers: {
40
- Authorization: token
41
- }
42
- });
43
-
44
- return res.json();
52
+ function showVersion() {
53
+ console.log(`${green('umpordez')} ${dim('v' + version)}`);
45
54
  }
46
55
 
47
- function getChoices() {
48
- const choices = {
49
- 'I want to authenticate myself': async () => {
50
- const res = await inquirer.prompt([
51
- {
52
- type: 'password',
53
- name: 'key',
54
- message: `So, please tell me... what's your key?`
55
- }
56
- ]);
57
-
58
- await saveUser({ id: null, token: res.key });
59
-
60
- console.log('');
61
- console.log(chalk.green('Cool!! 😎 trying to login'));
62
- console.log(chalk.green('...'));
63
-
64
- const authUser = await tryAuthenticate(res.key);
65
-
66
- if (!authUser || !authUser.id) {
67
- console.error('OH NO, invalid credentials or something with your network dude');
68
- return promptMain();
69
- }
70
-
71
- await saveUser({ ...authUser, token: res.key });
72
-
73
- user = authUser;
74
- console.log(`Hello hello ${user.login}!!!`);
75
-
76
- return promptMain();
77
- },
78
- 'I want to logout': async () => {
79
- await saveUser({});
80
- user = {};
81
-
82
- console.log(chalk.green('ciao'));
83
- return promptMain();
84
- },
85
- 'I want to leave': () => {
86
- console.log(chalk.green('So, my friend...'));
87
- console.log(chalk.green('farewell o/'));
88
- },
89
- };
90
-
91
- if (user?.id) {
92
- delete choices['I want to authenticate myself'];
93
- } else {
94
- delete choices['I want to logout'];
56
+ async function runBuild(args) {
57
+ const appDir = args[1];
58
+ const buildDir = args[2];
59
+
60
+ if (!appDir || !buildDir) {
61
+ console.log(red('Usage: umpordez build <app-dir> <build-dir>'));
62
+ console.log('');
63
+ console.log(dim(' <app-dir> Path to the project (source)'));
64
+ console.log(dim(' <build-dir> Path to the builds output'));
65
+ console.log('');
66
+ console.log(dim(' Example:'));
67
+ console.log(` ${green('$')} umpordez build ../app ../builds`);
68
+ process.exit(1);
95
69
  }
96
70
 
97
- return choices;
71
+ await buildProject(appDir, buildDir);
98
72
  }
99
73
 
74
+ async function runBuildDeps(args) {
75
+ const buildDir = args[1];
76
+
77
+ if (!buildDir) {
78
+ console.log(red('Usage: umpordez build-deps <build-dir>'));
79
+ console.log('');
80
+ console.log(dim(' <build-dir> Path to the builds output'));
81
+ console.log('');
82
+ console.log(dim(' Example:'));
83
+ console.log(` ${green('$')} umpordez build-deps ../builds`);
84
+ process.exit(1);
85
+ }
100
86
 
101
- const welcomeMessages = [
102
- 'hello, my young padawan',
103
- 'how you doin?',
104
- 'whats up?',
105
- 'heeeey yooo'
106
- ];
107
-
108
-
109
- async function promptMain() {
110
- const choices = getChoices();
111
-
112
- const option = await inquirer.prompt([
113
- {
114
- type: 'rawlist',
115
- name: 'task',
116
- message: `${sample(welcomeMessages)}\n Please, choose one:`,
117
- choices: Object.keys(choices)
118
- }
119
- ]);
120
-
121
-
122
- console.log('');
123
- console.log('You have choose:');
124
- console.log(chalk.cyan(option.task));
125
- console.log('');
126
-
127
- await choices[option.task]();
87
+ await buildDeps(buildDir);
128
88
  }
129
89
 
130
- await promptMain();
90
+ const args = process.argv.slice(2);
91
+ const command = args[0];
92
+
93
+ if (!command || command === 'create') {
94
+ showBanner();
95
+ await createProject();
96
+ } else if (command === 'build') {
97
+ await runBuild(args);
98
+ } else if (command === 'build-deps') {
99
+ await runBuildDeps(args);
100
+ } else if (command === '--help' || command === '-h' || command === 'help') {
101
+ showHelp();
102
+ } else if (command === '--version' || command === '-v') {
103
+ showVersion();
104
+ } else {
105
+ console.log(red(`Unknown command: ${command}`));
106
+ console.log(`Run ${green('umpordez --help')} to see available commands.`);
107
+ process.exit(1);
108
+ }