@xeplr/cli 0.3.0 → 0.3.2
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 +1 -15
- package/lib/generate/index.js +1 -63
- package/lib/questions.js +0 -4
- package/package.json +1 -1
- package/templates/CLAUDE.md +0 -1
- package/templates/README.md +6 -25
- package/templates/api/README.md +1 -0
- package/templates/api/app.js +0 -4
- package/templates/api/bin/www +1 -15
- package/templates/api/development.env.tpl +7 -2
- package/templates/api/package.json +1 -3
- package/templates/api/production.env.example +4 -6
- package/templates/api/routes/index.js +0 -5
- package/templates/ui/dotenv.tpl +0 -1
- package/templates/ui/package.json +0 -1
- package/templates/ui/src/App.jsx +0 -20
- package/templates/ui/src/index.css +0 -16
- package/templates/ui/src/menu.js +0 -1
- package/templates/ui/src/pages/ConfigureUI.jsx +5 -4
- package/templates/ui/src/pages/Forms.jsx +120 -17
- package/templates/ui/src/pages/Masters.jsx +114 -0
- package/templates/ui/vite.config.js +1 -7
- package/templates/api/bin/workflow +0 -52
- package/templates/api/migrations-auth/0005_flows_menu.sql +0 -14
- package/templates/api/routes/flows.js +0 -47
- package/templates/api/workflow.js +0 -34
- package/templates/ui/src/api/flows.js +0 -8
- package/templates/ui/src/pages/FlowDesigner.jsx +0 -54
- package/templates/ui/src/pages/Flows.jsx +0 -125
- package/templates/ui/src/pages/FlowsHome.jsx +0 -65
- package/templates/ui/src/pages/Journey.jsx +0 -33
package/README.md
CHANGED
|
@@ -41,12 +41,6 @@ Already working, with nothing to wire up:
|
|
|
41
41
|
- **Menu**: rename, reorder and hide the side rail's items.
|
|
42
42
|
|
|
43
43
|
Nobody else sees it, and the API refuses everyone else.
|
|
44
|
-
- **Flows, in every app** — your forms one after another, where what someone
|
|
45
|
-
fills in decides the next screen. Designed in Configure UI → Flows, walked at
|
|
46
|
-
`/journey/<key>`, and resumable. Run by `@xeplr/workflow` **inside the API**,
|
|
47
|
-
in the app's own database — nothing extra to start, no port, no setting. It
|
|
48
|
-
follows the app's tenancy (none, or the levels you chose) and never creates
|
|
49
|
-
companies or workspaces of its own.
|
|
50
44
|
- **Multi-tenancy, if you ask for it** — companies (or companies and
|
|
51
45
|
workspaces, up to four levels). Every row is stamped with the one it was made
|
|
52
46
|
in, every read sees only its rows, membership is checked on every request,
|
|
@@ -77,7 +71,7 @@ These are architecture decisions, not defaults to tune — each one exists becau
|
|
|
77
71
|
| **`REDIS_PREFIX` is required and unique per app** (`api/development.env`, set to `<name>:`). | `@xeplr/auth`, `@xeplr/utils` | Two apps on one Redis under the shared default serve each other's sessions, menus and API permissions. Sign-in refuses to start without it, or with `xeplr:`. |
|
|
78
72
|
| **Designing, new forms and publishing are Super Admin only.** | `api/routes/access.js`, `@xeplr/auth` menu routes | Publishing changes database tables. Every other screen route checks the permission catalog. |
|
|
79
73
|
| **The browser is not a security boundary.** Front-end hooks (`ui/src/pages/Edit<Form>.jsx`) shape what people see and send; anything that must hold goes in server hooks (`api/screens/<form>/<form>.hooks.js`); data shape in the model (`<form>.model.js`). | `@xeplr/ui-factory`, `@xeplr/factory` | |
|
|
80
|
-
| **Anything every app would write identically lives in a package, not in the generated app.** A generated app is a starting point that then lives its own life — glue copied into it can never be fixed centrally, so an app made last month keeps that copy forever. When
|
|
74
|
+
| **Anything every app would write identically lives in a package, not in the generated app.** A generated app is a starting point that then lives its own life — glue copied into it can never be fixed centrally, so an app made last month keeps that copy forever. When Configure UI → Masters needed to know which forms are masters, that answer went into `@xeplr/ui-factory` (`useScreenSource`) and the generated page only reads `masters`. | `@xeplr/ui-factory` | A fix or a new control type has to reach apps that already exist, and it cannot reach code that was copied into them. `test/cli.test.js` fails if the glue reappears in the generated app. |
|
|
81
75
|
| **Every data address is under `/api`.** | `api/routes`, `ui/vite.config.js` | A page path and a data path must never collide. |
|
|
82
76
|
| **No guessed database names or connections.** | `api/development.env` | A wrong-but-present value starts cleanly and reads as empty data. |
|
|
83
77
|
| **Email is checked at start, never fatal.** The sign-in banner shows `email ✓ …` or `✗ NOT WORKING — reason`. | `@xeplr/auth` | Sign-in works without email; activation, invite and reset links cannot be sent until `EMAIL_PROVIDER` is set. |
|
|
@@ -206,14 +200,6 @@ no defaults, ever.
|
|
|
206
200
|
|
|
207
201
|
Encryption keys and signing secrets are **generated, never asked**.
|
|
208
202
|
|
|
209
|
-
**Flows, inside the API.** `@xeplr/workflow` is a dependency of `api/`, and
|
|
210
|
-
`bin/www` starts it with `registerWorkflow` after the screens — in the app's
|
|
211
|
-
own database (`DB_API`, its own migration record `workflow_migrations`), with
|
|
212
|
-
`tenantTables: false` so it follows the app's tenancy rather than creating
|
|
213
|
-
`companies` / `workspaces`, and `access: true` so each flows route checks the
|
|
214
|
-
caller's permissions, which its `migrations-auth` grants (loaded through
|
|
215
|
-
`XEPLR_AUTH_MIGRATIONS`).
|
|
216
|
-
|
|
217
203
|
## Licence
|
|
218
204
|
|
|
219
205
|
MIT
|
package/lib/generate/index.js
CHANGED
|
@@ -28,52 +28,13 @@ function substitutions(answers) {
|
|
|
28
28
|
__UI_PORT__: String(answers.ports.ui),
|
|
29
29
|
__AUTH_PORT__: String(answers.ports.auth),
|
|
30
30
|
__API_PORT__: String(answers.ports.api),
|
|
31
|
-
// The port after the API's — suggested for a service run on its own (workflow).
|
|
32
|
-
__NEXT_PORT__: String(answers.ports.api + 1),
|
|
33
31
|
__ENCRYPTION_KEY__: answers.secrets.encryptionKey,
|
|
34
32
|
__JWT_SECRET__: answers.secrets.jwtSecret,
|
|
35
33
|
__ADMIN_EMAIL__: answers.admin.email,
|
|
36
34
|
__ADMIN_PASSWORD__: answers.admin.password,
|
|
37
35
|
__DB_CONNECTION__: answers.connection || '',
|
|
38
36
|
__CONNECTION_NOTE__: connectionNote(answers)
|
|
39
|
-
}, tenancySubstitutions(answers.tenancy || [])
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* FLOWS ARE IN EVERY APP, and so is what runs them: @xeplr/workflow, started
|
|
44
|
-
* INSIDE the API (bin/www → registerWorkflow) in the app's own database. There
|
|
45
|
-
* is no Workflow server to run, no port, no setting and nothing to connect, so
|
|
46
|
-
* nothing here depends on an answer — these tokens only carry text.
|
|
47
|
-
*/
|
|
48
|
-
function flowsSubstitutions(answers) {
|
|
49
|
-
return {
|
|
50
|
-
// Where workflow runs. Its database is DB_API with the app's login — no
|
|
51
|
-
// setting; only the port, and only to run it as its own service.
|
|
52
|
-
__WF_ENV__: [
|
|
53
|
-
'# ── Flows (@xeplr/workflow) ──',
|
|
54
|
-
'# BLANK: workflow runs INSIDE this API (npm run start-api) — nothing else to',
|
|
55
|
-
'# start. Set a port to run it as its OWN SERVICE (npm run start-workflow);',
|
|
56
|
-
'# then point /api/workflow at it: WORKFLOW_URL in ui/.env for local, nginx',
|
|
57
|
-
'# in production (README, "Production"). This app\'s next free port: ' + (answers.ports.api + 1) + '.',
|
|
58
|
-
'WORKFLOW_PORT=',
|
|
59
|
-
''
|
|
60
|
-
].join('\n'),
|
|
61
|
-
__WF_UI_ENV__: [
|
|
62
|
-
'',
|
|
63
|
-
'# Where /api/workflow goes. BLANK: the API above (workflow runs inside it).',
|
|
64
|
-
'# Set it when api/development.env sets WORKFLOW_PORT — e.g.',
|
|
65
|
-
'# http://localhost:' + (answers.ports.api + 1),
|
|
66
|
-
'WORKFLOW_URL='
|
|
67
|
-
].join('\n'),
|
|
68
|
-
// Workflow's permissions — who may design a flow, who may run one — load
|
|
69
|
-
// into this app's sign-in service after the app's own.
|
|
70
|
-
__WF_AUTH_MIGRATIONS__: ',./node_modules/@xeplr/workflow/migrations-auth',
|
|
71
|
-
__WF_README__: flowsReadme(answers),
|
|
72
|
-
__WF_CLAUDE__: [
|
|
73
|
-
'**Flows** — forms one after another, run by `@xeplr/workflow` INSIDE this API (bin/www starts it) and stored in this app\'s own database. Design them in Configure UI → Flows, never in code: a flow is data, reached through `/api/flows` (routes/flows.js), and walked at `/journey/<key>`. An arrow tests one field of the screen it leaves; a form that is a step must be published first. Workflow follows this app\'s tenancy — it never creates companies or workspaces of its own.',
|
|
74
|
-
''
|
|
75
|
-
].join('\n')
|
|
76
|
-
};
|
|
37
|
+
}, tenancySubstitutions(answers.tenancy || []));
|
|
77
38
|
}
|
|
78
39
|
|
|
79
40
|
/**
|
|
@@ -117,8 +78,6 @@ function tenancySubstitutions(levels) {
|
|
|
117
78
|
" '/api/tenants': tenants.router,"
|
|
118
79
|
].join('\n') : '',
|
|
119
80
|
__MT_MEMBER_GATE__: on ? 'tenants.memberGate' : 'access.anyone',
|
|
120
|
-
// api/app.js — the request middleware bin/workflow runs as well.
|
|
121
|
-
__MT_MIDDLEWARE_LIST__: on ? '[mtMiddleware()]' : '[]',
|
|
122
81
|
__MT_MEMBER_NOUN__: on ? levels.map(function (l) { return l.label.toLowerCase(); }).join(' and ') : 'data (no tenancy: nothing to check)',
|
|
123
82
|
|
|
124
83
|
// ui/src/main.jsx
|
|
@@ -159,27 +118,6 @@ function tenancySubstitutions(levels) {
|
|
|
159
118
|
};
|
|
160
119
|
}
|
|
161
120
|
|
|
162
|
-
/** The README's Flows section. */
|
|
163
|
-
function flowsReadme(answers) {
|
|
164
|
-
var tenancy = answers.tenancy || [];
|
|
165
|
-
return [
|
|
166
|
-
'## Flows',
|
|
167
|
-
'',
|
|
168
|
-
'Your forms, one after another: what someone fills in on one decides which comes next. Design one in **Configure UI → Flows**, publish it, and people start it from **Flows** in the side rail (or at `/journey/<key>`). The run id joins the address once it starts, so a journey can be reloaded, left, or handed to someone else and opened on the step it is on.',
|
|
169
|
-
'',
|
|
170
|
-
'Flows are run by **`@xeplr/workflow`**, which runs **inside this API** — `bin/www` starts it after the screens. Nothing else to start, no port, no setting:',
|
|
171
|
-
'',
|
|
172
|
-
'| | |',
|
|
173
|
-
'|---|---|',
|
|
174
|
-
'| **Database** | this app\'s own (`DB_API`). Workflow\'s tables (`workflows`, `workflow_steps`, `workflow_runs`…) sit beside the forms\' tables, and it keeps its own record of which of its migrations ran (`workflow_migrations`). Each screen still saves into its own table. |',
|
|
175
|
-
'| **Tenancy** | ' + (tenancy.length
|
|
176
|
-
? 'this app\'s — flows and runs belong to the ' + tenancy.map(function (l) { return l.label.toLowerCase(); }).join(' and ') + ' chosen after signing in, checked by the same membership gate as everything else.'
|
|
177
|
-
: 'none, like the rest of this app — flows belong to the whole app.') + ' Workflow never creates companies or workspaces of its own. |',
|
|
178
|
-
'| **Permissions** | each route checks the caller\'s (`List flows`, `Start flow run`, `Publish flow`…), granted to Super Admin, CompanyAdmin, Creator and — read only — Viewer by `@xeplr/workflow`\'s `migrations-auth`, which `XEPLR_AUTH_MIGRATIONS` loads. |',
|
|
179
|
-
''
|
|
180
|
-
].join('\n');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
121
|
/** One level's own table: what the picker lists. Its parents' ids in mtIdN. */
|
|
184
122
|
function tenantTableSql(level, i) {
|
|
185
123
|
var parents = i === 0 ? 'none — the outermost level' : i === 1 ? 'mtId1 holds the one above' : 'mtId1–mtId' + i + ' hold the ones above';
|
package/lib/questions.js
CHANGED
|
@@ -134,10 +134,6 @@ async function collect(session, argvName) {
|
|
|
134
134
|
tenancy = tenancyLevels(levelsText);
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
// ── flows ─────────────────────────────────────────────────────────────
|
|
138
|
-
// Nothing to ask: every app has flows, run by @xeplr/workflow inside its own
|
|
139
|
-
// API and database (see lib/generate's flowsSubstitutions).
|
|
140
|
-
|
|
141
137
|
// ── the database ──────────────────────────────────────────────────────
|
|
142
138
|
//
|
|
143
139
|
// ASKED, not defaulted, and not left for the reader to do by hand either.
|
package/package.json
CHANGED
package/templates/CLAUDE.md
CHANGED
|
@@ -49,7 +49,6 @@ Then change only what the request asks for:
|
|
|
49
49
|
|
|
50
50
|
**A list opens its form on a page or in a popup** — `"openIn": "page"` in the entity spec, or Configure UI → Forms → the list. A page needs `onOpenRecord` on the list's page (where to navigate) and `onDone` on the form's page (where Save and Cancel go back to): `Tasks.jsx` and `EditTask.jsx` show both, with routes `/tasks/new` and `/tasks/:id` in `App.jsx`.
|
|
51
51
|
|
|
52
|
-
__WF_CLAUDE__
|
|
53
52
|
**Keys and labels.** A form's key names its screens and table and never changes once published; its label is the screens' name. A menu item's key (`menus.name`) is what `drawerItems` matches; its label is what the rail shows, renamed in Configure UI → Menu. Code only ever uses keys.
|
|
54
53
|
|
|
55
54
|
### The prompt
|
package/templates/README.md
CHANGED
|
@@ -21,9 +21,6 @@ cd ui && npm run dev # the app http://localhost:__UI_PORT_
|
|
|
21
21
|
|
|
22
22
|
Open http://localhost:__UI_PORT__ and sign in as `__ADMIN_EMAIL__`.
|
|
23
23
|
|
|
24
|
-
Flows (Xeplr Workflow) run **inside the API** — nothing else to start. To run
|
|
25
|
-
them as their own service instead, see "Where each address goes" below.
|
|
26
|
-
|
|
27
24
|
## Where each address goes
|
|
28
25
|
|
|
29
26
|
The browser only ever calls **paths** on the app's own address — never a port.
|
|
@@ -33,39 +30,24 @@ locally (`ui/vite.config.js`, from `ui/.env`), nginx in production.
|
|
|
33
30
|
| path | served by | local (`ui/.env`) | production (nginx) |
|
|
34
31
|
|---|---|---|---|
|
|
35
32
|
| `/auth/api/…` | the sign-in service | `AUTH_URL` | `127.0.0.1:<AUTH_PORT>` |
|
|
36
|
-
| `/api/workflow/…` | flows — `@xeplr/workflow` | `WORKFLOW_URL`, or `API_URL` when blank | the workflow process, or the API when it runs inside |
|
|
37
33
|
| `/api/…`, `/health`, `/whoami` | the API | `API_URL` | `127.0.0.1:<__PREFIX___PORT>` |
|
|
38
34
|
|
|
39
|
-
**Workflow on its own port.** Set `WORKFLOW_PORT` in `api/development.env`
|
|
40
|
-
(this app's next free port is __NEXT_PORT__), set `WORKFLOW_URL` in `ui/.env` to
|
|
41
|
-
`http://localhost:__NEXT_PORT__`, and start it beside the API:
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
cd api && npm run start-workflow # flows http://localhost:__NEXT_PORT__
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Same database, same sign-in, same permissions and the same `/api/workflow/…`
|
|
48
|
-
paths — only the process differs. Leave `WORKFLOW_PORT` blank and it runs inside
|
|
49
|
-
the API again.
|
|
50
|
-
|
|
51
35
|
### Production
|
|
52
36
|
|
|
53
37
|
`api/production.env.example` lists every setting (copy it to
|
|
54
38
|
`api/production.env`, fill in, start with `NODE_ENV=production`). Build the UI
|
|
55
|
-
(`cd ui && npm run build`) and let nginx serve it and route the paths
|
|
56
|
-
more specific `/api/workflow/` first:
|
|
39
|
+
(`cd ui && npm run build`) and let nginx serve it and route the paths:
|
|
57
40
|
|
|
58
41
|
```nginx
|
|
59
42
|
server {
|
|
60
43
|
server_name app.example.com;
|
|
61
44
|
root /srv/__NAME__/ui/dist;
|
|
62
45
|
|
|
63
|
-
location /auth/api/
|
|
64
|
-
location /api/
|
|
65
|
-
location /
|
|
66
|
-
location = /
|
|
67
|
-
location
|
|
68
|
-
location / { try_files $uri /index.html; } # the React app's own pages
|
|
46
|
+
location /auth/api/ { proxy_pass http://127.0.0.1:__AUTH_PORT__; }
|
|
47
|
+
location /api/ { proxy_pass http://127.0.0.1:__API_PORT__; }
|
|
48
|
+
location = /health { proxy_pass http://127.0.0.1:__API_PORT__; }
|
|
49
|
+
location = /whoami { proxy_pass http://127.0.0.1:__API_PORT__; }
|
|
50
|
+
location / { try_files $uri /index.html; } # the React app's own pages
|
|
69
51
|
}
|
|
70
52
|
```
|
|
71
53
|
|
|
@@ -84,7 +66,6 @@ Every one of these checks the same sign-in token, so one login covers them all.
|
|
|
84
66
|
|
|
85
67
|
> 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.
|
|
86
68
|
|
|
87
|
-
__WF_README__
|
|
88
69
|
## Rules this app follows
|
|
89
70
|
|
|
90
71
|
| Rule | Why |
|
package/templates/api/README.md
CHANGED
|
@@ -38,4 +38,5 @@ The API (`@xeplr/base-apis`) and the sign-in service (`@xeplr/auth`), with this
|
|
|
38
38
|
| `__PREFIX___CONNECTION`, `AUTH_DB_CONNECTION_INFO_ENCRYPTED` | the database, encrypted — `npm run db:encrypt` |
|
|
39
39
|
| `DB_API`, `AUTH_DB_NAME` | the two database names — never guessed |
|
|
40
40
|
| `AUTH_SUPER_ADMIN_EMAIL` / `_PASSWORD` | the first account — created once, when the sign-in database is first set up |
|
|
41
|
+
| `AUTH_TENANT_HEADER` | `none`: one set of roles for everyone. A multi-tenant app names its company header (e.g. `x-company-id`), so a role made inside a company belongs to it |
|
|
41
42
|
| `EMAIL_PROVIDER` and its settings | optional; without them sign-in works, but activation and reset links cannot be sent |
|
package/templates/api/app.js
CHANGED
|
@@ -13,10 +13,6 @@ var memberGate = __MT_MEMBER_GATE__;
|
|
|
13
13
|
var port = process.env.__PREFIX___PORT;
|
|
14
14
|
|
|
15
15
|
module.exports = buildApp;
|
|
16
|
-
// bin/www and bin/workflow hand the same gate to the flows router they start,
|
|
17
|
-
// and bin/workflow runs the same request middleware (tenancy) as this app.
|
|
18
|
-
module.exports.memberGate = memberGate;
|
|
19
|
-
module.exports.middleware = function() { return __MT_MIDDLEWARE_LIST__; };
|
|
20
16
|
|
|
21
17
|
function buildApp(extraRoutes) {
|
|
22
18
|
return createApp(port, '__NAME___api', {
|
package/templates/api/bin/www
CHANGED
|
@@ -76,21 +76,7 @@ async function start() {
|
|
|
76
76
|
var published = await factory.publishScreens(screens.documents);
|
|
77
77
|
if (published.published.length) console.log('[api] published screens: ' + published.published.join(', '));
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
// (routes/flows.js says why). It creates its tables on first start, keeps its
|
|
81
|
-
// own migration record, and follows this app's tenancy instead of creating
|
|
82
|
-
// companies / workspaces of its own.
|
|
83
|
-
var buildApp = require('../app');
|
|
84
|
-
// WORKFLOW_PORT set: it runs as its own service (npm run start-workflow),
|
|
85
|
-
// and this API leaves it alone.
|
|
86
|
-
if (process.env.WORKFLOW_PORT) {
|
|
87
|
-
console.log('[api] flows: workflow runs on its own port ' + process.env.WORKFLOW_PORT + ' (npm run start-workflow)');
|
|
88
|
-
buildApp();
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
require('../routes/flows').mount(await require('../workflow')(buildApp.memberGate));
|
|
92
|
-
|
|
93
|
-
buildApp();
|
|
79
|
+
require('../app')();
|
|
94
80
|
}
|
|
95
81
|
|
|
96
82
|
start().catch(function(err) {
|
|
@@ -42,7 +42,7 @@ AUTH_URL=http://localhost:__AUTH_PORT__
|
|
|
42
42
|
|
|
43
43
|
# Sign-in setup files: the screens' permissions (@xeplr/factory's), then this
|
|
44
44
|
# app's own — menu entries and anything else it adds. Comma-separated, in order.
|
|
45
|
-
XEPLR_AUTH_MIGRATIONS=./node_modules/@xeplr/factory/migrations-auth,./migrations-
|
|
45
|
+
XEPLR_AUTH_MIGRATIONS=./node_modules/@xeplr/factory/migrations-auth,./migrations-auth
|
|
46
46
|
|
|
47
47
|
# ── The first account you sign in with ──
|
|
48
48
|
# Created for you when you set up the database. Change the password here and
|
|
@@ -50,6 +50,12 @@ XEPLR_AUTH_MIGRATIONS=./node_modules/@xeplr/factory/migrations-auth,./migrations
|
|
|
50
50
|
AUTH_SUPER_ADMIN_EMAIL=__ADMIN_EMAIL__
|
|
51
51
|
AUTH_SUPER_ADMIN_PASSWORD=__ADMIN_PASSWORD__
|
|
52
52
|
|
|
53
|
+
# ── Which company a role belongs to ──
|
|
54
|
+
# none: this app has one set of roles for everyone. A multi-tenant app names
|
|
55
|
+
# the header that carries the company id instead (e.g. x-company-id), so a
|
|
56
|
+
# role made inside a company belongs to that company.
|
|
57
|
+
AUTH_TENANT_HEADER=none
|
|
58
|
+
|
|
53
59
|
# ── Links that go out in emails ──
|
|
54
60
|
# FULLY QUALIFIED, and they point at UI pages rather than at the API. The
|
|
55
61
|
# sign-in service builds these links and appends the token as ?token=... — an
|
|
@@ -57,7 +63,6 @@ AUTH_SUPER_ADMIN_PASSWORD=__ADMIN_PASSWORD__
|
|
|
57
63
|
AUTH_ACTIVATION_URL=http://localhost:__UI_PORT__/auth/activate
|
|
58
64
|
AUTH_INVITE_URL=http://localhost:__UI_PORT__/auth/accept-invite
|
|
59
65
|
|
|
60
|
-
__WF_ENV__
|
|
61
66
|
# ── Uploaded files ──
|
|
62
67
|
# Where a form's FILE field puts what people upload. The path is what the
|
|
63
68
|
# record stores; the file itself is served only through the API's own route,
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
"migrate:auth": "dotenv -e development.env -- xeplr-auth-migrate up --connection-env AUTH_DB_CONNECTION_INFO_ENCRYPTED",
|
|
15
15
|
"migrate:up": "dotenv -e development.env -- xeplr-migrate up --dir ./migrations --connectionName api --connection-env __PREFIX___CONNECTION",
|
|
16
16
|
"start-auth": "dotenv -e ./development.env -- xeplr-auth-server",
|
|
17
|
-
"start-api": "node ./bin/www"
|
|
18
|
-
"start-workflow": "node ./bin/workflow"
|
|
17
|
+
"start-api": "node ./bin/www"
|
|
19
18
|
},
|
|
20
19
|
"dependencies": {
|
|
21
20
|
"@xeplr/auth": "^1.1.4",
|
|
@@ -24,7 +23,6 @@
|
|
|
24
23
|
"@xeplr/factory": "^1.1.0",
|
|
25
24
|
"@xeplr/ui-factory": "^1.1.0",
|
|
26
25
|
"@xeplr/utils": "^1.0.11",
|
|
27
|
-
"@xeplr/workflow": "^1.2.0",
|
|
28
26
|
"dotenv": "^17.4.2",
|
|
29
27
|
"express": "^5.2.0",
|
|
30
28
|
"multer": "^2.0.2"
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# __NAME__ API — PRODUCTION settings. Copy to production.env on the server
|
|
2
|
-
# and fill in; start with NODE_ENV=production (bin/www
|
|
3
|
-
# <NODE_ENV>.env). production.env is never committed (.gitignore).
|
|
2
|
+
# and fill in; start with NODE_ENV=production (bin/www reads <NODE_ENV>.env). production.env is never committed (.gitignore).
|
|
4
3
|
#
|
|
5
4
|
# Every value that is a secret is blank here. Generate new ones for production
|
|
6
5
|
# — never reuse development's.
|
|
@@ -11,9 +10,6 @@ LOG_DIR=./logs
|
|
|
11
10
|
# ── Ports: one per process on this server ──
|
|
12
11
|
__PREFIX___PORT=__API_PORT__
|
|
13
12
|
AUTH_PORT=__AUTH_PORT__
|
|
14
|
-
# Blank: workflow runs inside the API. Set: it runs as its own process
|
|
15
|
-
# (npm run start-workflow) and nginx sends /api/workflow to it.
|
|
16
|
-
WORKFLOW_PORT=
|
|
17
13
|
|
|
18
14
|
# ── Databases (same server, one login) ──
|
|
19
15
|
ENCRYPTION_KEY=
|
|
@@ -27,9 +23,11 @@ REDIS_PREFIX=__NAME__:
|
|
|
27
23
|
AUTH_JWT_SECRET=
|
|
28
24
|
# Where THIS server reaches the sign-in service (not the public address).
|
|
29
25
|
AUTH_URL=http://127.0.0.1:__AUTH_PORT__
|
|
30
|
-
XEPLR_AUTH_MIGRATIONS=./node_modules/@xeplr/factory/migrations-auth,./migrations-auth
|
|
26
|
+
XEPLR_AUTH_MIGRATIONS=./node_modules/@xeplr/factory/migrations-auth,./migrations-auth
|
|
31
27
|
AUTH_SUPER_ADMIN_EMAIL=
|
|
32
28
|
AUTH_SUPER_ADMIN_PASSWORD=
|
|
29
|
+
# none for one set of roles; a multi-tenant app names its company header, e.g. x-company-id
|
|
30
|
+
AUTH_TENANT_HEADER=none
|
|
33
31
|
|
|
34
32
|
# ── Public addresses (what people's browsers open) ──
|
|
35
33
|
AUTH_ACTIVATION_URL=https://app.example.com/auth/activate
|
|
@@ -34,9 +34,4 @@ router.get('/whoami', function(req, res) {
|
|
|
34
34
|
// var { genericRoute } = require('@xeplr/base-apis');
|
|
35
35
|
// router.use('/api/things', genericRoute({ key: 'thing', model: require('../models').Thing }));
|
|
36
36
|
|
|
37
|
-
// Flows: Xeplr Workflow, at /api/workflow — inside this API, or on its
|
|
38
|
-
// own port with WORKFLOW_PORT (see routes/flows.js). The path is the same
|
|
39
|
-
// either way: the browser never knows which.
|
|
40
|
-
router.use('/api/workflow', require('./flows'));
|
|
41
|
-
|
|
42
37
|
module.exports = router;
|
package/templates/ui/dotenv.tpl
CHANGED
package/templates/ui/src/App.jsx
CHANGED
|
@@ -4,9 +4,6 @@ import { authRoutes, authPath, NavPage, ProtectedRoute, useAccess } from '@xeplr
|
|
|
4
4
|
import Home from './pages/Home.jsx'
|
|
5
5
|
import Tasks from './pages/Tasks.jsx'
|
|
6
6
|
import EditTask from './pages/EditTask.jsx'
|
|
7
|
-
import FlowDesigner from './pages/FlowDesigner.jsx'
|
|
8
|
-
import Journey from './pages/Journey.jsx'
|
|
9
|
-
import FlowsHome from './pages/FlowsHome.jsx'
|
|
10
7
|
import ConfigureUI from './pages/ConfigureUI.jsx'
|
|
11
8
|
import { FORM_MENU_PREFIX } from './menu.js'
|
|
12
9
|
import FormDesigner from './pages/FormDesigner.jsx'
|
|
@@ -24,15 +21,6 @@ const TasksIcon = (
|
|
|
24
21
|
</svg>
|
|
25
22
|
)
|
|
26
23
|
|
|
27
|
-
const FlowsIcon = (
|
|
28
|
-
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor"
|
|
29
|
-
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
30
|
-
<rect x="3" y="3" width="6" height="6" rx="1" />
|
|
31
|
-
<rect x="15" y="15" width="6" height="6" rx="1" />
|
|
32
|
-
<path d="M9 6h4a2 2 0 0 1 2 2v7" />
|
|
33
|
-
</svg>
|
|
34
|
-
)
|
|
35
|
-
|
|
36
24
|
const FormsIcon = (
|
|
37
25
|
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor"
|
|
38
26
|
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
@@ -80,8 +68,6 @@ function Shell() {
|
|
|
80
68
|
const drawerItems = useMemo(() => [
|
|
81
69
|
{ key: 'Home', icon: HomeIcon, path: '/home', clickHandler: () => navigate('/home') },
|
|
82
70
|
{ key: 'Tasks', icon: TasksIcon, path: '/tasks', clickHandler: () => navigate('/tasks') },
|
|
83
|
-
// Always in the rail — the page says when Xeplr Workflow is not connected.
|
|
84
|
-
{ key: 'Flows', icon: FlowsIcon, path: '/flows', clickHandler: () => navigate('/flows') },
|
|
85
71
|
__MT_DRAWER_ITEM__
|
|
86
72
|
// Every form added to the menu, with no code: "form:crop" opens /forms/crop.
|
|
87
73
|
...menus.filter((key) => key.startsWith(FORM_MENU_PREFIX)).map((key) => ({
|
|
@@ -129,12 +115,6 @@ __MT_SELECT_ROUTE__
|
|
|
129
115
|
<Route path="/forms/:form" element={<FormRecords />} />
|
|
130
116
|
<Route path="/configure" element={<ConfigureUI />} />
|
|
131
117
|
<Route path="/configure/forms/:form/design/:part" element={<FormDesigner />} />
|
|
132
|
-
{/* Flows — run by @xeplr/workflow inside this app's own API. */}
|
|
133
|
-
<Route path="/flows" element={<FlowsHome />} />
|
|
134
|
-
<Route path="/configure/flows/:flow" element={<FlowDesigner />} />
|
|
135
|
-
{/* A journey: its run id joins the address once it starts, so it can be resumed. */}
|
|
136
|
-
<Route path="/journey/:flow" element={<Journey />} />
|
|
137
|
-
<Route path="/journey/:flow/:run" element={<Journey />} />
|
|
138
118
|
</Route>
|
|
139
119
|
|
|
140
120
|
<Route path="*" element={<Navigate to="/auth/login" replace />} />
|
|
@@ -358,19 +358,3 @@ body { margin: 0; }
|
|
|
358
358
|
.app-menu-shown { display: inline-flex; gap: 6px; align-items: center; }
|
|
359
359
|
|
|
360
360
|
.app-menu-settings .app-table { margin-bottom: 14px; }
|
|
361
|
-
|
|
362
|
-
/* ── Flows, before Workflow is connected (src/pages/Flows.jsx) ─────────────── */
|
|
363
|
-
.app-connect {
|
|
364
|
-
max-width: 720px;
|
|
365
|
-
padding: 22px 24px;
|
|
366
|
-
border: 1px solid var(--xeplr-border-primary);
|
|
367
|
-
border-radius: 10px;
|
|
368
|
-
background: var(--xeplr-bg-tertiary);
|
|
369
|
-
line-height: 1.55;
|
|
370
|
-
}
|
|
371
|
-
.app-connect h2 { margin: 0 0 8px; font-size: 18px; }
|
|
372
|
-
.app-connect p { margin: 0 0 14px; }
|
|
373
|
-
.app-connect-steps { margin: 0 0 14px; padding-left: 20px; }
|
|
374
|
-
.app-connect-steps li { margin-bottom: 14px; }
|
|
375
|
-
.app-connect code { font-size: 12.5px; padding: 1px 5px; border-radius: 4px; background: var(--xeplr-bg-secondary); border: 1px solid var(--xeplr-border-primary); }
|
|
376
|
-
.app-connect pre { margin: 6px 0 0; font-size: 12.5px; }
|
package/templates/ui/src/menu.js
CHANGED
|
@@ -2,20 +2,21 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Forms every UI in the app: make one, design it, publish it, open it, add it
|
|
4
4
|
// to the side rail
|
|
5
|
+
// Masters the lists the rest of the app points at — departments, statuses,
|
|
6
|
+
// countries. Ordinary forms with a flag, gathered in one place because
|
|
7
|
+
// that is what they are: reference data, not daily work
|
|
5
8
|
// Menu the side rail's items: rename, reorder, hide
|
|
6
|
-
// Flows forms one after another — what is filled in decides the next; run by
|
|
7
|
-
// Xeplr Workflow, and says how to connect it until it is
|
|
8
9
|
//
|
|
9
10
|
// Configuration lives here, not in the side rail — the rail is for the pages
|
|
10
11
|
// people use.
|
|
11
12
|
import { useSearchParams } from 'react-router-dom'
|
|
12
13
|
import Forms from './Forms.jsx'
|
|
14
|
+
import Masters from './Masters.jsx'
|
|
13
15
|
import MenuSettings from './MenuSettings.jsx'
|
|
14
|
-
import Flows from './Flows.jsx'
|
|
15
16
|
|
|
16
17
|
const TABS = [
|
|
17
18
|
{ id: 'forms', label: 'Forms', Page: Forms },
|
|
18
|
-
{ id: '
|
|
19
|
+
{ id: 'masters', label: 'Masters', Page: Masters },
|
|
19
20
|
{ id: 'menu', label: 'Menu', Page: MenuSettings }
|
|
20
21
|
]
|
|
21
22
|
|