@stonecrop/nuxt 0.13.8 → 0.13.10
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/dist/module.d.mts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/package.json +9 -9
- package/src/cli/index.ts +4 -3
- package/src/cli/installers/doctypes.ts +17 -162
- package/src/cli/installers/frontend.ts +79 -1
- package/src/cli/installers/grafserv.ts +28 -180
- package/src/cli/utils/config.ts +39 -0
- package/src/cli/utils/templates.ts +34 -0
- package/templates/Project.json +56 -0
- package/templates/Task.json +74 -0
- package/templates/data.ts +90 -0
- package/templates/index.vue +98 -0
- package/templates/resolvers.ts +222 -205
- package/templates/schema.graphql +31 -4
- package/templates/stonecrop.client.ts +42 -0
- package/templates/stonecrop.ts +82 -0
- package/templates/useDoctypes.ts +53 -0
- package/templates/useRouteAdapter.ts +36 -0
- package/templates/Example.json +0 -84
- package/templates/example-table.json +0 -58
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template loading for CLI installers
|
|
3
|
+
*
|
|
4
|
+
* Templates live in the package's templates/ directory (shipped via the
|
|
5
|
+
* "files" field in package.json). There is deliberately no inline fallback:
|
|
6
|
+
* a missing template means a broken installation (wrong dist layout or
|
|
7
|
+
* corrupted install), and failing loudly beats scaffolding a silently
|
|
8
|
+
* broken app from stale duplicated content.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync } from 'node:fs'
|
|
12
|
+
import { readFile } from 'node:fs/promises'
|
|
13
|
+
import { join, dirname } from 'pathe'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Load a template file from the package's templates/ directory
|
|
20
|
+
* @throws if the template file cannot be found
|
|
21
|
+
*/
|
|
22
|
+
export async function loadTemplate(filename: string): Promise<string> {
|
|
23
|
+
// utils/ -> cli/ -> src/ -> package root
|
|
24
|
+
const templatePath = join(__dirname, '..', '..', '..', 'templates', filename)
|
|
25
|
+
|
|
26
|
+
if (!existsSync(templatePath)) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Template not found: ${templatePath}. ` +
|
|
29
|
+
'The @stonecrop/nuxt package installation appears to be incomplete — try reinstalling.'
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return readFile(templatePath, 'utf-8')
|
|
34
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Project",
|
|
3
|
+
"slug": "project",
|
|
4
|
+
"fields": [
|
|
5
|
+
{
|
|
6
|
+
"fieldname": "id",
|
|
7
|
+
"label": "ID",
|
|
8
|
+
"component": "ATextInput",
|
|
9
|
+
"fieldtype": "Data",
|
|
10
|
+
"mode": "display"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"fieldname": "title",
|
|
14
|
+
"label": "Title",
|
|
15
|
+
"component": "ATextInput",
|
|
16
|
+
"fieldtype": "Data",
|
|
17
|
+
"required": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"fieldname": "description",
|
|
21
|
+
"label": "Description",
|
|
22
|
+
"component": "ATextarea",
|
|
23
|
+
"fieldtype": "Text"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"fieldname": "status",
|
|
27
|
+
"label": "Status",
|
|
28
|
+
"component": "ADropdown",
|
|
29
|
+
"fieldtype": "Select",
|
|
30
|
+
"options": ["Active", "Archived"],
|
|
31
|
+
"default": "Active"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"fieldname": "createdAt",
|
|
35
|
+
"label": "Created At",
|
|
36
|
+
"component": "ATextInput",
|
|
37
|
+
"fieldtype": "Datetime",
|
|
38
|
+
"mode": "display"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"workflow": {
|
|
42
|
+
"states": ["Active", "Archived"],
|
|
43
|
+
"actions": {
|
|
44
|
+
"save": {
|
|
45
|
+
"label": "Save",
|
|
46
|
+
"handler": "project:save"
|
|
47
|
+
},
|
|
48
|
+
"archive": {
|
|
49
|
+
"label": "Archive Project",
|
|
50
|
+
"handler": "archive_project",
|
|
51
|
+
"allowedStates": ["Active"],
|
|
52
|
+
"confirm": true
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Task",
|
|
3
|
+
"slug": "task",
|
|
4
|
+
"fields": [
|
|
5
|
+
{
|
|
6
|
+
"fieldname": "id",
|
|
7
|
+
"label": "ID",
|
|
8
|
+
"component": "ATextInput",
|
|
9
|
+
"fieldtype": "Data",
|
|
10
|
+
"mode": "display"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"fieldname": "title",
|
|
14
|
+
"label": "Title",
|
|
15
|
+
"component": "ATextInput",
|
|
16
|
+
"fieldtype": "Data",
|
|
17
|
+
"required": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"fieldname": "projectId",
|
|
21
|
+
"label": "Project",
|
|
22
|
+
"component": "ACombobox",
|
|
23
|
+
"fieldtype": "Link",
|
|
24
|
+
"options": "project",
|
|
25
|
+
"required": true
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"fieldname": "status",
|
|
29
|
+
"label": "Status",
|
|
30
|
+
"component": "ADropdown",
|
|
31
|
+
"fieldtype": "Select",
|
|
32
|
+
"options": ["Todo", "In Progress", "Done"],
|
|
33
|
+
"default": "Todo"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"fieldname": "description",
|
|
37
|
+
"label": "Description",
|
|
38
|
+
"component": "ATextarea",
|
|
39
|
+
"fieldtype": "Text"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"fieldname": "dueDate",
|
|
43
|
+
"label": "Due Date",
|
|
44
|
+
"component": "ADatepicker",
|
|
45
|
+
"fieldtype": "Date"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"fieldname": "createdAt",
|
|
49
|
+
"label": "Created At",
|
|
50
|
+
"component": "ATextInput",
|
|
51
|
+
"fieldtype": "Datetime",
|
|
52
|
+
"mode": "display"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"workflow": {
|
|
56
|
+
"states": ["Todo", "In Progress", "Done"],
|
|
57
|
+
"actions": {
|
|
58
|
+
"save": {
|
|
59
|
+
"label": "Save",
|
|
60
|
+
"handler": "task:save"
|
|
61
|
+
},
|
|
62
|
+
"start_task": {
|
|
63
|
+
"label": "Start Task",
|
|
64
|
+
"handler": "start_task",
|
|
65
|
+
"allowedStates": ["Todo"]
|
|
66
|
+
},
|
|
67
|
+
"complete_task": {
|
|
68
|
+
"label": "Complete",
|
|
69
|
+
"handler": "complete_task",
|
|
70
|
+
"allowedStates": ["In Progress"]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory data store
|
|
3
|
+
*
|
|
4
|
+
* This module holds the in-memory Maps used as the data layer for this app.
|
|
5
|
+
* Both server/resolvers.ts (for reading) and server/plugins/stonecrop.ts (for
|
|
6
|
+
* writing via action handlers) import from here.
|
|
7
|
+
*
|
|
8
|
+
* To connect a real database, replace this module with your PostGraphile + pgClient setup.
|
|
9
|
+
* See: https://stonecrop.io/docs/guides/postgraphile
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface Project {
|
|
13
|
+
id: string
|
|
14
|
+
title: string
|
|
15
|
+
description: string
|
|
16
|
+
status: 'Active' | 'Archived'
|
|
17
|
+
createdAt: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface Task {
|
|
21
|
+
id: string
|
|
22
|
+
title: string
|
|
23
|
+
projectId: string
|
|
24
|
+
status: 'Todo' | 'In Progress' | 'Done'
|
|
25
|
+
description: string
|
|
26
|
+
dueDate: string | null
|
|
27
|
+
createdAt: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const projects = new Map<string, Project>([
|
|
31
|
+
[
|
|
32
|
+
'1',
|
|
33
|
+
{
|
|
34
|
+
id: '1',
|
|
35
|
+
title: 'Website Redesign',
|
|
36
|
+
description: 'Redesign the company website with a modern look and feel',
|
|
37
|
+
status: 'Active',
|
|
38
|
+
createdAt: '2025-01-01T00:00:00Z',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
'2',
|
|
43
|
+
{
|
|
44
|
+
id: '2',
|
|
45
|
+
title: 'Mobile App',
|
|
46
|
+
description: 'Build the iOS and Android app for field staff',
|
|
47
|
+
status: 'Active',
|
|
48
|
+
createdAt: '2025-01-02T00:00:00Z',
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
])
|
|
52
|
+
|
|
53
|
+
export const tasks = new Map<string, Task>([
|
|
54
|
+
[
|
|
55
|
+
'1',
|
|
56
|
+
{
|
|
57
|
+
id: '1',
|
|
58
|
+
title: 'Create wireframes',
|
|
59
|
+
projectId: '1',
|
|
60
|
+
status: 'Todo',
|
|
61
|
+
description: 'Draft initial wireframes for all main pages',
|
|
62
|
+
dueDate: '2025-02-01',
|
|
63
|
+
createdAt: '2025-01-05T00:00:00Z',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
[
|
|
67
|
+
'2',
|
|
68
|
+
{
|
|
69
|
+
id: '2',
|
|
70
|
+
title: 'Set up design system',
|
|
71
|
+
projectId: '1',
|
|
72
|
+
status: 'In Progress',
|
|
73
|
+
description: 'Configure colors, typography, and component library',
|
|
74
|
+
dueDate: null,
|
|
75
|
+
createdAt: '2025-01-06T00:00:00Z',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
'3',
|
|
80
|
+
{
|
|
81
|
+
id: '3',
|
|
82
|
+
title: 'Define API contract',
|
|
83
|
+
projectId: '2',
|
|
84
|
+
status: 'Todo',
|
|
85
|
+
description: 'Document all API endpoints needed for the mobile app',
|
|
86
|
+
dueDate: '2025-01-20',
|
|
87
|
+
createdAt: '2025-01-07T00:00:00Z',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
])
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ClientOnly>
|
|
3
|
+
<Desktop
|
|
4
|
+
:available-doctypes="availableDoctypes"
|
|
5
|
+
:route-adapter="routeAdapter"
|
|
6
|
+
@action="handleAction"
|
|
7
|
+
@load-records="handleLoadRecords"
|
|
8
|
+
@load-record="handleLoadRecord" />
|
|
9
|
+
<template #fallback>
|
|
10
|
+
<div class="sc-loading">
|
|
11
|
+
<p>Loading...</p>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
</ClientOnly>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import {
|
|
19
|
+
Desktop,
|
|
20
|
+
type ActionEventPayload,
|
|
21
|
+
type LoadRecordEventPayload,
|
|
22
|
+
type LoadRecordsEventPayload,
|
|
23
|
+
} from '@stonecrop/desktop'
|
|
24
|
+
import { useStonecrop } from '@stonecrop/stonecrop'
|
|
25
|
+
|
|
26
|
+
import { useRouteAdapter } from '~/composables/useRouteAdapter'
|
|
27
|
+
import {
|
|
28
|
+
doctypeMap,
|
|
29
|
+
useDoctypeConfig,
|
|
30
|
+
fetchDoctypeRecords,
|
|
31
|
+
fetchDoctypeRecord,
|
|
32
|
+
runDoctypeAction,
|
|
33
|
+
} from '~/composables/useDoctypes'
|
|
34
|
+
|
|
35
|
+
const routeAdapter = useRouteAdapter()
|
|
36
|
+
const { stonecrop } = useStonecrop()
|
|
37
|
+
|
|
38
|
+
const availableDoctypes = computed(() => Array.from(doctypeMap.keys()))
|
|
39
|
+
|
|
40
|
+
async function handleLoadRecords(payload: LoadRecordsEventPayload) {
|
|
41
|
+
const doctypeConfig = useDoctypeConfig(payload.doctype)
|
|
42
|
+
if (!doctypeConfig || !stonecrop.value) return
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const { data } = await fetchDoctypeRecords({ name: doctypeConfig.name })
|
|
46
|
+
for (const record of data) {
|
|
47
|
+
const recordId = record.id as string
|
|
48
|
+
if (recordId) stonecrop.value.addRecord(payload.doctype, recordId, record)
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Failed to load records:', error)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function handleLoadRecord(payload: LoadRecordEventPayload) {
|
|
56
|
+
if (!stonecrop.value || payload.recordId.startsWith('new-')) return
|
|
57
|
+
|
|
58
|
+
const doctypeConfig = useDoctypeConfig(payload.doctype)
|
|
59
|
+
if (!doctypeConfig) return
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const record = await fetchDoctypeRecord({ name: doctypeConfig.name }, payload.recordId)
|
|
63
|
+
if (record) stonecrop.value.addRecord(payload.doctype, payload.recordId, record)
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Failed to load record:', error)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function handleAction(payload: ActionEventPayload) {
|
|
70
|
+
const doctypeConfig = useDoctypeConfig(payload.doctype)
|
|
71
|
+
if (!doctypeConfig) return
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const result = await runDoctypeAction(doctypeConfig, payload.name, {
|
|
75
|
+
id: payload.recordId,
|
|
76
|
+
data: payload.data,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
if (result.success && result.data && stonecrop.value && payload.recordId) {
|
|
80
|
+
stonecrop.value.addRecord(payload.doctype, payload.recordId, result.data as Record<string, unknown>)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!result.success) console.error('Action failed:', result.error)
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('Action error:', error)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<style>
|
|
91
|
+
.sc-loading {
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: center;
|
|
95
|
+
min-height: 50vh;
|
|
96
|
+
color: #666;
|
|
97
|
+
}
|
|
98
|
+
</style>
|