@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.
- package/README.md +105 -1
- package/bin/init.mjs +104 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +84 -5
- package/package.json +25 -10
- package/src/cli/detect.ts +125 -0
- package/src/cli/index.ts +184 -0
- package/src/cli/installers/casl.ts +51 -0
- package/src/cli/installers/doctypes.ts +206 -0
- package/src/cli/installers/frontend.ts +68 -0
- package/src/cli/installers/grafserv.ts +308 -0
- package/src/cli/installers/graphql-client.ts +36 -0
- package/src/cli/installers/index.ts +10 -0
- package/src/cli/installers/rockfoil.ts +51 -0
- package/src/cli/prompts.ts +204 -0
- package/src/cli/utils/config.ts +260 -0
- package/src/cli/utils/index.ts +15 -0
- package/src/cli/utils/package.ts +128 -0
- package/src/cli/utils/plugin.ts +107 -0
- package/templates/Example.json +85 -0
- package/templates/example-table.json +60 -0
- package/templates/plugins.ts +81 -0
- package/templates/resolvers.ts +256 -0
- package/templates/schema.graphql +129 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for manipulating nuxt.config.ts
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
6
|
+
import { existsSync } from 'node:fs'
|
|
7
|
+
import { join } from 'pathe'
|
|
8
|
+
import consola from 'consola'
|
|
9
|
+
|
|
10
|
+
export interface NuxtConfigUpdate {
|
|
11
|
+
/** Module to add to the modules array */
|
|
12
|
+
module?: string
|
|
13
|
+
/** Module options to add (key in defineNuxtConfig) */
|
|
14
|
+
moduleOptions?: {
|
|
15
|
+
key: string
|
|
16
|
+
value: string
|
|
17
|
+
}
|
|
18
|
+
/** Import to add at the top of the file */
|
|
19
|
+
import?: string
|
|
20
|
+
/** Nitro configuration to add */
|
|
21
|
+
nitroConfig?: {
|
|
22
|
+
externalsInline?: string[]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a module is already configured in nuxt.config.ts
|
|
28
|
+
*/
|
|
29
|
+
export async function hasModule(cwd: string, moduleName: string): Promise<boolean> {
|
|
30
|
+
const configPath = findNuxtConfig(cwd)
|
|
31
|
+
if (!configPath) return false
|
|
32
|
+
|
|
33
|
+
const content = await readFile(configPath, 'utf-8')
|
|
34
|
+
|
|
35
|
+
// Check for module in modules array
|
|
36
|
+
const modulePatterns = [
|
|
37
|
+
new RegExp(`['"\`]${escapeRegex(moduleName)}['"\`]`),
|
|
38
|
+
new RegExp(`from\\s+['"\`]${escapeRegex(moduleName)}['"\`]`),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
return modulePatterns.some(pattern => pattern.test(content))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Find the nuxt.config file in the project
|
|
46
|
+
*/
|
|
47
|
+
export function findNuxtConfig(cwd: string): string | null {
|
|
48
|
+
const candidates = ['nuxt.config.ts', 'nuxt.config.js', 'nuxt.config.mjs']
|
|
49
|
+
|
|
50
|
+
for (const candidate of candidates) {
|
|
51
|
+
const fullPath = join(cwd, candidate)
|
|
52
|
+
if (existsSync(fullPath)) {
|
|
53
|
+
return fullPath
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Update nuxt.config.ts with new module configuration
|
|
62
|
+
*
|
|
63
|
+
* This uses string manipulation rather than AST parsing for simplicity
|
|
64
|
+
* and to preserve formatting/comments as much as possible.
|
|
65
|
+
*/
|
|
66
|
+
export async function updateNuxtConfig(cwd: string, updates: NuxtConfigUpdate): Promise<boolean> {
|
|
67
|
+
const configPath = findNuxtConfig(cwd)
|
|
68
|
+
if (!configPath) {
|
|
69
|
+
consola.error('Could not find nuxt.config.ts in', cwd)
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let content = await readFile(configPath, 'utf-8')
|
|
74
|
+
let modified = false
|
|
75
|
+
|
|
76
|
+
// Add import if specified
|
|
77
|
+
if (updates.import) {
|
|
78
|
+
if (!content.includes(updates.import)) {
|
|
79
|
+
// Find the first import or the start of the file
|
|
80
|
+
const importMatch = content.match(/^import\s+/m)
|
|
81
|
+
if (importMatch && importMatch.index !== undefined) {
|
|
82
|
+
// Add after existing imports
|
|
83
|
+
const lastImportMatch = content.match(/^import\s.*$/gm)
|
|
84
|
+
if (lastImportMatch && lastImportMatch.length > 0) {
|
|
85
|
+
const lastImport = lastImportMatch[lastImportMatch.length - 1]
|
|
86
|
+
if (lastImport) {
|
|
87
|
+
const lastImportIndex = content.lastIndexOf(lastImport)
|
|
88
|
+
const insertIndex = lastImportIndex + lastImport.length
|
|
89
|
+
content = content.slice(0, insertIndex) + '\n' + updates.import + content.slice(insertIndex)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
// Add at the start of the file
|
|
94
|
+
content = updates.import + '\n\n' + content
|
|
95
|
+
}
|
|
96
|
+
modified = true
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Add module to modules array
|
|
101
|
+
if (updates.module) {
|
|
102
|
+
const moduleEntry = updates.module
|
|
103
|
+
|
|
104
|
+
// Find the modules array
|
|
105
|
+
const modulesMatch = content.match(/modules\s*:\s*\[/)
|
|
106
|
+
if (modulesMatch && modulesMatch.index !== undefined) {
|
|
107
|
+
// Find the closing bracket
|
|
108
|
+
const startIndex = modulesMatch.index + modulesMatch[0].length
|
|
109
|
+
const closingIndex = findMatchingBracket(content, startIndex - 1)
|
|
110
|
+
|
|
111
|
+
if (closingIndex !== -1) {
|
|
112
|
+
// Check if module is already present in the modules array (not just anywhere in the file)
|
|
113
|
+
const arrayContent = content.slice(startIndex, closingIndex)
|
|
114
|
+
const moduleAlreadyExists = arrayContent.includes(moduleEntry)
|
|
115
|
+
|
|
116
|
+
if (!moduleAlreadyExists) {
|
|
117
|
+
// Check if array is empty
|
|
118
|
+
const separator = arrayContent.trim().length > 0 ? ', ' : ''
|
|
119
|
+
|
|
120
|
+
content = content.slice(0, closingIndex) + separator + moduleEntry + content.slice(closingIndex)
|
|
121
|
+
modified = true
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
// modules array doesn't exist, we need to add it
|
|
126
|
+
const defineNuxtConfigMatch = content.match(/defineNuxtConfig\s*\(\s*\{/)
|
|
127
|
+
if (defineNuxtConfigMatch && defineNuxtConfigMatch.index !== undefined) {
|
|
128
|
+
const insertIndex = defineNuxtConfigMatch.index + defineNuxtConfigMatch[0].length
|
|
129
|
+
content = content.slice(0, insertIndex) + `\n\tmodules: [${moduleEntry}],` + content.slice(insertIndex)
|
|
130
|
+
modified = true
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Add module options
|
|
136
|
+
if (updates.moduleOptions) {
|
|
137
|
+
const { key, value } = updates.moduleOptions
|
|
138
|
+
|
|
139
|
+
// Check if the key already exists
|
|
140
|
+
const keyPattern = new RegExp(`${escapeRegex(key)}\\s*:`)
|
|
141
|
+
if (!keyPattern.test(content)) {
|
|
142
|
+
// Find defineNuxtConfig and add the option
|
|
143
|
+
const defineNuxtConfigMatch = content.match(/defineNuxtConfig\s*\(\s*\{/)
|
|
144
|
+
if (defineNuxtConfigMatch && defineNuxtConfigMatch.index !== undefined) {
|
|
145
|
+
// Find a good place to insert (after modules if it exists, otherwise at the start)
|
|
146
|
+
const modulesEndMatch = content.match(/modules\s*:\s*\[[^\]]*\]\s*,?/)
|
|
147
|
+
if (modulesEndMatch && modulesEndMatch.index !== undefined) {
|
|
148
|
+
const insertIndex = modulesEndMatch.index + modulesEndMatch[0].length
|
|
149
|
+
content = content.slice(0, insertIndex) + `\n\n\t${key}: ${value},` + content.slice(insertIndex)
|
|
150
|
+
} else {
|
|
151
|
+
const insertIndex = defineNuxtConfigMatch.index + defineNuxtConfigMatch[0].length
|
|
152
|
+
content = content.slice(0, insertIndex) + `\n\t${key}: ${value},` + content.slice(insertIndex)
|
|
153
|
+
}
|
|
154
|
+
modified = true
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Add Nitro configuration
|
|
160
|
+
if (updates.nitroConfig) {
|
|
161
|
+
// Check if nitro config already exists
|
|
162
|
+
const nitroMatch = content.match(/nitro\s*:\s*\{/)
|
|
163
|
+
|
|
164
|
+
if (updates.nitroConfig.externalsInline) {
|
|
165
|
+
const packagesStr = updates.nitroConfig.externalsInline.map(pkg => `'${pkg}'`).join(', ')
|
|
166
|
+
|
|
167
|
+
if (nitroMatch && nitroMatch.index !== undefined) {
|
|
168
|
+
// Nitro config exists, check if externals.inline exists
|
|
169
|
+
const externalsMatch = content.match(/externals\s*:\s*\{/)
|
|
170
|
+
|
|
171
|
+
if (externalsMatch && externalsMatch.index !== undefined && externalsMatch.index > nitroMatch.index) {
|
|
172
|
+
// Check if inline array exists
|
|
173
|
+
const inlineMatch = content.match(/inline\s*:\s*\[/)
|
|
174
|
+
|
|
175
|
+
if (inlineMatch && inlineMatch.index !== undefined && inlineMatch.index > externalsMatch.index) {
|
|
176
|
+
// inline array exists, add packages if not present
|
|
177
|
+
const startIndex = inlineMatch.index + inlineMatch[0].length
|
|
178
|
+
const closingIndex = findMatchingBracket(content, startIndex - 1)
|
|
179
|
+
|
|
180
|
+
if (closingIndex !== -1) {
|
|
181
|
+
const arrayContent = content.slice(startIndex, closingIndex).trim()
|
|
182
|
+
const separator = arrayContent.length > 0 ? ', ' : ''
|
|
183
|
+
content = content.slice(0, closingIndex) + separator + packagesStr + content.slice(closingIndex)
|
|
184
|
+
modified = true
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
// externals exists but no inline, add it
|
|
188
|
+
const startIndex = externalsMatch.index + externalsMatch[0].length
|
|
189
|
+
content = content.slice(0, startIndex) + `\n\t\t\tinline: [${packagesStr}],` + content.slice(startIndex)
|
|
190
|
+
modified = true
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
// nitro exists but no externals, add it
|
|
194
|
+
const startIndex = nitroMatch.index + nitroMatch[0].length
|
|
195
|
+
content =
|
|
196
|
+
content.slice(0, startIndex) +
|
|
197
|
+
`\n\t\texternals: {\n\t\t\tinline: [${packagesStr}],\n\t\t},` +
|
|
198
|
+
content.slice(startIndex)
|
|
199
|
+
modified = true
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
// No nitro config, add everything - insert after modules array
|
|
203
|
+
const defineNuxtConfigMatch = content.match(/defineNuxtConfig\s*\(\s*\{/)
|
|
204
|
+
if (defineNuxtConfigMatch && defineNuxtConfigMatch.index !== undefined) {
|
|
205
|
+
// Try to find modules array to insert after it
|
|
206
|
+
const modulesEndMatch = content.match(/modules\s*:\s*\[[^\]]*\]\s*,?/)
|
|
207
|
+
if (modulesEndMatch && modulesEndMatch.index !== undefined) {
|
|
208
|
+
const insertIndex = modulesEndMatch.index + modulesEndMatch[0].length
|
|
209
|
+
content =
|
|
210
|
+
content.slice(0, insertIndex) +
|
|
211
|
+
`\n\n\t// Nitro configuration for Stonecrop CSS handling\n\tnitro: {\n\t\texternals: {\n\t\t\tinline: [${packagesStr}],\n\t\t},\n\t},` +
|
|
212
|
+
content.slice(insertIndex)
|
|
213
|
+
} else {
|
|
214
|
+
// No modules, add at the start of config
|
|
215
|
+
const insertIndex = defineNuxtConfigMatch.index + defineNuxtConfigMatch[0].length
|
|
216
|
+
content =
|
|
217
|
+
content.slice(0, insertIndex) +
|
|
218
|
+
`\n\t// Nitro configuration for Stonecrop CSS handling\n\tnitro: {\n\t\texternals: {\n\t\t\tinline: [${packagesStr}],\n\t\t},\n\t},` +
|
|
219
|
+
content.slice(insertIndex)
|
|
220
|
+
}
|
|
221
|
+
modified = true
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (modified) {
|
|
228
|
+
await writeFile(configPath, content, 'utf-8')
|
|
229
|
+
consola.success(`Updated ${configPath}`)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return modified
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Find the matching closing bracket
|
|
237
|
+
*/
|
|
238
|
+
function findMatchingBracket(content: string, openIndex: number): number {
|
|
239
|
+
const openChar = content[openIndex]
|
|
240
|
+
const closeChar = openChar === '[' ? ']' : openChar === '{' ? '}' : ')'
|
|
241
|
+
|
|
242
|
+
let depth = 1
|
|
243
|
+
let i = openIndex + 1
|
|
244
|
+
|
|
245
|
+
while (i < content.length && depth > 0) {
|
|
246
|
+
const char = content[i]
|
|
247
|
+
if (char === openChar) depth++
|
|
248
|
+
else if (char === closeChar) depth--
|
|
249
|
+
i++
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return depth === 0 ? i - 1 : -1
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Escape special regex characters
|
|
257
|
+
*/
|
|
258
|
+
function escapeRegex(str: string): string {
|
|
259
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
260
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI utilities barrel export
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { hasModule, findNuxtConfig, updateNuxtConfig, type NuxtConfigUpdate } from './config'
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
readPackageJson,
|
|
9
|
+
writePackageJson,
|
|
10
|
+
hasPackage,
|
|
11
|
+
addDependencies,
|
|
12
|
+
detectPackageManager,
|
|
13
|
+
getInstallCommand,
|
|
14
|
+
type PackageJson,
|
|
15
|
+
} from './package'
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for manipulating package.json
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
6
|
+
import { existsSync } from 'node:fs'
|
|
7
|
+
import { join } from 'pathe'
|
|
8
|
+
import consola from 'consola'
|
|
9
|
+
|
|
10
|
+
export interface PackageJson {
|
|
11
|
+
name?: string
|
|
12
|
+
version?: string
|
|
13
|
+
dependencies?: Record<string, string>
|
|
14
|
+
devDependencies?: Record<string, string>
|
|
15
|
+
[key: string]: unknown
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Read package.json from the project
|
|
20
|
+
*/
|
|
21
|
+
export async function readPackageJson(cwd: string): Promise<PackageJson | null> {
|
|
22
|
+
const packagePath = join(cwd, 'package.json')
|
|
23
|
+
|
|
24
|
+
if (!existsSync(packagePath)) {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const content = await readFile(packagePath, 'utf-8')
|
|
29
|
+
return JSON.parse(content)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Write package.json to the project
|
|
34
|
+
*/
|
|
35
|
+
export async function writePackageJson(cwd: string, pkg: PackageJson): Promise<void> {
|
|
36
|
+
const packagePath = join(cwd, 'package.json')
|
|
37
|
+
await writeFile(packagePath, JSON.stringify(pkg, null, '\t') + '\n', 'utf-8')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if a package is installed (in dependencies or devDependencies)
|
|
42
|
+
*/
|
|
43
|
+
export async function hasPackage(cwd: string, packageName: string): Promise<boolean> {
|
|
44
|
+
const pkg = await readPackageJson(cwd)
|
|
45
|
+
if (!pkg) return false
|
|
46
|
+
|
|
47
|
+
return !!(pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Add dependencies to package.json
|
|
52
|
+
*/
|
|
53
|
+
export async function addDependencies(
|
|
54
|
+
cwd: string,
|
|
55
|
+
dependencies: Record<string, string>,
|
|
56
|
+
options: { dev?: boolean } = {}
|
|
57
|
+
): Promise<boolean> {
|
|
58
|
+
const pkg = await readPackageJson(cwd)
|
|
59
|
+
if (!pkg) {
|
|
60
|
+
consola.error('Could not find package.json in', cwd)
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const key = options.dev ? 'devDependencies' : 'dependencies'
|
|
65
|
+
pkg[key] = pkg[key] || {}
|
|
66
|
+
|
|
67
|
+
let modified = false
|
|
68
|
+
for (const [name, version] of Object.entries(dependencies)) {
|
|
69
|
+
if (!pkg[key]![name]) {
|
|
70
|
+
pkg[key]![name] = version
|
|
71
|
+
modified = true
|
|
72
|
+
consola.info(`Adding ${name}@${version} to ${key}`)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (modified) {
|
|
77
|
+
// Sort dependencies alphabetically
|
|
78
|
+
pkg[key] = sortObject(pkg[key] as Record<string, string>)
|
|
79
|
+
await writePackageJson(cwd, pkg)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return modified
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the package manager used in the project
|
|
87
|
+
*/
|
|
88
|
+
export function detectPackageManager(cwd: string): 'npm' | 'yarn' | 'pnpm' | 'bun' {
|
|
89
|
+
if (existsSync(join(cwd, 'bun.lockb')) || existsSync(join(cwd, 'bun.lock'))) {
|
|
90
|
+
return 'bun'
|
|
91
|
+
}
|
|
92
|
+
if (existsSync(join(cwd, 'pnpm-lock.yaml'))) {
|
|
93
|
+
return 'pnpm'
|
|
94
|
+
}
|
|
95
|
+
if (existsSync(join(cwd, 'yarn.lock'))) {
|
|
96
|
+
return 'yarn'
|
|
97
|
+
}
|
|
98
|
+
return 'npm'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get the install command for the detected package manager
|
|
103
|
+
*/
|
|
104
|
+
export function getInstallCommand(cwd: string): string {
|
|
105
|
+
const pm = detectPackageManager(cwd)
|
|
106
|
+
switch (pm) {
|
|
107
|
+
case 'bun':
|
|
108
|
+
return 'bun install'
|
|
109
|
+
case 'pnpm':
|
|
110
|
+
return 'pnpm install'
|
|
111
|
+
case 'yarn':
|
|
112
|
+
return 'yarn'
|
|
113
|
+
default:
|
|
114
|
+
return 'npm install'
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Sort an object's keys alphabetically
|
|
120
|
+
*/
|
|
121
|
+
function sortObject(obj: Record<string, string>): Record<string, string> {
|
|
122
|
+
return Object.keys(obj)
|
|
123
|
+
.sort()
|
|
124
|
+
.reduce((acc, key) => {
|
|
125
|
+
acc[key] = obj[key]
|
|
126
|
+
return acc
|
|
127
|
+
}, {} as Record<string, string>)
|
|
128
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for managing Grafserv plugin configuration in nuxt.config.ts
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
6
|
+
import consola from 'consola'
|
|
7
|
+
|
|
8
|
+
import { findNuxtConfig } from './config'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Add a plugin to the grafserv preset plugins array in nuxt.config.ts
|
|
12
|
+
*
|
|
13
|
+
* @param cwd - Current working directory
|
|
14
|
+
* @param pluginCode - The plugin code to add (e.g., "pglCaslPlugin" or "createPglRockfoilPlugin({})")
|
|
15
|
+
* @returns true if successfully added, false otherwise
|
|
16
|
+
*/
|
|
17
|
+
export async function addPluginToGrafservConfig(cwd: string, pluginCode: string): Promise<boolean> {
|
|
18
|
+
const configPath = findNuxtConfig(cwd)
|
|
19
|
+
if (!configPath) {
|
|
20
|
+
consola.error('Could not find nuxt.config.ts')
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let content = await readFile(configPath, 'utf-8')
|
|
25
|
+
|
|
26
|
+
// Check if plugin is already configured
|
|
27
|
+
if (content.includes(pluginCode)) {
|
|
28
|
+
consola.info('Plugin already configured in nuxt.config.ts')
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Find grafserv configuration
|
|
33
|
+
const grafservMatch = content.match(/grafserv\s*:\s*\{/)
|
|
34
|
+
if (!grafservMatch || grafservMatch.index === undefined) {
|
|
35
|
+
consola.warn('Could not find grafserv configuration in nuxt.config.ts')
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check if preset exists within grafserv config
|
|
40
|
+
const grafservStart = grafservMatch.index
|
|
41
|
+
const presetMatch = content.slice(grafservStart).match(/preset\s*:\s*\{/)
|
|
42
|
+
|
|
43
|
+
if (presetMatch && presetMatch.index !== undefined) {
|
|
44
|
+
const presetStart = grafservStart + presetMatch.index
|
|
45
|
+
// Check if plugins array exists within preset
|
|
46
|
+
const pluginsMatch = content.slice(presetStart).match(/plugins\s*:\s*\[/)
|
|
47
|
+
|
|
48
|
+
if (pluginsMatch && pluginsMatch.index !== undefined) {
|
|
49
|
+
// Plugins array exists, add to it
|
|
50
|
+
const pluginsStart = presetStart + pluginsMatch.index + pluginsMatch[0].length
|
|
51
|
+
const closingBracket = findMatchingBracket(content, pluginsStart - 1)
|
|
52
|
+
|
|
53
|
+
if (closingBracket !== -1) {
|
|
54
|
+
const arrayContent = content.slice(pluginsStart, closingBracket).trim()
|
|
55
|
+
const separator = arrayContent.length > 0 ? ', ' : ''
|
|
56
|
+
|
|
57
|
+
content = content.slice(0, closingBracket) + separator + pluginCode + content.slice(closingBracket)
|
|
58
|
+
|
|
59
|
+
await writeFile(configPath, content, 'utf-8')
|
|
60
|
+
consola.success('Added plugin to grafserv.preset.plugins array')
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
// preset exists but no plugins array, add it
|
|
65
|
+
const presetContentStart = presetStart + presetMatch[0].length
|
|
66
|
+
content =
|
|
67
|
+
content.slice(0, presetContentStart) + `\n\t\t\tplugins: [${pluginCode}],` + content.slice(presetContentStart)
|
|
68
|
+
|
|
69
|
+
await writeFile(configPath, content, 'utf-8')
|
|
70
|
+
consola.success('Added plugins array to grafserv.preset configuration')
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
// grafserv exists but no preset, add preset with plugins
|
|
75
|
+
const grafservContentStart = grafservStart + grafservMatch[0].length
|
|
76
|
+
content =
|
|
77
|
+
content.slice(0, grafservContentStart) +
|
|
78
|
+
`\n\t\tpreset: {\n\t\t\tplugins: [${pluginCode}],\n\t\t},` +
|
|
79
|
+
content.slice(grafservContentStart)
|
|
80
|
+
|
|
81
|
+
await writeFile(configPath, content, 'utf-8')
|
|
82
|
+
consola.success('Added preset.plugins configuration to grafserv')
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Find the matching closing bracket
|
|
91
|
+
*/
|
|
92
|
+
function findMatchingBracket(content: string, openIndex: number): number {
|
|
93
|
+
const openChar = content[openIndex]
|
|
94
|
+
const closeChar = openChar === '[' ? ']' : openChar === '{' ? '}' : ')'
|
|
95
|
+
|
|
96
|
+
let depth = 1
|
|
97
|
+
let i = openIndex + 1
|
|
98
|
+
|
|
99
|
+
while (i < content.length && depth > 0) {
|
|
100
|
+
const char = content[i]
|
|
101
|
+
if (char === openChar) depth++
|
|
102
|
+
else if (char === closeChar) depth--
|
|
103
|
+
i++
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return depth === 0 ? i - 1 : -1
|
|
107
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Example",
|
|
3
|
+
"slug": "example/:id",
|
|
4
|
+
"tableName": "examples",
|
|
5
|
+
"fields": [
|
|
6
|
+
{
|
|
7
|
+
"fieldname": "id",
|
|
8
|
+
"fieldtype": "Data",
|
|
9
|
+
"label": "ID",
|
|
10
|
+
"readOnly": true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"fieldname": "title",
|
|
14
|
+
"fieldtype": "Data",
|
|
15
|
+
"label": "Title",
|
|
16
|
+
"required": true,
|
|
17
|
+
"width": "30ch"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"fieldname": "description",
|
|
21
|
+
"fieldtype": "Text",
|
|
22
|
+
"label": "Description"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"fieldname": "status",
|
|
26
|
+
"fieldtype": "Select",
|
|
27
|
+
"label": "Status",
|
|
28
|
+
"options": ["Draft", "Active", "Archived"],
|
|
29
|
+
"default": "Draft"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"fieldname": "priority",
|
|
33
|
+
"fieldtype": "Select",
|
|
34
|
+
"label": "Priority",
|
|
35
|
+
"options": ["Low", "Medium", "High"],
|
|
36
|
+
"default": "Medium"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"fieldname": "assignee",
|
|
40
|
+
"fieldtype": "Link",
|
|
41
|
+
"label": "Assignee",
|
|
42
|
+
"options": "User"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"fieldname": "dueDate",
|
|
46
|
+
"fieldtype": "Date",
|
|
47
|
+
"label": "Due Date"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"fieldname": "createdAt",
|
|
51
|
+
"fieldtype": "Datetime",
|
|
52
|
+
"label": "Created At",
|
|
53
|
+
"readOnly": true
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"fieldname": "updatedAt",
|
|
57
|
+
"fieldtype": "Datetime",
|
|
58
|
+
"label": "Updated At",
|
|
59
|
+
"readOnly": true
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"workflow": {
|
|
63
|
+
"states": ["Draft", "Active", "Archived"],
|
|
64
|
+
"actions": {
|
|
65
|
+
"activate": {
|
|
66
|
+
"label": "Activate",
|
|
67
|
+
"handler": "activate_example",
|
|
68
|
+
"allowedStates": ["Draft"],
|
|
69
|
+
"confirm": true
|
|
70
|
+
},
|
|
71
|
+
"archive": {
|
|
72
|
+
"label": "Archive",
|
|
73
|
+
"handler": "archive_example",
|
|
74
|
+
"allowedStates": ["Active"],
|
|
75
|
+
"confirm": true
|
|
76
|
+
},
|
|
77
|
+
"reopen": {
|
|
78
|
+
"label": "Reopen",
|
|
79
|
+
"handler": "reopen_example",
|
|
80
|
+
"allowedStates": ["Archived"],
|
|
81
|
+
"confirm": false
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Example",
|
|
3
|
+
"slug": "example",
|
|
4
|
+
"tableName": "examples",
|
|
5
|
+
"listDoctype": "Example",
|
|
6
|
+
"schema": [
|
|
7
|
+
{
|
|
8
|
+
"component": "ATable",
|
|
9
|
+
"columns": [
|
|
10
|
+
{
|
|
11
|
+
"name": "id",
|
|
12
|
+
"label": "ID",
|
|
13
|
+
"fieldtype": "Data",
|
|
14
|
+
"width": "8ch"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "title",
|
|
18
|
+
"label": "Title",
|
|
19
|
+
"fieldtype": "Data",
|
|
20
|
+
"width": "25ch"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "status",
|
|
24
|
+
"label": "Status",
|
|
25
|
+
"fieldtype": "Data",
|
|
26
|
+
"width": "10ch"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "priority",
|
|
30
|
+
"label": "Priority",
|
|
31
|
+
"fieldtype": "Data",
|
|
32
|
+
"width": "10ch"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "assignee",
|
|
36
|
+
"label": "Assignee",
|
|
37
|
+
"fieldtype": "Data",
|
|
38
|
+
"width": "15ch"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "dueDate",
|
|
42
|
+
"label": "Due Date",
|
|
43
|
+
"fieldtype": "Date",
|
|
44
|
+
"width": "12ch"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "createdAt",
|
|
48
|
+
"label": "Created",
|
|
49
|
+
"fieldtype": "Datetime",
|
|
50
|
+
"width": "18ch"
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"config": {
|
|
54
|
+
"view": "list",
|
|
55
|
+
"sortable": true,
|
|
56
|
+
"filterable": true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|