@xeplr/cli 0.1.6 → 0.1.8
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 +2 -0
- package/bin/xeplr.js +8 -1
- package/lib/generate/index.js +7 -2
- package/package.json +1 -1
- package/templates/README.md +48 -0
- package/templates/api/README.md +40 -0
- package/templates/api/development.env.tpl +13 -0
- package/templates/api/package.json +1 -1
- package/templates/ui/README.md +28 -0
- package/templates/ui/src/pages/TasksHelp.jsx +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,8 @@ Already working, with nothing to wire up:
|
|
|
45
45
|
and choosing one is the first screen after signing in.
|
|
46
46
|
- **A welcome screen** that checks it can reach everything and tells you which
|
|
47
47
|
files to change first.
|
|
48
|
+
- **READMEs** at the project root and in `api/` and `ui/`: how to start it,
|
|
49
|
+
every command, where things live, and the rules the app follows.
|
|
48
50
|
- **A `CLAUDE.md`** that tells Claude how to create a new UI in this app — the
|
|
49
51
|
screens, the page with its front-end hooks, the server hooks and model, the
|
|
50
52
|
route and menu entry — and a ready prompt (also shown beside the sample):
|
package/bin/xeplr.js
CHANGED
|
@@ -54,7 +54,14 @@ async function main() {
|
|
|
54
54
|
|
|
55
55
|
var session = prompt.createSession();
|
|
56
56
|
try {
|
|
57
|
-
|
|
57
|
+
// A folder that already has something in it is refused NOW, before a
|
|
58
|
+
// single question is asked — not after the database password.
|
|
59
|
+
var argName = argv[1] && argv[1].indexOf('--') !== 0 ? argv[1] : undefined;
|
|
60
|
+
if (argName && generate.isOccupied(path.resolve(process.cwd(), argName))) {
|
|
61
|
+
console.error('\n ' + path.resolve(process.cwd(), argName) + ' already exists and is not empty.\n');
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
var answers = await questions.collect(session, argName);
|
|
58
65
|
console.log(plan.describe(answers));
|
|
59
66
|
|
|
60
67
|
var go = await session.confirm(' Create it?', true);
|
package/lib/generate/index.js
CHANGED
|
@@ -226,7 +226,7 @@ function walk(dir) {
|
|
|
226
226
|
* @returns {{ written: string[] }}
|
|
227
227
|
*/
|
|
228
228
|
function generate(answers, target) {
|
|
229
|
-
if (
|
|
229
|
+
if (isOccupied(target)) {
|
|
230
230
|
throw new Error(target + ' already exists and is not empty.');
|
|
231
231
|
}
|
|
232
232
|
|
|
@@ -270,4 +270,9 @@ function generate(answers, target) {
|
|
|
270
270
|
return { written: written.sort() };
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
|
|
273
|
+
/** A folder that exists and has something in it — generating there could overwrite work. */
|
|
274
|
+
function isOccupied(target) {
|
|
275
|
+
return fs.existsSync(target) && fs.readdirSync(target).length > 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
module.exports = { isOccupied: isOccupied, generate: generate, substitutions: substitutions, fill: fill };
|
package/package.json
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# __NAME__
|
|
2
|
+
|
|
3
|
+
An xeplr application, created by [`@xeplr/cli`](https://www.npmjs.com/package/@xeplr/cli).
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
__NAME__/
|
|
7
|
+
├─ api/ the API and the sign-in service → api/README.md
|
|
8
|
+
├─ ui/ the React app → ui/README.md
|
|
9
|
+
└─ CLAUDE.md how to change this app — for Claude, and a good read for people
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Start it
|
|
13
|
+
|
|
14
|
+
Needs **PostgreSQL** and **Redis** running.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd api && npm run start-auth # sign-in http://localhost:__AUTH_PORT__
|
|
18
|
+
cd api && npm run start-api # the API http://localhost:__API_PORT__
|
|
19
|
+
cd ui && npm run dev # the app http://localhost:__UI_PORT__
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Open http://localhost:__UI_PORT__ and sign in as `__ADMIN_EMAIL__`.
|
|
23
|
+
|
|
24
|
+
## What is in it
|
|
25
|
+
|
|
26
|
+
- **Sign-in** — login, register, password reset, profile, and the admin screens for users, roles and permissions (`@xeplr/auth`, `@xeplr/ui-account`).
|
|
27
|
+
- **Tasks** — a sample built from screens: a list, and a form it opens in a popup, saved into an ordinary `tasks` table.
|
|
28
|
+
- **Configure UI** — in the settings menu, for Super Admin: every form (make one, design it, publish it, add it to the side rail) and the side rail itself (rename, reorder, hide).
|
|
29
|
+
|
|
30
|
+
## Create a new UI
|
|
31
|
+
|
|
32
|
+
- **Without code** — Configure UI → Forms → New form → design → Publish → Add to menu.
|
|
33
|
+
- **With Claude** — open Claude in this folder and give it (change the name and the fields):
|
|
34
|
+
|
|
35
|
+
> Create a new UI for farming departments. One record is a farming department with: Name (required), Region (dropdown: North, South, East, West), Area in acres (number, at least 0), Started on (date), Notes (long text). Show Name, Region and Area in the list. Follow "Create a new UI" in CLAUDE.md.
|
|
36
|
+
|
|
37
|
+
## Rules this app follows
|
|
38
|
+
|
|
39
|
+
| Rule | Why |
|
|
40
|
+
|---|---|
|
|
41
|
+
| Records live in **real tables**, one column per field — never JSON. | Reports stay plain SQL. |
|
|
42
|
+
| A **key** and a **label**, everywhere: code uses keys (form keys, menu keys); people read labels, renamed in Configure UI. | Renaming what people see never breaks code. |
|
|
43
|
+
| **`REDIS_PREFIX`** in `api/development.env` is required and unique to this app. | Apps sharing a Redis would otherwise serve each other's sessions and permissions. |
|
|
44
|
+
| Designing and publishing forms is **Super Admin only**. | Publishing changes database tables. |
|
|
45
|
+
| Rules that must hold go in **server hooks**; the browser only shapes what people see and send. | The browser is not a security boundary. |
|
|
46
|
+
| Every data address is under **`/api`**. | A page and a data path must never collide. |
|
|
47
|
+
|
|
48
|
+
The full list, and where each piece lives, is in `CLAUDE.md`.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# __NAME__ — api
|
|
2
|
+
|
|
3
|
+
The API (`@xeplr/base-apis`) and the sign-in service (`@xeplr/auth`), with this app's forms served by `@xeplr/factory`.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
| Command | Does |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `npm run setup` | create both databases (`__NAME___auth`, `__NAME___api`), the sign-in tables, permissions and menus |
|
|
10
|
+
| `npm run start-auth` | the sign-in service, port __AUTH_PORT__ — refuses to start without Redis or `REDIS_PREFIX`; its banner says whether email works |
|
|
11
|
+
| `npm run start-api` | the API, port __API_PORT__ — runs `migrations/` and publishes never-published screens on start |
|
|
12
|
+
| `npm run migrate:auth` | apply new `migrations-auth/*.sql` (a new menu row, say), then restart sign-in |
|
|
13
|
+
| `npm run db:encrypt` | redo the encrypted database connection |
|
|
14
|
+
| `npm run check-env` | check every required setting is present |
|
|
15
|
+
| `npx xeplr-factory screens screens/<form>/<form>.entity.json -o screens/<form> --no-pages` | a form's screens, server hooks and model from its spec |
|
|
16
|
+
| `npx xeplr-factory validate <screen>.json` | check a screen document |
|
|
17
|
+
|
|
18
|
+
## Layout
|
|
19
|
+
|
|
20
|
+
| Path | What |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `development.env` | every setting — ports, the encrypted connection, `REDIS_PREFIX`, secrets, the super admin, email |
|
|
23
|
+
| `env.required.js` | the settings that must be present; the API refuses to start and names any that are missing |
|
|
24
|
+
| `app.js` | the Express app: the auth gate, and the routes |
|
|
25
|
+
| `routes/index.js` | this app's own routes — every data address under `/api` |
|
|
26
|
+
| `routes/access.js` | who may design and publish forms (Super Admin) |
|
|
27
|
+
| `screens/` | the forms this app ships with: per form, its spec, list and edit screens, server hooks and model — registered in `screens/index.js` |
|
|
28
|
+
| `migrations/` | hand-written SQL for tables no form owns |
|
|
29
|
+
| `migrations-auth/` | this app's rows in the sign-in database — menus (by key), permissions |
|
|
30
|
+
| `models/` | models for hand-written tables |
|
|
31
|
+
|
|
32
|
+
## Settings to know
|
|
33
|
+
|
|
34
|
+
| Setting | |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `REDIS_PREFIX` | **required, unique to this app** (`__NAME__:`) |
|
|
37
|
+
| `__PREFIX___CONNECTION`, `AUTH_DB_CONNECTION_INFO_ENCRYPTED` | the database, encrypted — `npm run db:encrypt` |
|
|
38
|
+
| `DB_API`, `AUTH_DB_NAME` | the two database names — never guessed |
|
|
39
|
+
| `AUTH_SUPER_ADMIN_EMAIL` / `_PASSWORD` | the first account — created once, when the sign-in database is first set up |
|
|
40
|
+
| `EMAIL_PROVIDER` and its settings | optional; without them sign-in works, but activation and reset links cannot be sent |
|
|
@@ -56,3 +56,16 @@ AUTH_SUPER_ADMIN_PASSWORD=__ADMIN_PASSWORD__
|
|
|
56
56
|
# email template can carry a token as a value but cannot build an address.
|
|
57
57
|
AUTH_ACTIVATION_URL=http://localhost:__UI_PORT__/auth/activate
|
|
58
58
|
AUTH_INVITE_URL=http://localhost:__UI_PORT__/auth/accept-invite
|
|
59
|
+
|
|
60
|
+
# ── Email (optional) ──
|
|
61
|
+
# Without it sign-in works, but activation, invite and password-reset links
|
|
62
|
+
# cannot be sent. The sign-in service checks it at start and says so in its
|
|
63
|
+
# banner: "email ✓ smtp — host:587, connected" or "✗ NOT WORKING — reason".
|
|
64
|
+
# Providers: smtp | brevo | aws | azure — see @xeplr/utils' README.
|
|
65
|
+
# EMAIL_PROVIDER=smtp
|
|
66
|
+
# SMTP_HOST=
|
|
67
|
+
# SMTP_PORT=587
|
|
68
|
+
# SMTP_USER=
|
|
69
|
+
# SMTP_PASS=
|
|
70
|
+
# SMTP_FROM=
|
|
71
|
+
# (smtp also needs `npm install nodemailer` in this folder)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# __NAME__ — ui
|
|
2
|
+
|
|
3
|
+
The React app (Vite), using `@xeplr/ui-account` for sign-in and the app shell and `@xeplr/ui-factory` for forms.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm run dev # http://localhost:__UI_PORT__
|
|
7
|
+
npm run build # → dist/
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`.env` holds the port and where the sign-in service and the API are; `vite.config.js` proxies `/auth/api` to sign-in and `/api` (and `/health`, `/whoami`) to the API. A new data prefix must be added there too.
|
|
11
|
+
|
|
12
|
+
## Layout
|
|
13
|
+
|
|
14
|
+
| Path | What |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `src/main.jsx` | `ThemeProvider` (required — the theme's colours exist only inside it) and `AccessProvider` |
|
|
17
|
+
| `src/App.jsx` | routes, the side rail (`drawerItems`) and the settings menu (`settingsOverrides`) — **by key**, never by label |
|
|
18
|
+
| `src/menu.js` | every menu key this app uses; forms added to the menu are `form:<key>` |
|
|
19
|
+
| `src/api/factory.js` | the calls to the API's forms routes |
|
|
20
|
+
| `src/pages/Tasks.jsx` | the sample list; `EditTask.jsx` holds the task form's **front-end hooks** (`TaskHooks extends FactoryHooks` — every method calls `super`) |
|
|
21
|
+
| `src/pages/ConfigureUI.jsx` | Configure UI (Super Admin): `Forms.jsx` and `MenuSettings.jsx` tabs; `FormDesigner.jsx` designs one form |
|
|
22
|
+
| `src/pages/FormRecords.jsx` | opens any published form — what a form added to the menu links to |
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- `authFetch` returns the **parsed body**, not a Response — no `.json()`.
|
|
27
|
+
- A side-rail item needs an **icon** (a collapsed rail shows icons only) and its key must exist as a menu row, or it is dropped silently.
|
|
28
|
+
- Set page backgrounds on a container inside the theme, never on `body`.
|
|
@@ -51,7 +51,7 @@ export default function TasksHelp() {
|
|
|
51
51
|
new column as you publish.
|
|
52
52
|
</p>
|
|
53
53
|
|
|
54
|
-
<h3>Or
|
|
54
|
+
<h3>Or without code</h3>
|
|
55
55
|
<p>
|
|
56
56
|
<strong>Configure UI → Forms → New form</strong>: label and key, design,
|
|
57
57
|
publish, add to menu — no code. The full steps for a form with its own page, hooks and
|