@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,308 @@
1
+ /**
2
+ * GraphQL server installer
3
+ * Installs @stonecrop/nuxt-grafserv and scaffolds server 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
+ import { addDependencies } from '../utils/package'
12
+ import { updateNuxtConfig } from '../utils/config'
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url))
15
+
16
+ export interface GrafservInstallerOptions {
17
+ cwd: string
18
+ schemaPath?: string
19
+ resolversPath?: string
20
+ endpoint?: string
21
+ }
22
+
23
+ /**
24
+ * Install the @stonecrop/nuxt-grafserv GraphQL server
25
+ */
26
+ export async function installGrafserv(options: GrafservInstallerOptions): Promise<boolean> {
27
+ const {
28
+ cwd,
29
+ schemaPath = 'server/schema.graphql',
30
+ resolversPath = 'server/resolvers.ts',
31
+ endpoint = '/graphql/',
32
+ } = options
33
+
34
+ consola.start('Installing @stonecrop/nuxt-grafserv GraphQL server...')
35
+
36
+ try {
37
+ // Scaffold server files first
38
+ await scaffoldServerFiles(cwd)
39
+
40
+ // Add dependencies - use latest published versions
41
+ await addDependencies(cwd, {
42
+ '@stonecrop/nuxt-grafserv': 'latest',
43
+ '@stonecrop/graphql-middleware': 'latest',
44
+ graphql: '^16.11.0',
45
+ grafast: '^1.0.0-rc.4',
46
+ })
47
+
48
+ // Update nuxt.config.ts
49
+ const configUpdated = await updateNuxtConfig(cwd, {
50
+ import: "import type { ModuleOptions as GrafservOptions } from '@stonecrop/nuxt-grafserv'",
51
+ module: "'@stonecrop/nuxt-grafserv'",
52
+ moduleOptions: {
53
+ key: 'grafserv',
54
+ value: `{
55
+ // GraphQL schema and resolvers
56
+ schema: '${schemaPath}',
57
+ resolvers: '${resolversPath}',
58
+
59
+ // GraphQL endpoint
60
+ url: '${endpoint}',
61
+
62
+ // Enable GraphiQL in development
63
+ graphiql: true,
64
+
65
+ // Graphile preset with grafserv options
66
+ preset: {
67
+ grafserv: {
68
+ websockets: false,
69
+ },
70
+ },
71
+ } as GrafservOptions`,
72
+ },
73
+ })
74
+
75
+ if (!configUpdated) {
76
+ consola.warn('Could not automatically update nuxt.config.ts')
77
+ consola.info('Please add @stonecrop/nuxt-grafserv to your modules array manually')
78
+ }
79
+
80
+ consola.success('@stonecrop/nuxt-grafserv installed successfully')
81
+ return true
82
+ } catch (error) {
83
+ consola.error('Failed to install @stonecrop/nuxt-grafserv:', error)
84
+ return false
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Scaffold the server directory with GraphQL files
90
+ */
91
+ async function scaffoldServerFiles(cwd: string): Promise<void> {
92
+ const serverDir = join(cwd, 'server')
93
+
94
+ // Create server directory if it doesn't exist
95
+ if (!existsSync(serverDir)) {
96
+ await mkdir(serverDir, { recursive: true })
97
+ consola.info('Created server/ directory')
98
+ }
99
+
100
+ // Scaffold schema.graphql
101
+ const schemaPath = join(serverDir, 'schema.graphql')
102
+ if (!existsSync(schemaPath)) {
103
+ const schemaTemplate = await loadTemplate('schema.graphql')
104
+ await writeFile(schemaPath, schemaTemplate, 'utf-8')
105
+ consola.info('Created server/schema.graphql')
106
+ } else {
107
+ consola.info('server/schema.graphql already exists, skipping')
108
+ }
109
+
110
+ // Scaffold resolvers.ts
111
+ const resolversPath = join(serverDir, 'resolvers.ts')
112
+ if (!existsSync(resolversPath)) {
113
+ const resolversTemplate = await loadTemplate('resolvers.ts')
114
+ await writeFile(resolversPath, resolversTemplate, 'utf-8')
115
+ consola.info('Created server/resolvers.ts')
116
+ } else {
117
+ consola.info('server/resolvers.ts already exists, skipping')
118
+ }
119
+
120
+ // Scaffold plugins.ts
121
+ const pluginsPath = join(serverDir, 'plugins.ts')
122
+ if (!existsSync(pluginsPath)) {
123
+ const pluginsTemplate = await loadTemplate('plugins.ts')
124
+ await writeFile(pluginsPath, pluginsTemplate, 'utf-8')
125
+ consola.info('Created server/plugins.ts')
126
+ } else {
127
+ consola.info('server/plugins.ts already exists, skipping')
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Load a template file
133
+ */
134
+ async function loadTemplate(filename: string): Promise<string> {
135
+ // Try to load from templates directory
136
+ const templatePath = join(__dirname, '..', '..', '..', 'templates', filename)
137
+
138
+ if (existsSync(templatePath)) {
139
+ return readFile(templatePath, 'utf-8')
140
+ }
141
+
142
+ // Fallback to inline templates
143
+ return getInlineTemplate(filename)
144
+ }
145
+
146
+ /**
147
+ * Get inline template content as fallback
148
+ */
149
+ function getInlineTemplate(filename: string): string {
150
+ const templates: Record<string, string> = {
151
+ 'schema.graphql': `# Stonecrop GraphQL Schema
152
+ # Add your type definitions here
153
+
154
+ scalar JSON
155
+
156
+ type Query {
157
+ """
158
+ Health check endpoint
159
+ """
160
+ healthCheck: HealthStatus!
161
+
162
+ """
163
+ Get metadata for a doctype
164
+ """
165
+ getMeta(doctype: String!): JSON
166
+ }
167
+
168
+ type Mutation {
169
+ """
170
+ Execute a doctype action
171
+ """
172
+ stonecropAction(doctype: String!, action: String!, args: JSON): ActionResult!
173
+ }
174
+
175
+ type HealthStatus {
176
+ status: String!
177
+ timestamp: String!
178
+ }
179
+
180
+ type ActionResult {
181
+ success: Boolean!
182
+ data: JSON
183
+ error: String
184
+ }
185
+ `,
186
+ 'resolvers.ts': `/**
187
+ * GraphQL Resolvers
188
+ * Add your resolver implementations here
189
+ */
190
+
191
+ export const resolvers = {
192
+ Query: {
193
+ healthCheck: () => ({
194
+ status: 'healthy',
195
+ timestamp: new Date().toISOString(),
196
+ }),
197
+
198
+ getMeta: (_: unknown, { doctype }: { doctype: string }) => {
199
+ // TODO: Implement doctype metadata lookup
200
+ console.log('getMeta called for:', doctype)
201
+ return null
202
+ },
203
+ },
204
+
205
+ Mutation: {
206
+ stonecropAction: async (
207
+ _: unknown,
208
+ { doctype, action, args }: { doctype: string; action: string; args?: unknown }
209
+ ) => {
210
+ // TODO: Implement action execution
211
+ console.log('stonecropAction called:', { doctype, action, args })
212
+ return {
213
+ success: true,
214
+ data: null,
215
+ error: null,
216
+ }
217
+ },
218
+ },
219
+ }
220
+
221
+ export default resolvers
222
+ `,
223
+ 'plugins.ts': `/**
224
+ * Grafserv Plugins
225
+ * Add custom middleware and hooks via Grafserv plugins
226
+ *
227
+ * @see https://grafast.org/grafserv/plugins
228
+ */
229
+
230
+ import type { GraphileConfig } from 'graphile-config'
231
+
232
+ /**
233
+ * Example: Request logging plugin
234
+ */
235
+ const loggingPlugin: GraphileConfig.Plugin = {
236
+ name: 'request-logging',
237
+ version: '1.0.0',
238
+ grafserv: {
239
+ middleware: {
240
+ processGraphQLRequestBody: async (next, event) => {
241
+ const start = Date.now()
242
+ console.log('[GraphQL] Request started:', {
243
+ path: event.request.url,
244
+ method: event.request.method,
245
+ })
246
+
247
+ const result = await next()
248
+
249
+ const duration = Date.now() - start
250
+ console.log(\`[GraphQL] Request completed in \${duration}ms\`)
251
+
252
+ return result
253
+ },
254
+ },
255
+ },
256
+ }
257
+
258
+ /**
259
+ * Example: Authentication plugin
260
+ */
261
+ const authPlugin: GraphileConfig.Plugin = {
262
+ name: 'authentication',
263
+ version: '1.0.0',
264
+ grafserv: {
265
+ middleware: {
266
+ processGraphQLRequestBody: async (next, event) => {
267
+ // Extract authentication from headers
268
+ const authHeader = event.request.headers.get('authorization')
269
+
270
+ if (authHeader?.startsWith('Bearer ')) {
271
+ const token = authHeader.slice(7)
272
+ // TODO: Validate token and set user context
273
+ console.log('[Auth] Token received:', token)
274
+ } else {
275
+ console.log('[Auth] Anonymous request')
276
+ }
277
+
278
+ return next()
279
+ },
280
+ },
281
+ },
282
+ }
283
+
284
+ /**
285
+ * Export all plugins
286
+ * Import these in your nuxt.config.ts:
287
+ *
288
+ * import plugins from './server/plugins'
289
+ *
290
+ * export default defineNuxtConfig({
291
+ * grafserv: {
292
+ * preset: {
293
+ * plugins
294
+ * }
295
+ * }
296
+ * })
297
+ */
298
+ export const plugins: GraphileConfig.Plugin[] = [
299
+ loggingPlugin,
300
+ // authPlugin, // Uncomment to enable authentication
301
+ ]
302
+
303
+ export default plugins
304
+ `,
305
+ }
306
+
307
+ return templates[filename] || ''
308
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * GraphQL client installer
3
+ * Installs @stonecrop/graphql-client for frontend GraphQL operations
4
+ */
5
+
6
+ import consola from 'consola'
7
+ import { addDependencies } from '../utils/package'
8
+
9
+ export interface GraphqlClientInstallerOptions {
10
+ cwd: string
11
+ }
12
+
13
+ /**
14
+ * Install the @stonecrop/graphql-client package
15
+ */
16
+ export async function installGraphqlClient(options: GraphqlClientInstallerOptions): Promise<boolean> {
17
+ const { cwd } = options
18
+
19
+ consola.start('Installing @stonecrop/graphql-client...')
20
+
21
+ try {
22
+ // Add dependencies - use latest published version
23
+ await addDependencies(cwd, {
24
+ '@stonecrop/graphql-client': 'latest',
25
+ graphql: '^16.11.0',
26
+ 'graphql-request': '~6.0.0',
27
+ })
28
+
29
+ consola.success('@stonecrop/graphql-client installed successfully')
30
+ consola.info('Usage: import { createClient } from "@stonecrop/graphql-client"')
31
+ return true
32
+ } catch (error) {
33
+ consola.error('Failed to install @stonecrop/graphql-client:', error)
34
+ return false
35
+ }
36
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Feature installers barrel export
3
+ */
4
+
5
+ export { installFrontend, type FrontendInstallerOptions } from './frontend'
6
+ export { installGrafserv, type GrafservInstallerOptions } from './grafserv'
7
+ export { installGraphqlClient, type GraphqlClientInstallerOptions } from './graphql-client'
8
+ export { installCasl, type CaslInstallerOptions } from './casl'
9
+ export { installRockfoil, type RockfoilInstallerOptions } from './rockfoil'
10
+ export { installDoctypes, type DoctypesInstallerOptions } from './doctypes'
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Rockfoil installer
3
+ * Installs @stonecrop/rockfoil PostGraphile middleware
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 RockfoilInstallerOptions {
13
+ cwd: string
14
+ }
15
+
16
+ /**
17
+ * Install the @stonecrop/rockfoil PostGraphile middleware
18
+ */
19
+ export async function installRockfoil(options: RockfoilInstallerOptions): Promise<boolean> {
20
+ const { cwd } = options
21
+
22
+ consola.start('Installing @stonecrop/rockfoil PostGraphile middleware...')
23
+
24
+ try {
25
+ // Add dependencies - use latest published version
26
+ await addDependencies(cwd, {
27
+ '@stonecrop/rockfoil': 'latest',
28
+ postgraphile: '^5.0.0-rc.4',
29
+ })
30
+
31
+ // Add import to nuxt.config.ts
32
+ await updateNuxtConfig(cwd, {
33
+ import: "import { createPglRockfoilPlugin } from '@stonecrop/rockfoil'",
34
+ })
35
+
36
+ // Add plugin to grafserv preset configuration
37
+ const pluginAdded = await addPluginToGrafservConfig(cwd, 'createPglRockfoilPlugin({})')
38
+
39
+ if (pluginAdded) {
40
+ consola.success('@stonecrop/rockfoil installed and configured successfully')
41
+ } else {
42
+ consola.success('@stonecrop/rockfoil installed successfully')
43
+ consola.info('Add createPglRockfoilPlugin({}) to your grafserv preset plugins array manually')
44
+ }
45
+
46
+ return true
47
+ } catch (error) {
48
+ consola.error('Failed to install @stonecrop/rockfoil:', error)
49
+ return false
50
+ }
51
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Interactive prompts for feature selection
3
+ */
4
+
5
+ import prompts from 'prompts'
6
+ import consola from 'consola'
7
+ import type { DetectedFeatures } from './detect'
8
+
9
+ export interface SelectedFeatures {
10
+ frontend: boolean
11
+ graphql: boolean
12
+ graphqlClient: boolean
13
+ casl: boolean
14
+ rockfoil: boolean
15
+ doctypes: boolean
16
+ }
17
+
18
+ export interface PromptOptions {
19
+ /** Pre-selected features from CLI flags */
20
+ preselected?: Partial<SelectedFeatures>
21
+ /** Already detected features in the project */
22
+ detected?: DetectedFeatures
23
+ /** Skip confirmation prompts */
24
+ skipConfirm?: boolean
25
+ }
26
+
27
+ /**
28
+ * Prompt the user to select which features to install
29
+ */
30
+ export async function promptFeatures(options: PromptOptions = {}): Promise<SelectedFeatures | null> {
31
+ const { preselected = {}, detected, skipConfirm } = options
32
+
33
+ // If any features are pre-selected via flags, use those
34
+ const hasPreselected = Object.values(preselected).some(Boolean)
35
+ if (hasPreselected) {
36
+ return {
37
+ frontend: preselected.frontend ?? false,
38
+ graphql: preselected.graphql ?? false,
39
+ graphqlClient: preselected.graphqlClient ?? false,
40
+ casl: preselected.casl ?? false,
41
+ rockfoil: preselected.rockfoil ?? false,
42
+ doctypes: preselected.doctypes ?? false,
43
+ }
44
+ }
45
+
46
+ // Build choices based on what's already installed
47
+ const choices = [
48
+ {
49
+ title: '@stonecrop/nuxt - Frontend module',
50
+ value: 'frontend',
51
+ description: 'Schema-driven UI components and routing',
52
+ disabled: detected?.hasFrontend,
53
+ selected: !detected?.hasFrontend,
54
+ },
55
+ {
56
+ title: '@stonecrop/graphql-client - GraphQL client',
57
+ value: 'graphqlClient',
58
+ description: 'GraphQL client with Stonecrop integration',
59
+ disabled: detected?.hasGraphqlClient,
60
+ selected: false,
61
+ },
62
+ {
63
+ title: '@stonecrop/nuxt-grafserv - GraphQL server',
64
+ value: 'graphql',
65
+ description: 'Grafserv-based GraphQL API with middleware support',
66
+ disabled: detected?.hasGraphql,
67
+ selected: false,
68
+ },
69
+ {
70
+ title: '@stonecrop/casl-middleware - Authorization',
71
+ value: 'casl',
72
+ description: 'CASL-based permission system for GraphQL',
73
+ disabled: detected?.hasCasl,
74
+ selected: false,
75
+ },
76
+ {
77
+ title: '@stonecrop/rockfoil - PostGraphile middleware',
78
+ value: 'rockfoil',
79
+ description: 'PostGraphile integration for database-driven GraphQL',
80
+ disabled: detected?.hasRockfoil,
81
+ selected: false,
82
+ },
83
+ {
84
+ title: 'Sample doctypes',
85
+ value: 'doctypes',
86
+ description: 'Example doctype JSON files to get started',
87
+ disabled: detected?.hasDoctypes,
88
+ selected: false,
89
+ },
90
+ ]
91
+
92
+ // Filter out disabled choices for the hint
93
+ const availableCount = choices.filter(c => !c.disabled).length
94
+ if (availableCount === 0) {
95
+ consola.info('All Stonecrop features are already installed!')
96
+ return null
97
+ }
98
+
99
+ // Show what's already installed
100
+ if (detected) {
101
+ const installed = choices.filter(c => c.disabled)
102
+ if (installed.length > 0) {
103
+ consola.info('Already installed:')
104
+ for (const item of installed) {
105
+ consola.info(` ✓ ${item.title}`)
106
+ }
107
+ console.log()
108
+ }
109
+ }
110
+
111
+ const response = await prompts(
112
+ {
113
+ type: 'multiselect',
114
+ name: 'features',
115
+ message: 'Select features to install',
116
+ choices: choices.filter(c => !c.disabled),
117
+ hint: '- Space to select. Return to submit',
118
+ instructions: false,
119
+ },
120
+ {
121
+ onCancel: () => {
122
+ consola.info('Installation cancelled')
123
+ process.exit(0)
124
+ },
125
+ }
126
+ )
127
+
128
+ if (!response.features || response.features.length === 0) {
129
+ consola.info('No features selected')
130
+ return null
131
+ }
132
+
133
+ const selected: SelectedFeatures = {
134
+ frontend: response.features.includes('frontend'),
135
+ graphql: response.features.includes('graphql'),
136
+ graphqlClient: response.features.includes('graphqlClient'),
137
+ casl: response.features.includes('casl'),
138
+ rockfoil: response.features.includes('rockfoil'),
139
+ doctypes: response.features.includes('doctypes'),
140
+ }
141
+
142
+ // Confirm selection
143
+ if (!skipConfirm) {
144
+ console.log()
145
+ consola.info('Selected features:')
146
+ if (selected.frontend) consola.info(' • @stonecrop/nuxt')
147
+ if (selected.graphqlClient) consola.info(' • @stonecrop/graphql-client')
148
+ if (selected.graphql) consola.info(' • @stonecrop/nuxt-grafserv')
149
+ if (selected.casl) consola.info(' • @stonecrop/casl-middleware')
150
+ if (selected.rockfoil) consola.info(' • @stonecrop/rockfoil')
151
+ if (selected.doctypes) consola.info(' • Sample doctypes')
152
+ console.log()
153
+
154
+ const confirm = await prompts({
155
+ type: 'confirm',
156
+ name: 'proceed',
157
+ message: 'Proceed with installation?',
158
+ initial: true,
159
+ })
160
+
161
+ if (!confirm.proceed) {
162
+ consola.info('Installation cancelled')
163
+ return null
164
+ }
165
+ }
166
+
167
+ return selected
168
+ }
169
+
170
+ /**
171
+ * Prompt for GraphQL server configuration
172
+ */
173
+ export async function promptGraphqlConfig(): Promise<{
174
+ schemaPath: string
175
+ resolversPath: string
176
+ endpoint: string
177
+ } | null> {
178
+ const response = await prompts([
179
+ {
180
+ type: 'text',
181
+ name: 'schemaPath',
182
+ message: 'GraphQL schema path',
183
+ initial: './server/schema.graphql',
184
+ },
185
+ {
186
+ type: 'text',
187
+ name: 'resolversPath',
188
+ message: 'Resolvers file path',
189
+ initial: './server/resolvers.ts',
190
+ },
191
+ {
192
+ type: 'text',
193
+ name: 'endpoint',
194
+ message: 'GraphQL endpoint URL',
195
+ initial: '/graphql/',
196
+ },
197
+ ])
198
+
199
+ if (!response.schemaPath) {
200
+ return null
201
+ }
202
+
203
+ return response
204
+ }