@xeplr/cli 0.1.1 → 0.1.3

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 (43) hide show
  1. package/README.md +47 -13
  2. package/bin/xeplr.js +10 -4
  3. package/lib/generate/index.js +131 -4
  4. package/lib/nextSteps.js +33 -18
  5. package/lib/plan.js +7 -0
  6. package/lib/questions.js +60 -1
  7. package/lib/setup.js +59 -0
  8. package/package.json +2 -1
  9. package/templates/api/app.js +20 -1
  10. package/templates/api/bin/www +18 -4
  11. package/templates/api/db/setup.js +1 -0
  12. package/templates/api/development.env.tpl +8 -2
  13. package/templates/api/migrations/README.md +9 -0
  14. package/templates/api/migrations-auth/0003_designer_menu.sql +19 -0
  15. package/templates/api/models/index.js +7 -6
  16. package/templates/api/package.json +5 -3
  17. package/templates/api/routes/index.js +6 -15
  18. package/templates/api/screens/index.js +32 -0
  19. package/templates/api/screens/task/task-edit.screen.json +103 -0
  20. package/templates/api/screens/task/task-list.screen.json +45 -0
  21. package/templates/api/screens/task/task.entity.json +10 -0
  22. package/templates/api/screens/task/task.hooks.js +37 -0
  23. package/templates/ui/package.json +4 -0
  24. package/templates/ui/src/App.jsx +18 -2
  25. package/templates/ui/src/api/factory.js +10 -0
  26. package/templates/ui/src/index.css +25 -0
  27. package/templates/ui/src/main.jsx +2 -0
  28. package/templates/ui/src/pages/Designer.jsx +74 -0
  29. package/templates/ui/src/pages/Home.jsx +4 -0
  30. package/templates/ui/src/pages/Tasks.jsx +18 -94
  31. package/templates/ui/src/pages/TasksHelp.jsx +35 -33
  32. package/templates/ui/vite.config.js +1 -1
  33. package/templates-tenancy/api/migrations/0001_tenants.sql +8 -0
  34. package/templates-tenancy/api/migrations-auth/0004_switch_menu.sql +10 -0
  35. package/templates-tenancy/api/routes/tenants.js +130 -0
  36. package/templates-tenancy/api/tenancy.js +12 -0
  37. package/templates-tenancy/ui/src/pages/SelectScope.css +45 -0
  38. package/templates-tenancy/ui/src/pages/SelectScope.jsx +154 -0
  39. package/templates-tenancy/ui/src/tenancy.js +3 -0
  40. package/templates/api/migrations/0001_tasks.sql +0 -23
  41. package/templates/api/models/Task.js +0 -48
  42. package/templates/ui/src/api/tasks.js +0 -34
  43. package/templates/ui/src/useTasksController.js +0 -83
@@ -1,48 +0,0 @@
1
- var { BaseModel } = require('@xeplr/db');
2
-
3
- /**
4
- * A task. The sample model — deliberately small, and deliberately showing the
5
- * three things almost every real model needs: required fields, a value
6
- * restricted to a known set, and a date.
7
- *
8
- * `jsonSchema` is not decoration. It is checked on every insert and update, so
9
- * a bad value is refused by the API with a message naming the field, rather
10
- * than reaching the database and failing as a constraint error nobody can read.
11
- */
12
- class Task extends BaseModel {
13
- static get tableName() { return 'tasks'; }
14
- static get idColumn() { return 'id'; }
15
-
16
- // Rows are NOT scoped to a company/workspace here, because this app has not
17
- // declared any tenancy levels. Leave it as the default (true) and add
18
- // registerMTs() later, and every row written before that has no owner.
19
- static get multiTenant() { return false; }
20
-
21
- static get jsonSchema() {
22
- return {
23
- type: 'object',
24
- required: ['title'],
25
- properties: {
26
- // 25 characters, because that is what the framework's own id generator
27
- // produces (see genericController — ids are minted there, not here).
28
- id: { type: 'string', maxLength: 25 },
29
- title: { type: 'string', minLength: 1, maxLength: 200 },
30
- description: { type: ['string', 'null'], maxLength: 1000 },
31
- status: { type: 'string', enum: ['todo', 'in_progress', 'done'] },
32
- dueDate: { type: ['string', 'null'] },
33
- isActive: { type: 'boolean' },
34
- recordCreatedDate: { type: ['string', 'null'] },
35
- recordModifiedDate: { type: ['string', 'null'] },
36
- recordCreatedBy: { type: ['string', 'null'], maxLength: 25 },
37
- recordModifiedBy: { type: ['string', 'null'], maxLength: 25 }
38
- }
39
- };
40
- }
41
-
42
- $beforeInsert() {
43
- super.$beforeInsert(); // keeps recordCreatedDate / isActive handling
44
- if (!this.status) this.status = 'todo';
45
- }
46
- }
47
-
48
- module.exports = Task;
@@ -1,34 +0,0 @@
1
- // MODEL LAYER — plain functions, no React.
2
- //
3
- // Kept apart from the screen on purpose: this file could be called from a
4
- // test, a script, or a different design entirely. `authFetch` attaches the
5
- // signed-in user's token and returns the PARSED body, throwing on failure.
6
- import { authFetch } from '@xeplr/ui-account'
7
-
8
- export async function listTasks() {
9
- const res = await authFetch('/api/tasks')
10
- return res.dataArray || []
11
- }
12
-
13
- /**
14
- * Create and update in one call.
15
- *
16
- * The API takes a CHANGESET — an array where each entry is judged by what it
17
- * carries: no id means create, an id means update, an id plus `deleted: true`
18
- * means remove. One request can do all three, and the whole array is applied
19
- * in a single transaction, so a later failure undoes the earlier entries
20
- * rather than leaving half of them written.
21
- */
22
- export function saveTasks(changeset) {
23
- return authFetch('/api/tasks/save', {
24
- method: 'POST',
25
- body: JSON.stringify(changeset)
26
- })
27
- }
28
-
29
- export function deleteTasks(ids) {
30
- return authFetch('/api/tasks/delete', {
31
- method: 'POST',
32
- body: JSON.stringify({ ids })
33
- })
34
- }
@@ -1,83 +0,0 @@
1
- // CONTROLLER LAYER — state and handlers, no JSX.
2
- //
3
- // The same split the @xeplr/ui-* libraries use. A different design can import
4
- // this hook and render the fields however it likes; the behaviour below is
5
- // unchanged by that, which is what makes a rebrand safe.
6
- import { useCallback, useEffect, useState } from 'react'
7
- import { listTasks, saveTasks, deleteTasks } from './api/tasks.js'
8
-
9
- const EMPTY = { title: '', description: '', status: 'todo', dueDate: '' }
10
-
11
- export function useTasksController() {
12
- const [tasks, setTasks] = useState([])
13
- const [form, setForm] = useState(EMPTY)
14
- const [editingId, setEditingId] = useState(null)
15
- const [busy, setBusy] = useState(false)
16
- const [error, setError] = useState(null)
17
-
18
- const refresh = useCallback(async () => {
19
- try {
20
- setTasks(await listTasks())
21
- setError(null)
22
- } catch (e) {
23
- setError(e.message)
24
- }
25
- }, [])
26
-
27
- useEffect(() => { refresh() }, [refresh])
28
-
29
- const setField = useCallback((name, value) => {
30
- setForm((f) => ({ ...f, [name]: value }))
31
- }, [])
32
-
33
- const edit = useCallback((task) => {
34
- setEditingId(task.id)
35
- setForm({
36
- title: task.title || '',
37
- description: task.description || '',
38
- status: task.status || 'todo',
39
- // The API returns a full timestamp; a date input wants just the day.
40
- dueDate: task.dueDate ? String(task.dueDate).slice(0, 10) : ''
41
- })
42
- }, [])
43
-
44
- const cancel = useCallback(() => {
45
- setEditingId(null)
46
- setForm(EMPTY)
47
- }, [])
48
-
49
- const submit = useCallback(async (e) => {
50
- if (e) e.preventDefault()
51
- setBusy(true)
52
- try {
53
- // An entry with no id is a create; with one, an update. Same call.
54
- const entry = { ...form, dueDate: form.dueDate || null }
55
- if (editingId) entry.id = editingId
56
- await saveTasks([entry])
57
- cancel()
58
- await refresh()
59
- setError(null)
60
- } catch (e2) {
61
- // The API refuses on the model's own jsonSchema and names the field, so
62
- // showing its message beats inventing our own.
63
- setError(e2.message)
64
- } finally {
65
- setBusy(false)
66
- }
67
- }, [form, editingId, cancel, refresh])
68
-
69
- const remove = useCallback(async (id) => {
70
- setBusy(true)
71
- try {
72
- await deleteTasks([id])
73
- if (editingId === id) cancel()
74
- await refresh()
75
- } catch (e) {
76
- setError(e.message)
77
- } finally {
78
- setBusy(false)
79
- }
80
- }, [editingId, cancel, refresh])
81
-
82
- return { tasks, form, setField, submit, edit, cancel, remove, editingId, busy, error, refresh }
83
- }