@stonecrop/nuxt 0.7.4 → 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 CHANGED
@@ -28,7 +28,39 @@ Stonecrop is a **schema-driven UI framework** that generates forms, tables, and
28
28
 
29
29
  ## Quick Setup
30
30
 
31
- Install the module to your Nuxt application:
31
+ ### Option 1: Interactive Installer (Recommended)
32
+
33
+ Use the Stonecrop CLI to interactively install features:
34
+
35
+ ```bash
36
+ npx @stonecrop/nuxt init
37
+ ```
38
+
39
+ This will prompt you to select which features to install:
40
+ - **@stonecrop/nuxt** - Frontend module with schema-driven UI
41
+ - **@stonecrop/graphql-client** - GraphQL client with Stonecrop integration
42
+ - **@stonecrop/nuxt-grafserv** - GraphQL server with Grafserv
43
+ - **@stonecrop/casl-middleware** - CASL authorization
44
+ - **@stonecrop/rockfoil** - PostGraphile middleware for database-driven GraphQL
45
+ - **Sample doctypes** - Example doctype files to get started
46
+
47
+ You can also use flags for non-interactive installation:
48
+
49
+ ```bash
50
+ # Install everything
51
+ npx @stonecrop/nuxt init --frontend --graphql-client --graphql --casl --rockfoil --doctypes --yes
52
+
53
+ # Install just the frontend module
54
+ npx @stonecrop/nuxt init --frontend
55
+
56
+ # Add GraphQL server to existing setup
57
+ npx @stonecrop/nuxt init --graphql
58
+
59
+ # Add PostGraphile middleware
60
+ npx @stonecrop/nuxt init --rockfoil
61
+ ```
62
+
63
+ ### Option 2: Manual Installation
32
64
 
33
65
  ```bash
34
66
  npx nuxi module add @stonecrop/nuxt
@@ -297,6 +329,78 @@ npm run test
297
329
  npm run test:watch
298
330
  ```
299
331
 
332
+ ### Testing the CLI Locally
333
+
334
+ To test the `npx @stonecrop/nuxt init` command from another directory outside this project:
335
+
336
+ **1. Build the monorepo (from stonecrop root):**
337
+
338
+ ```bash
339
+ cd /path/to/stonecrop
340
+ rush update
341
+ rush build
342
+ ```
343
+
344
+ **2. Create a test Nuxt project (in a separate directory):**
345
+
346
+ ```bash
347
+ cd /tmp # or any directory outside stonecrop
348
+ npx nuxi init my-test-app
349
+ cd my-test-app
350
+ npm install
351
+ ```
352
+
353
+ **3. Run the CLI using the local package:**
354
+
355
+ ```bash
356
+ # Option A: Run from within the nuxt package directory (simplest)
357
+ # This ensures Node can find the dependencies
358
+ cd /path/to/stonecrop/nuxt
359
+ node bin/init.mjs init --cwd /tmp/my-test-app
360
+
361
+ # Option B: Use pnpm link (from the test project)
362
+ cd /path/to/stonecrop/nuxt
363
+ pnpm link --global
364
+ cd /tmp/my-test-app
365
+ pnpm link --global @stonecrop/nuxt
366
+ npx stonecrop-nuxt init
367
+
368
+ # Option C: Use npm pack to create a tarball (simulates real npm install)
369
+ cd /path/to/stonecrop/nuxt
370
+ npm pack
371
+ cd /tmp/my-test-app
372
+ npm install /path/to/stonecrop/nuxt/stonecrop-nuxt-0.6.3.tgz
373
+ npx stonecrop-nuxt init
374
+ ```
375
+
376
+ > **Note:** Option A uses `--cwd` to specify the target directory while running from within
377
+ > the nuxt package where dependencies are available. Options B and C install the package
378
+ > into the test project so dependencies are resolved correctly.
379
+
380
+ **4. Interactive testing:**
381
+
382
+ The CLI will detect that you're in a Nuxt project and prompt for features:
383
+
384
+ ```
385
+ 🌱 Stonecrop Nuxt Installer
386
+
387
+ ✔ Nuxt project detected
388
+
389
+ ? Select features to install
390
+ ◉ @stonecrop/nuxt - Frontend module
391
+ ◯ @stonecrop/nuxt-grafserv - GraphQL server
392
+ ◯ @stonecrop/casl-middleware - Authorization
393
+ ◉ Sample doctypes
394
+ ```
395
+
396
+ **5. Verify the installation:**
397
+
398
+ After running the installer, check:
399
+ - `package.json` has the new dependencies
400
+ - `nuxt.config.ts` has the module configuration
401
+ - `doctypes/` folder contains sample schemas (if selected)
402
+ - `server/` folder contains GraphQL files (if selected)
403
+
300
404
  <!-- Badges -->
301
405
  [npm-version-src]: https://img.shields.io/npm/v/@stonecrop/nuxt/latest.svg?style=flat&colorA=020420&colorB=00DC82
302
406
  [npm-version-href]: https://npmjs.com/package/@stonecrop/nuxt
package/bin/init.mjs ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @stonecrop/nuxt CLI entry point
4
+ *
5
+ * Usage:
6
+ * npx @stonecrop/nuxt init # Interactive mode
7
+ * npx @stonecrop/nuxt init --help # Show help
8
+ */
9
+
10
+ import { defineCommand, runMain } from 'citty'
11
+ import consola from 'consola'
12
+ import { createJiti } from 'jiti'
13
+ import { fileURLToPath } from 'node:url'
14
+ import { dirname, join } from 'node:path'
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url))
17
+
18
+ // Use jiti to load TypeScript CLI code
19
+ const jiti = createJiti(import.meta.url, {
20
+ interopDefault: true,
21
+ esmResolve: true,
22
+ })
23
+
24
+ const { runInstaller } = await jiti.import(join(__dirname, '..', 'src', 'cli', 'index.ts'))
25
+
26
+ const main = defineCommand({
27
+ meta: {
28
+ name: 'stonecrop-nuxt',
29
+ version: '0.6.3',
30
+ description: 'Install Stonecrop modules into your Nuxt project',
31
+ },
32
+ subCommands: {
33
+ init: defineCommand({
34
+ meta: {
35
+ name: 'init',
36
+ description: 'Initialize Stonecrop in your Nuxt project',
37
+ },
38
+ args: {
39
+ frontend: {
40
+ type: 'boolean',
41
+ description: 'Install @stonecrop/nuxt frontend module',
42
+ default: false,
43
+ },
44
+ 'graphql-client': {
45
+ type: 'boolean',
46
+ description: 'Install @stonecrop/graphql-client',
47
+ default: false,
48
+ },
49
+ graphql: {
50
+ type: 'boolean',
51
+ description: 'Install @stonecrop/nuxt-grafserv GraphQL server',
52
+ default: false,
53
+ },
54
+ casl: {
55
+ type: 'boolean',
56
+ description: 'Install @stonecrop/casl-middleware authorization',
57
+ default: false,
58
+ },
59
+ rockfoil: {
60
+ type: 'boolean',
61
+ description: 'Install @stonecrop/rockfoil PostGraphile middleware',
62
+ default: false,
63
+ },
64
+ doctypes: {
65
+ type: 'boolean',
66
+ description: 'Scaffold sample doctypes',
67
+ default: false,
68
+ },
69
+ cwd: {
70
+ type: 'string',
71
+ description: 'Working directory (defaults to current directory)',
72
+ default: process.cwd(),
73
+ },
74
+ yes: {
75
+ type: 'boolean',
76
+ alias: 'y',
77
+ description: 'Skip confirmation prompts',
78
+ default: false,
79
+ },
80
+ },
81
+ async run({ args }) {
82
+ try {
83
+ await runInstaller({
84
+ cwd: args.cwd,
85
+ features: {
86
+ frontend: args.frontend,
87
+ graphqlClient: args['graphql-client'],
88
+ graphql: args.graphql,
89
+ casl: args.casl,
90
+ rockfoil: args.rockfoil,
91
+ doctypes: args.doctypes,
92
+ },
93
+ skipConfirm: args.yes,
94
+ })
95
+ } catch (error) {
96
+ consola.error('Installation failed:', error)
97
+ process.exit(1)
98
+ }
99
+ },
100
+ }),
101
+ },
102
+ })
103
+
104
+ runMain(main)
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stonecrop/nuxt",
3
3
  "configKey": "stonecrop",
4
- "version": "0.7.4",
4
+ "version": "0.7.5",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -1,9 +1,63 @@
1
- import { existsSync } from 'node:fs';
1
+ import { existsSync, realpathSync } from 'node:fs';
2
2
  import { readdir, readFile } from 'node:fs/promises';
3
- import { extname } from 'node:path';
4
- import { createResolver, defineNuxtModule, useLogger, addLayout, extendPages, addServerHandler, addPlugin } from '@nuxt/kit';
3
+ import { dirname, extname } from 'node:path';
4
+ import { createResolver, defineNuxtModule, useLogger, addVitePlugin, addLayout, extendPages, addServerHandler, addPlugin } from '@nuxt/kit';
5
+
6
+ function createSymlinkedPackagesPlugin(options) {
7
+ const { rootDir, packages, logger } = options;
8
+ return {
9
+ name: "stonecrop-symlinked-packages",
10
+ config() {
11
+ const allowPaths = /* @__PURE__ */ new Set();
12
+ const nuxtModulePath = `${rootDir}/node_modules/@stonecrop/nuxt`;
13
+ try {
14
+ if (existsSync(nuxtModulePath)) {
15
+ const realNuxtModulePath = realpathSync(nuxtModulePath);
16
+ if (realNuxtModulePath !== nuxtModulePath) {
17
+ const monorepoRoot = dirname(realNuxtModulePath);
18
+ allowPaths.add(monorepoRoot);
19
+ logger?.(`@stonecrop/nuxt is symlinked, adding monorepo root: ${monorepoRoot}`);
20
+ }
21
+ }
22
+ } catch (e) {
23
+ logger?.(`Error checking @stonecrop/nuxt symlink: ${e instanceof Error ? e.message : String(e)}`);
24
+ }
25
+ for (const pkg of packages) {
26
+ const pkgPath = `${rootDir}/node_modules/${pkg}`;
27
+ try {
28
+ if (existsSync(pkgPath)) {
29
+ const realPath = realpathSync(pkgPath);
30
+ if (realPath !== pkgPath) {
31
+ allowPaths.add(realPath);
32
+ logger?.(`Adding symlinked package to fs.allow: ${realPath}`);
33
+ }
34
+ }
35
+ } catch {
36
+ }
37
+ }
38
+ if (allowPaths.size > 0) {
39
+ logger?.(`Vite fs.allow updated with ${allowPaths.size} path(s)`);
40
+ return {
41
+ server: {
42
+ fs: {
43
+ allow: [...allowPaths]
44
+ }
45
+ }
46
+ };
47
+ }
48
+ }
49
+ };
50
+ }
5
51
 
6
52
  const { resolve } = createResolver(import.meta.url);
53
+ const STONECROP_PACKAGES = [
54
+ "@stonecrop/aform",
55
+ "@stonecrop/atable",
56
+ "@stonecrop/stonecrop",
57
+ "@stonecrop/node-editor",
58
+ "@stonecrop/utilities",
59
+ "@stonecrop/themes"
60
+ ];
7
61
  const module$1 = defineNuxtModule({
8
62
  meta: {
9
63
  name: "@stonecrop/nuxt",
@@ -16,8 +70,33 @@ const module$1 = defineNuxtModule({
16
70
  doctypesDir: void 0
17
71
  };
18
72
  },
19
- async setup(_options, nuxt) {
73
+ async setup(options, nuxt) {
20
74
  const logger = useLogger("@stonecrop/nuxt", { level: nuxt.options.dev ? 3 : 0 });
75
+ nuxt.options.build.transpile = nuxt.options.build.transpile || [];
76
+ for (const pkg of STONECROP_PACKAGES) {
77
+ if (!nuxt.options.build.transpile.includes(pkg)) {
78
+ nuxt.options.build.transpile.push(pkg);
79
+ }
80
+ }
81
+ logger.log("Added Stonecrop packages to build.transpile for SSR CSS handling");
82
+ nuxt.hook("nitro:config", (config) => {
83
+ config.externals = config.externals || {};
84
+ config.externals.inline = config.externals.inline || [];
85
+ for (const pkg of STONECROP_PACKAGES) {
86
+ if (!config.externals.inline.includes(pkg)) {
87
+ config.externals.inline.push(pkg);
88
+ }
89
+ }
90
+ logger.log("Added Stonecrop packages to Nitro externals.inline for CSS bundling");
91
+ });
92
+ if (nuxt.options.dev) {
93
+ const symlinkedPackagesPlugin = createSymlinkedPackagesPlugin({
94
+ rootDir: nuxt.options.rootDir,
95
+ packages: STONECROP_PACKAGES,
96
+ logger: (msg) => logger.log(msg)
97
+ });
98
+ addVitePlugin(symlinkedPackagesPlugin);
99
+ }
21
100
  const layoutsDir = resolve("runtime/layouts");
22
101
  const homepage = resolve(layoutsDir, "StonecropHome.vue");
23
102
  addLayout(homepage, "home");
@@ -94,7 +173,7 @@ const module$1 = defineNuxtModule({
94
173
  }
95
174
  }
96
175
  }
97
- if (_options.docbuilder) {
176
+ if (options.docbuilder) {
98
177
  logger.log("DocBuilder enabled, adding routes and handlers");
99
178
  const pagesDir = resolve("runtime/pages");
100
179
  const docBuilderIndex = resolve(pagesDir, "DocBuilderIndex.vue");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonecrop/nuxt",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
4
4
  "description": "Nuxt module for Stonecrop",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -13,6 +13,9 @@
13
13
  "url": "https://github.com/agritheory/stonecrop",
14
14
  "directory": "nuxt"
15
15
  },
16
+ "bin": {
17
+ "stonecrop-nuxt": "./bin/init.mjs"
18
+ },
16
19
  "exports": {
17
20
  ".": {
18
21
  "types": "./dist/types.d.mts",
@@ -28,19 +31,27 @@
28
31
  }
29
32
  },
30
33
  "files": [
31
- "dist"
34
+ "dist",
35
+ "bin",
36
+ "templates",
37
+ "src/cli"
32
38
  ],
33
39
  "dependencies": {
34
40
  "@nuxt/kit": "^4.2.2",
35
41
  "pinia": "^3.0.4",
36
- "@stonecrop/aform": "0.7.4",
37
- "@stonecrop/casl-middleware": "0.7.4",
38
- "@stonecrop/atable": "0.7.4",
39
- "@stonecrop/graphql-middleware": "0.7.4",
40
- "@stonecrop/node-editor": "0.7.4",
41
- "@stonecrop/nuxt-grafserv": "0.7.4",
42
- "@stonecrop/schema": "0.7.4",
43
- "@stonecrop/stonecrop": "0.7.4"
42
+ "citty": "^0.1.6",
43
+ "consola": "^3.4.0",
44
+ "jiti": "^2.4.2",
45
+ "pathe": "^2.0.3",
46
+ "prompts": "^2.4.2",
47
+ "@stonecrop/aform": "0.7.5",
48
+ "@stonecrop/atable": "0.7.5",
49
+ "@stonecrop/graphql-middleware": "0.7.5",
50
+ "@stonecrop/casl-middleware": "0.7.5",
51
+ "@stonecrop/node-editor": "0.7.5",
52
+ "@stonecrop/schema": "0.7.5",
53
+ "@stonecrop/nuxt-grafserv": "0.7.5",
54
+ "@stonecrop/stonecrop": "0.7.5"
44
55
  },
45
56
  "devDependencies": {
46
57
  "@eslint/js": "^9.39.2",
@@ -50,6 +61,8 @@
50
61
  "@nuxt/module-builder": "^1.0.2",
51
62
  "@nuxt/schema": "^4.2.2",
52
63
  "@nuxt/test-utils": "^3.23.0",
64
+ "@vitest/coverage-istanbul": "^4.0.17",
65
+ "@types/prompts": "^2.4.9",
53
66
  "browserslist": "latest",
54
67
  "baseline-browser-mapping": "latest",
55
68
  "eslint": "^9.39.2",
@@ -57,6 +70,7 @@
57
70
  "eslint-plugin-vue": "^10.6.2",
58
71
  "globals": "^17.0.0",
59
72
  "nuxt": "^4.2.2",
73
+ "postgraphile": "^5.0.0-rc.4",
60
74
  "typescript": "^5.9.3",
61
75
  "typescript-eslint": "^8.53.0",
62
76
  "vue": "^3.5.26",
@@ -82,6 +96,7 @@
82
96
  "lint": "eslint .",
83
97
  "test": "vitest run",
84
98
  "test:ui": "vitest --ui",
99
+ "test:coverage": "vitest run --coverage",
85
100
  "test:watch": "vitest watch",
86
101
  "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
87
102
  }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Detection utilities for existing Stonecrop setup
3
+ */
4
+
5
+ import { existsSync } from 'node:fs'
6
+ import { join } from 'pathe'
7
+ import { hasModule, findNuxtConfig } from './utils/config'
8
+ import { hasPackage, readPackageJson } from './utils/package'
9
+
10
+ export interface DetectedFeatures {
11
+ /** Is this a valid Nuxt project? */
12
+ isNuxtProject: boolean
13
+ /** Is @stonecrop/nuxt installed? */
14
+ hasFrontend: boolean
15
+ /** Is @stonecrop/nuxt-grafserv installed? */
16
+ hasGraphql: boolean
17
+ /** Is @stonecrop/graphql-client installed? */
18
+ hasGraphqlClient: boolean
19
+ /** Is @stonecrop/casl-middleware installed? */
20
+ hasCasl: boolean
21
+ /** Is @stonecrop/rockfoil installed? */
22
+ hasRockfoil: boolean
23
+ /** Does the doctypes directory exist? */
24
+ hasDoctypes: boolean
25
+ /** Does the server directory exist? */
26
+ hasServerDir: boolean
27
+ /** Does server/schema.graphql exist? */
28
+ hasSchema: boolean
29
+ /** Does server/resolvers.ts exist? */
30
+ hasResolvers: boolean
31
+ }
32
+
33
+ /**
34
+ * Detect what Stonecrop features are already installed in the project
35
+ */
36
+ export async function detectFeatures(cwd: string): Promise<DetectedFeatures> {
37
+ const result: DetectedFeatures = {
38
+ isNuxtProject: false,
39
+ hasFrontend: false,
40
+ hasGraphql: false,
41
+ hasGraphqlClient: false,
42
+ hasCasl: false,
43
+ hasRockfoil: false,
44
+ hasDoctypes: false,
45
+ hasServerDir: false,
46
+ hasSchema: false,
47
+ hasResolvers: false,
48
+ }
49
+
50
+ // Check if this is a Nuxt project
51
+ const nuxtConfig = findNuxtConfig(cwd)
52
+ const pkg = await readPackageJson(cwd)
53
+
54
+ if (!nuxtConfig || !pkg) {
55
+ return result
56
+ }
57
+
58
+ // Check for Nuxt in dependencies
59
+ const hasNuxt = !!(pkg.dependencies?.nuxt || pkg.devDependencies?.nuxt)
60
+ result.isNuxtProject = hasNuxt
61
+
62
+ if (!hasNuxt) {
63
+ return result
64
+ }
65
+
66
+ // Check for installed packages
67
+ result.hasFrontend = await hasPackage(cwd, '@stonecrop/nuxt')
68
+ result.hasGraphql = await hasPackage(cwd, '@stonecrop/nuxt-grafserv')
69
+ result.hasGraphqlClient = await hasPackage(cwd, '@stonecrop/graphql-client')
70
+ result.hasCasl = await hasPackage(cwd, '@stonecrop/casl-middleware')
71
+ result.hasRockfoil = await hasPackage(cwd, '@stonecrop/rockfoil')
72
+
73
+ // Also check nuxt.config for module registration
74
+ if (!result.hasFrontend) {
75
+ result.hasFrontend = await hasModule(cwd, '@stonecrop/nuxt')
76
+ }
77
+ if (!result.hasGraphql) {
78
+ result.hasGraphql = await hasModule(cwd, '@stonecrop/nuxt-grafserv')
79
+ }
80
+
81
+ // Check for directories and files
82
+ result.hasDoctypes = existsSync(join(cwd, 'doctypes'))
83
+ result.hasServerDir = existsSync(join(cwd, 'server'))
84
+ result.hasSchema = existsSync(join(cwd, 'server', 'schema.graphql'))
85
+ result.hasResolvers =
86
+ existsSync(join(cwd, 'server', 'resolvers.ts')) || existsSync(join(cwd, 'server', 'resolvers.js'))
87
+
88
+ return result
89
+ }
90
+
91
+ /**
92
+ * Check if the current directory is a valid Nuxt project
93
+ */
94
+ export async function isNuxtProject(cwd: string): Promise<boolean> {
95
+ const features = await detectFeatures(cwd)
96
+ return features.isNuxtProject
97
+ }
98
+
99
+ /**
100
+ * Get a summary of what's already installed
101
+ */
102
+ export function getInstalledSummary(features: DetectedFeatures): string[] {
103
+ const installed: string[] = []
104
+
105
+ if (features.hasFrontend) {
106
+ installed.push('@stonecrop/nuxt (frontend module)')
107
+ }
108
+ if (features.hasGraphqlClient) {
109
+ installed.push('@stonecrop/graphql-client (GraphQL client)')
110
+ }
111
+ if (features.hasGraphql) {
112
+ installed.push('@stonecrop/nuxt-grafserv (GraphQL server)')
113
+ }
114
+ if (features.hasCasl) {
115
+ installed.push('@stonecrop/casl-middleware (authorization)')
116
+ }
117
+ if (features.hasRockfoil) {
118
+ installed.push('@stonecrop/rockfoil (PostGraphile middleware)')
119
+ }
120
+ if (features.hasDoctypes) {
121
+ installed.push('doctypes/ directory')
122
+ }
123
+
124
+ return installed
125
+ }