@stonecrop/nuxt 0.7.3 → 0.7.5

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.
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Stonecrop Nuxt CLI - Main Installer
3
+ *
4
+ * Orchestrates the installation of Stonecrop features into a Nuxt project.
5
+ */
6
+
7
+ import consola from 'consola'
8
+ import { detectFeatures, getInstalledSummary } from './detect'
9
+ import { promptFeatures, type SelectedFeatures } from './prompts'
10
+ import { installFrontend } from './installers/frontend'
11
+ import { installGrafserv } from './installers/grafserv'
12
+ import { installGraphqlClient } from './installers/graphql-client'
13
+ import { installCasl } from './installers/casl'
14
+ import { installRockfoil } from './installers/rockfoil'
15
+ import { installDoctypes } from './installers/doctypes'
16
+ import { getInstallCommand } from './utils/package'
17
+
18
+ export interface InstallerOptions {
19
+ /** Working directory (defaults to current directory) */
20
+ cwd: string
21
+ /** Pre-selected features from CLI flags */
22
+ features?: Partial<SelectedFeatures>
23
+ /** Skip confirmation prompts */
24
+ skipConfirm?: boolean
25
+ }
26
+
27
+ /**
28
+ * Main installer entry point
29
+ */
30
+ export async function runInstaller(options: InstallerOptions): Promise<void> {
31
+ const { cwd, features: preselectedFeatures = {}, skipConfirm = false } = options
32
+
33
+ consola.box('🌱 Stonecrop Nuxt Installer')
34
+ console.log()
35
+
36
+ // Detect existing setup
37
+ consola.start('Detecting existing setup...')
38
+ const detected = await detectFeatures(cwd)
39
+
40
+ if (!detected.isNuxtProject) {
41
+ consola.error('This does not appear to be a Nuxt project.')
42
+ consola.info('Please run this command in a directory with nuxt.config.ts and Nuxt in package.json.')
43
+ process.exit(1)
44
+ }
45
+
46
+ consola.success('Nuxt project detected')
47
+
48
+ // Show what's already installed
49
+ const installed = getInstalledSummary(detected)
50
+ if (installed.length > 0) {
51
+ console.log()
52
+ consola.info('Already installed:')
53
+ for (const item of installed) {
54
+ consola.info(` ✓ ${item}`)
55
+ }
56
+ }
57
+
58
+ console.log()
59
+
60
+ // Prompt for features to install
61
+ const selectedFeatures = await promptFeatures({
62
+ preselected: preselectedFeatures,
63
+ detected,
64
+ skipConfirm,
65
+ })
66
+
67
+ if (!selectedFeatures) {
68
+ return
69
+ }
70
+
71
+ console.log()
72
+ consola.start('Installing selected features...')
73
+ console.log()
74
+
75
+ // Track installation results
76
+ const results: { feature: string; success: boolean }[] = []
77
+
78
+ // Install frontend module
79
+ if (selectedFeatures.frontend) {
80
+ const success = await installFrontend({ cwd })
81
+ results.push({ feature: '@stonecrop/nuxt', success })
82
+ console.log()
83
+ }
84
+
85
+ // Install GraphQL client
86
+ if (selectedFeatures.graphqlClient) {
87
+ const success = await installGraphqlClient({ cwd })
88
+ results.push({ feature: '@stonecrop/graphql-client', success })
89
+ console.log()
90
+ }
91
+
92
+ // Install GraphQL server
93
+ if (selectedFeatures.graphql) {
94
+ const success = await installGrafserv({ cwd })
95
+ results.push({ feature: '@stonecrop/nuxt-grafserv', success })
96
+ console.log()
97
+ }
98
+
99
+ // Install CASL middleware
100
+ if (selectedFeatures.casl) {
101
+ const success = await installCasl({ cwd })
102
+ results.push({ feature: '@stonecrop/casl-middleware', success })
103
+ console.log()
104
+ }
105
+
106
+ // Install Rockfoil middleware
107
+ if (selectedFeatures.rockfoil) {
108
+ const success = await installRockfoil({ cwd })
109
+ results.push({ feature: '@stonecrop/rockfoil', success })
110
+ console.log()
111
+ }
112
+
113
+ // Install sample doctypes
114
+ if (selectedFeatures.doctypes) {
115
+ const success = await installDoctypes({ cwd })
116
+ results.push({ feature: 'Sample doctypes', success })
117
+ console.log()
118
+ }
119
+
120
+ // Summary
121
+ const successCount = results.filter(r => r.success).length
122
+ const failCount = results.filter(r => !r.success).length
123
+
124
+ console.log()
125
+ if (failCount === 0) {
126
+ consola.success(`Successfully installed ${successCount} feature(s)`)
127
+ } else {
128
+ consola.warn(`Installed ${successCount} feature(s), ${failCount} failed`)
129
+ for (const result of results.filter(r => !r.success)) {
130
+ consola.error(` ✗ ${result.feature}`)
131
+ }
132
+ }
133
+
134
+ // Next steps
135
+ console.log()
136
+ consola.box('Next Steps')
137
+ console.log()
138
+
139
+ const installCmd = getInstallCommand(cwd)
140
+ consola.info(`1. Install dependencies:`)
141
+ consola.info(` ${installCmd}`)
142
+ console.log()
143
+
144
+ let stepNum = 2
145
+
146
+ if (selectedFeatures.graphql) {
147
+ consola.info(`${stepNum}. Update your GraphQL schema and resolvers:`)
148
+ consola.info(` - server/schema.graphql`)
149
+ consola.info(` - server/resolvers.ts`)
150
+ console.log()
151
+ stepNum++
152
+ }
153
+
154
+ if (selectedFeatures.rockfoil) {
155
+ consola.info(`${stepNum}. Configure Rockfoil:`)
156
+ consola.info(` - Set DATABASE_URL environment variable`)
157
+ consola.info(` - Configure the Rockfoil plugin options in nuxt.config.ts`)
158
+ console.log()
159
+ stepNum++
160
+ }
161
+
162
+ if (selectedFeatures.doctypes) {
163
+ consola.info(`${stepNum}. Customize your doctypes:`)
164
+ consola.info(` - doctypes/Example.json`)
165
+ consola.info(` - doctypes/example-table.json`)
166
+ console.log()
167
+ stepNum++
168
+ }
169
+
170
+ consola.info(`Start development server:`)
171
+ consola.info(` pnpm dev`)
172
+ console.log()
173
+
174
+ if (selectedFeatures.graphql) {
175
+ consola.info(`Access GraphiQL:`)
176
+ consola.info(` http://localhost:3000/graphql/`)
177
+ consola.info(` (Make sure to restart your dev server if it's already running)`)
178
+ console.log()
179
+ }
180
+ }
181
+
182
+ // Re-export types for external use
183
+ export type { SelectedFeatures } from './prompts'
184
+ export type { DetectedFeatures } from './detect'
@@ -0,0 +1,51 @@
1
+ /**
2
+ * CASL middleware installer
3
+ * Installs @stonecrop/casl-middleware and updates nuxt.config.ts
4
+ */
5
+
6
+ import consola from 'consola'
7
+
8
+ import { updateNuxtConfig } from '../utils/config'
9
+ import { addDependencies } from '../utils/package'
10
+ import { addPluginToGrafservConfig } from '../utils/plugin'
11
+
12
+ export interface CaslInstallerOptions {
13
+ cwd: string
14
+ }
15
+
16
+ /**
17
+ * Install the @stonecrop/casl-middleware authorization package
18
+ */
19
+ export async function installCasl(options: CaslInstallerOptions): Promise<boolean> {
20
+ const { cwd } = options
21
+
22
+ consola.start('Installing @stonecrop/casl-middleware authorization...')
23
+
24
+ try {
25
+ // Add dependencies - use latest published version
26
+ await addDependencies(cwd, {
27
+ '@stonecrop/casl-middleware': 'latest',
28
+ '@casl/ability': '^6.7.3',
29
+ })
30
+
31
+ // Add import to nuxt.config.ts
32
+ await updateNuxtConfig(cwd, {
33
+ import: "import { pglCaslPlugin } from '@stonecrop/casl-middleware'",
34
+ })
35
+
36
+ // Add plugin to grafserv preset configuration
37
+ const pluginAdded = await addPluginToGrafservConfig(cwd, 'pglCaslPlugin')
38
+
39
+ if (pluginAdded) {
40
+ consola.success('@stonecrop/casl-middleware installed and configured successfully')
41
+ } else {
42
+ consola.success('@stonecrop/casl-middleware installed successfully')
43
+ consola.info('Add pglCaslPlugin to your grafserv preset plugins array manually')
44
+ }
45
+
46
+ return true
47
+ } catch (error) {
48
+ consola.error('Failed to install @stonecrop/casl-middleware:', error)
49
+ return false
50
+ }
51
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Sample doctypes installer
3
+ * Scaffolds example doctype JSON files
4
+ */
5
+
6
+ import { existsSync } from 'node:fs'
7
+ import { mkdir, writeFile, readFile } from 'node:fs/promises'
8
+ import { join, dirname } from 'pathe'
9
+ import { fileURLToPath } from 'node:url'
10
+ import consola from 'consola'
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url))
13
+
14
+ export interface DoctypesInstallerOptions {
15
+ cwd: string
16
+ }
17
+
18
+ /**
19
+ * Install sample doctype files
20
+ */
21
+ export async function installDoctypes(options: DoctypesInstallerOptions): Promise<boolean> {
22
+ const { cwd } = options
23
+
24
+ consola.start('Scaffolding sample doctypes...')
25
+
26
+ try {
27
+ const doctypesDir = join(cwd, 'doctypes')
28
+
29
+ // Create doctypes directory if it doesn't exist
30
+ if (!existsSync(doctypesDir)) {
31
+ await mkdir(doctypesDir, { recursive: true })
32
+ consola.info('Created doctypes/ directory')
33
+ }
34
+
35
+ // Scaffold Example.json (form doctype)
36
+ const examplePath = join(doctypesDir, 'Example.json')
37
+ if (!existsSync(examplePath)) {
38
+ const exampleTemplate = await loadTemplate('Example.json')
39
+ await writeFile(examplePath, exampleTemplate, 'utf-8')
40
+ consola.info('Created doctypes/Example.json')
41
+ } else {
42
+ consola.info('doctypes/Example.json already exists, skipping')
43
+ }
44
+
45
+ // Scaffold example-table.json (table doctype)
46
+ const tableExamplePath = join(doctypesDir, 'example-table.json')
47
+ if (!existsSync(tableExamplePath)) {
48
+ const tableTemplate = await loadTemplate('example-table.json')
49
+ await writeFile(tableExamplePath, tableTemplate, 'utf-8')
50
+ consola.info('Created doctypes/example-table.json')
51
+ } else {
52
+ consola.info('doctypes/example-table.json already exists, skipping')
53
+ }
54
+
55
+ consola.success('Sample doctypes created successfully')
56
+ return true
57
+ } catch (error) {
58
+ consola.error('Failed to scaffold doctypes:', error)
59
+ return false
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Load a template file
65
+ */
66
+ async function loadTemplate(filename: string): Promise<string> {
67
+ // Try to load from templates directory
68
+ const templatePath = join(__dirname, '..', '..', '..', 'templates', filename)
69
+
70
+ if (existsSync(templatePath)) {
71
+ return readFile(templatePath, 'utf-8')
72
+ }
73
+
74
+ // Fallback to inline templates
75
+ return getInlineTemplate(filename)
76
+ }
77
+
78
+ /**
79
+ * Get inline template content as fallback
80
+ */
81
+ function getInlineTemplate(filename: string): string {
82
+ const templates: Record<string, string> = {
83
+ 'Example.json': JSON.stringify(
84
+ {
85
+ name: 'Example',
86
+ slug: 'example/:id',
87
+ tableName: 'examples',
88
+ fields: [
89
+ {
90
+ fieldname: 'id',
91
+ fieldtype: 'Data',
92
+ label: 'ID',
93
+ readOnly: true,
94
+ },
95
+ {
96
+ fieldname: 'title',
97
+ fieldtype: 'Data',
98
+ label: 'Title',
99
+ required: true,
100
+ },
101
+ {
102
+ fieldname: 'description',
103
+ fieldtype: 'Text',
104
+ label: 'Description',
105
+ },
106
+ {
107
+ fieldname: 'status',
108
+ fieldtype: 'Select',
109
+ label: 'Status',
110
+ options: ['Draft', 'Active', 'Archived'],
111
+ default: 'Draft',
112
+ },
113
+ {
114
+ fieldname: 'priority',
115
+ fieldtype: 'Select',
116
+ label: 'Priority',
117
+ options: ['Low', 'Medium', 'High'],
118
+ default: 'Medium',
119
+ },
120
+ {
121
+ fieldname: 'createdAt',
122
+ fieldtype: 'Datetime',
123
+ label: 'Created At',
124
+ readOnly: true,
125
+ },
126
+ {
127
+ fieldname: 'updatedAt',
128
+ fieldtype: 'Datetime',
129
+ label: 'Updated At',
130
+ readOnly: true,
131
+ },
132
+ ],
133
+ workflow: {
134
+ states: ['Draft', 'Active', 'Archived'],
135
+ actions: {
136
+ activate: {
137
+ label: 'Activate',
138
+ handler: 'activate_example',
139
+ allowedStates: ['Draft'],
140
+ confirm: true,
141
+ },
142
+ archive: {
143
+ label: 'Archive',
144
+ handler: 'archive_example',
145
+ allowedStates: ['Active'],
146
+ confirm: true,
147
+ },
148
+ },
149
+ },
150
+ },
151
+ null,
152
+ '\t'
153
+ ),
154
+ 'example-table.json': JSON.stringify(
155
+ {
156
+ name: 'Example',
157
+ slug: 'example',
158
+ tableName: 'examples',
159
+ schema: [
160
+ {
161
+ component: 'ATable',
162
+ columns: [
163
+ {
164
+ name: 'id',
165
+ label: 'ID',
166
+ fieldtype: 'Data',
167
+ width: '8ch',
168
+ },
169
+ {
170
+ name: 'title',
171
+ label: 'Title',
172
+ fieldtype: 'Data',
173
+ width: '20ch',
174
+ },
175
+ {
176
+ name: 'status',
177
+ label: 'Status',
178
+ fieldtype: 'Data',
179
+ width: '10ch',
180
+ },
181
+ {
182
+ name: 'priority',
183
+ label: 'Priority',
184
+ fieldtype: 'Data',
185
+ width: '10ch',
186
+ },
187
+ {
188
+ name: 'createdAt',
189
+ label: 'Created',
190
+ fieldtype: 'Datetime',
191
+ width: '18ch',
192
+ },
193
+ ],
194
+ config: {
195
+ view: 'list',
196
+ },
197
+ },
198
+ ],
199
+ },
200
+ null,
201
+ '\t'
202
+ ),
203
+ }
204
+
205
+ return templates[filename] || '{}'
206
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Frontend module installer
3
+ * Installs @stonecrop/nuxt and configures nuxt.config.ts
4
+ */
5
+
6
+ import consola from 'consola'
7
+ import { addDependencies } from '../utils/package'
8
+ import { updateNuxtConfig } from '../utils/config'
9
+
10
+ export interface FrontendInstallerOptions {
11
+ cwd: string
12
+ }
13
+
14
+ /**
15
+ * Install the @stonecrop/nuxt frontend module
16
+ */
17
+ export async function installFrontend(options: FrontendInstallerOptions): Promise<boolean> {
18
+ const { cwd } = options
19
+
20
+ consola.start('Installing @stonecrop/nuxt frontend module...')
21
+
22
+ try {
23
+ // Add @stonecrop/nuxt and its required peer dependencies
24
+ // These packages are imported by the Nuxt module and need to be available at runtime
25
+ await addDependencies(cwd, {
26
+ '@stonecrop/nuxt': 'latest',
27
+ '@stonecrop/aform': 'latest',
28
+ '@stonecrop/atable': 'latest',
29
+ '@stonecrop/stonecrop': 'latest',
30
+ '@stonecrop/node-editor': 'latest',
31
+ '@stonecrop/schema': 'latest',
32
+ '@stonecrop/utilities': 'latest',
33
+ pinia: '^3.0.4',
34
+ })
35
+
36
+ // Update nuxt.config.ts with module and Nitro configuration
37
+ await updateNuxtConfig(cwd, {
38
+ module: "'@stonecrop/nuxt'",
39
+ moduleOptions: {
40
+ key: 'stonecrop',
41
+ value: `{
42
+ // Enable DocBuilder for visual schema editing
43
+ docbuilder: false,
44
+ }`,
45
+ },
46
+ // Add Nitro configuration to handle CSS imports in Stonecrop packages
47
+ // This is required because the packages use vite-plugin-lib-inject-css which adds
48
+ // CSS imports to the JavaScript bundles. Node.js ESM loader doesn't understand .css files,
49
+ // so we need Nitro to bundle these packages (allowing Vite to process the CSS)
50
+ nitroConfig: {
51
+ externalsInline: [
52
+ '@stonecrop/aform',
53
+ '@stonecrop/atable',
54
+ '@stonecrop/stonecrop',
55
+ '@stonecrop/node-editor',
56
+ '@stonecrop/utilities',
57
+ ],
58
+ },
59
+ })
60
+
61
+ consola.success('@stonecrop/nuxt installed successfully')
62
+ consola.info('Added Nitro configuration for CSS handling')
63
+ return true
64
+ } catch (error) {
65
+ consola.error('Failed to install @stonecrop/nuxt:', error)
66
+ return false
67
+ }
68
+ }