umpordez 0.0.3 → 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.
- package/.claude/settings.local.json +16 -0
- package/build.mjs +409 -0
- package/cli.mjs +85 -107
- package/create.mjs +334 -0
- package/package.json +2 -7
- package/template/.claude/settings.local.json +20 -0
- package/template/.editorconfig +11 -0
- package/template/.github/copilot-instructions.md +186 -0
- package/template/.nvimrc +7 -0
- package/template/.vscode/extensions.json +9 -0
- package/template/.vscode/settings.json +28 -0
- package/template/AGENTS.md +305 -0
- package/template/CLAUDE.md +140 -0
- package/template/architecture.md +518 -0
- package/template/code.md +211 -0
- package/template/dev.sh +45 -0
- package/template/install.sh +47 -0
- package/template/misc/nginx.conf +151 -0
- package/template/misc/systemd/{{PROJECT_SLUG}}-api.service +31 -0
- package/template/misc/systemd/{{PROJECT_SLUG}}-site-api.service +31 -0
- package/template/misc/systemd/{{PROJECT_SLUG}}-site.service +30 -0
- package/template/seed.sh +23 -0
- package/template/server/.env.sample +40 -0
- package/template/server/.prettierrc +4 -0
- package/template/server/apps/api/app.ts +42 -0
- package/template/server/apps/api/routes/account.ts +98 -0
- package/template/server/apps/api/routes/admin.ts +80 -0
- package/template/server/apps/api/routes/auth.ts +222 -0
- package/template/server/apps/api/routes/files.ts +89 -0
- package/template/server/apps/api/routes/index.ts +19 -0
- package/template/server/apps/api/routes/user.ts +17 -0
- package/template/server/apps/shared/middlewares/demand-account-access.ts +52 -0
- package/template/server/apps/shared/middlewares/demand-admin-user.ts +25 -0
- package/template/server/apps/shared/middlewares/demand-user.ts +17 -0
- package/template/server/apps/shared/middlewares/error-handler.ts +28 -0
- package/template/server/apps/shared/middlewares/request-logger.ts +40 -0
- package/template/server/apps/shared/middlewares/try-set-user-by-token.ts +26 -0
- package/template/server/apps/shared/utils.ts +71 -0
- package/template/server/apps/site-api/app.ts +42 -0
- package/template/server/apps/site-api/routes/index.ts +10 -0
- package/template/server/apps/site-api/routes/public.ts +25 -0
- package/template/server/console/index.ts +26 -0
- package/template/server/console/runner.ts +67 -0
- package/template/server/console/tasks/example.ts +29 -0
- package/template/server/console/tasks/seed-admin.ts +71 -0
- package/template/server/core/context.ts +49 -0
- package/template/server/core/db.ts +12 -0
- package/template/server/core/email.ts +87 -0
- package/template/server/core/errors.ts +44 -0
- package/template/server/core/knexfile.ts +28 -0
- package/template/server/core/logger.ts +60 -0
- package/template/server/core/models/account.ts +319 -0
- package/template/server/core/models/auth.ts +317 -0
- package/template/server/core/models/base.ts +19 -0
- package/template/server/core/models/user.ts +343 -0
- package/template/server/core/s3.ts +183 -0
- package/template/server/emails/_styles.css +5 -0
- package/template/server/emails/_template.html +28 -0
- package/template/server/emails/accountWelcome.html +4 -0
- package/template/server/emails/forgetPassword.html +5 -0
- package/template/server/eslint.config.js +16 -0
- package/template/server/knex.sh +4 -0
- package/template/server/migrations/20260208000000_initial-schema.ts +56 -0
- package/template/server/migrations/20260208000001_seed-admin.ts +42 -0
- package/template/server/migrations/20260208000002_add-user-avatar.ts +13 -0
- package/template/server/package.json +58 -0
- package/template/server/tsconfig.json +27 -0
- package/template/server/types/api.ts +20 -0
- package/template/server/types/express.d.ts +35 -0
- package/template/ui/admin/.prettierrc +4 -0
- package/template/ui/admin/components.json +20 -0
- package/template/ui/admin/eslint.config.js +16 -0
- package/template/ui/admin/index.html +12 -0
- package/template/ui/admin/package.json +57 -0
- package/template/ui/admin/postcss.config.js +6 -0
- package/template/ui/admin/src/app.tsx +13 -0
- package/template/ui/admin/src/components/nav-user.tsx +95 -0
- package/template/ui/admin/src/components/theme-provider.tsx +65 -0
- package/template/ui/admin/src/components/theme-toggle.tsx +17 -0
- package/template/ui/admin/src/components/ui/badge.tsx +36 -0
- package/template/ui/admin/src/components/ui/button.tsx +56 -0
- package/template/ui/admin/src/components/ui/calendar.tsx +68 -0
- package/template/ui/admin/src/components/ui/card.tsx +79 -0
- package/template/ui/admin/src/components/ui/checkbox.tsx +28 -0
- package/template/ui/admin/src/components/ui/date-picker.tsx +78 -0
- package/template/ui/admin/src/components/ui/date-range-picker.tsx +208 -0
- package/template/ui/admin/src/components/ui/dialog.tsx +121 -0
- package/template/ui/admin/src/components/ui/dropdown-menu.tsx +200 -0
- package/template/ui/admin/src/components/ui/input.tsx +22 -0
- package/template/ui/admin/src/components/ui/label.tsx +24 -0
- package/template/ui/admin/src/components/ui/popover.tsx +29 -0
- package/template/ui/admin/src/components/ui/select.tsx +158 -0
- package/template/ui/admin/src/components/ui/separator.tsx +29 -0
- package/template/ui/admin/src/components/ui/sheet.tsx +136 -0
- package/template/ui/admin/src/components/ui/sidebar.tsx +759 -0
- package/template/ui/admin/src/components/ui/skeleton.tsx +15 -0
- package/template/ui/admin/src/components/ui/sonner.tsx +26 -0
- package/template/ui/admin/src/components/ui/switch.tsx +27 -0
- package/template/ui/admin/src/components/ui/table.tsx +117 -0
- package/template/ui/admin/src/components/ui/tabs.tsx +53 -0
- package/template/ui/admin/src/components/ui/textarea.tsx +22 -0
- package/template/ui/admin/src/components/ui/tooltip.tsx +28 -0
- package/template/ui/admin/src/hooks/queries/use-accounts.ts +68 -0
- package/template/ui/admin/src/hooks/queries/use-auth.ts +61 -0
- package/template/ui/admin/src/hooks/queries/use-dashboard.ts +70 -0
- package/template/ui/admin/src/hooks/queries/use-members.ts +90 -0
- package/template/ui/admin/src/hooks/queries/use-users.ts +99 -0
- package/template/ui/admin/src/hooks/use-confirm.tsx +92 -0
- package/template/ui/admin/src/hooks/use-mobile.tsx +22 -0
- package/template/ui/admin/src/hooks/use-sidebar.tsx +4 -0
- package/template/ui/admin/src/hooks/use-user.tsx +86 -0
- package/template/ui/admin/src/index.css +22 -0
- package/template/ui/admin/src/layouts/account.tsx +278 -0
- package/template/ui/admin/src/layouts/admin.tsx +182 -0
- package/template/ui/admin/src/layouts/public.tsx +9 -0
- package/template/ui/admin/src/layouts/user.tsx +107 -0
- package/template/ui/admin/src/lib/config.ts +9 -0
- package/template/ui/admin/src/lib/fetch-api.ts +101 -0
- package/template/ui/admin/src/lib/query-client.ts +12 -0
- package/template/ui/admin/src/lib/query-keys.ts +44 -0
- package/template/ui/admin/src/lib/utils.ts +24 -0
- package/template/ui/admin/src/main.tsx +17 -0
- package/template/ui/admin/src/pages/account/dashboard.tsx +128 -0
- package/template/ui/admin/src/pages/account/members.tsx +414 -0
- package/template/ui/admin/src/pages/account/settings.tsx +210 -0
- package/template/ui/admin/src/pages/admin/accounts.tsx +164 -0
- package/template/ui/admin/src/pages/admin/dashboard.tsx +243 -0
- package/template/ui/admin/src/pages/admin/users.tsx +395 -0
- package/template/ui/admin/src/pages/public/error.tsx +24 -0
- package/template/ui/admin/src/pages/public/forgot-password.tsx +74 -0
- package/template/ui/admin/src/pages/public/login.tsx +102 -0
- package/template/ui/admin/src/pages/public/reset-password.tsx +106 -0
- package/template/ui/admin/src/pages/public/signup.tsx +118 -0
- package/template/ui/admin/src/pages/user/profile.tsx +313 -0
- package/template/ui/admin/src/pages/user/select-account.tsx +114 -0
- package/template/ui/admin/src/router.tsx +71 -0
- package/template/ui/admin/tailwind.config.ts +71 -0
- package/template/ui/admin/tsconfig.json +24 -0
- package/template/ui/admin/vite.config.ts +15 -0
- package/template/ui/site/.env.sample +3 -0
- package/template/ui/site/.prettierrc +4 -0
- package/template/ui/site/app.ts +50 -0
- package/template/ui/site/eslint.config.js +16 -0
- package/template/ui/site/package.json +33 -0
- package/template/ui/site/styles/input.css +3 -0
- package/template/ui/site/tailwind.config.js +8 -0
- package/template/ui/site/tsconfig.json +15 -0
- package/template/ui/site/views/pages/home.ejs +305 -0
- package/template/ui/site/views/partials/footer.ejs +44 -0
- package/template/ui/site/views/partials/header.ejs +83 -0
- 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
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import inquirer from 'inquirer';
|
|
4
3
|
import chalk from 'chalk';
|
|
5
4
|
import figlet from 'figlet';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import { createProject } from './create.mjs';
|
|
8
|
+
import { buildProject, buildDeps } from './build.mjs';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const { version } = require('./package.json');
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
await fs.promises.writeFile('_user.json', JSON.stringify(user));
|
|
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
|
+
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('');
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const data = await fs.promises.readFile('_user.json');
|
|
24
|
-
return JSON.parse(data);
|
|
25
|
-
} catch (ex) { }
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let user = await getUser();
|
|
28
|
+
function showHelp() {
|
|
29
|
+
showBanner();
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
console.log(`
|
|
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
|
-
|
|
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
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|