create-velocms-plugin 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VeloCMS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # create-velocms-plugin
2
+
3
+ Scaffolding CLI for VeloCMS plugin developers. Generates a ready-to-develop plugin template with all necessary boilerplate.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx create-velocms-plugin@latest my-plugin
9
+ ```
10
+
11
+ This interactive CLI will ask you for:
12
+ - Plugin name
13
+ - Plugin slug (kebab-case identifier)
14
+ - Plugin type (block, integration, or widget)
15
+ - Plugin category (e.g., seo, commerce, writing, analytics)
16
+ - Author name
17
+ - Plugin description
18
+
19
+ ## Output
20
+
21
+ The CLI generates a directory with:
22
+
23
+ ```
24
+ my-plugin/
25
+ ├── src/
26
+ │ ├── index.ts # Main plugin hook handler(s) + manifest
27
+ │ └── admin.tsx # Optional admin UI component
28
+ ├── tests/
29
+ │ └── example.test.ts # Example test case (node:test + @velocms/plugin-sdk/test-helpers)
30
+ ├── manifest.json # Generated plugin manifest (auto-populated)
31
+ ├── package.json # npm config with @velocms/plugin-sdk dependency
32
+ ├── tsconfig.json # TypeScript configuration
33
+ ├── tsconfig.build.json # Build-time tsconfig (excludes tests/)
34
+ ├── README.md # Getting started guide
35
+ ├── .gitignore # Standard ignores
36
+ ├── .npmignore # What NOT to publish alongside dist/
37
+ └── .eslintrc.json # ESLint config (VeloCMS standards)
38
+ ```
39
+
40
+ ## Next steps
41
+
42
+ After scaffolding:
43
+
44
+ ```bash
45
+ cd my-plugin
46
+ npm install
47
+ npm run build
48
+ npm run dev
49
+ ```
50
+
51
+ For full plugin development guide, see https://velocms.org/developers/sdk.
52
+
53
+ ## License
54
+
55
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+ import { mkdirSync, writeFileSync, readFileSync, copyFileSync, existsSync, readdirSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { createInterface } from "node:readline";
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ async function prompt(question, defaultValue) {
8
+ return new Promise((resolve) => {
9
+ const rl = createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout,
12
+ });
13
+ const query = defaultValue ? `${question} (${defaultValue}) ` : `${question} `;
14
+ rl.question(query, (answer) => {
15
+ rl.close();
16
+ resolve(answer.trim() || defaultValue || "");
17
+ });
18
+ });
19
+ }
20
+ function generateManifest(config, _templatePath) {
21
+ const authorEmail = config.author.includes("@") ? config.author : `${config.author.toLowerCase().replace(/\s+/g, ".")}@example.com`;
22
+ return JSON.stringify({
23
+ $schema: "https://velocms.org/schemas/plugin-v2.json",
24
+ name: `@example/${config.slug}`,
25
+ displayName: config.name,
26
+ version: "1.0.0",
27
+ description: config.description,
28
+ author: {
29
+ name: config.author,
30
+ email: authorEmail,
31
+ },
32
+ type: config.type,
33
+ category: config.category,
34
+ icon: "./icon.png",
35
+ engines: {
36
+ velocms: ">=1.0.0",
37
+ },
38
+ capabilities: {
39
+ content: { read: true },
40
+ },
41
+ pricing: {
42
+ model: "free",
43
+ },
44
+ entry: {
45
+ runtime: "./dist/runtime.js",
46
+ },
47
+ permissions_displayed_to_user: ["Read your posts and pages"],
48
+ license: "MIT",
49
+ }, null, 2);
50
+ }
51
+ function copyDir(src, dest) {
52
+ mkdirSync(dest, { recursive: true });
53
+ try {
54
+ const files = readdirSync(src, { withFileTypes: true });
55
+ for (const file of files) {
56
+ const srcPath = join(src, file.name);
57
+ const destPath = join(dest, file.name);
58
+ if (file.isDirectory()) {
59
+ copyDir(srcPath, destPath);
60
+ }
61
+ else {
62
+ copyFileSync(srcPath, destPath);
63
+ }
64
+ }
65
+ }
66
+ catch (err) {
67
+ console.error(`Failed to copy directory: ${err}`);
68
+ process.exit(1);
69
+ }
70
+ }
71
+ async function main() {
72
+ const args = process.argv.slice(2);
73
+ const nonInteractive = args.includes("--non-interactive");
74
+ const slugArg = args.find((a) => a.startsWith("--slug="))?.split("=")[1];
75
+ let pluginName = args[0] || (nonInteractive ? slugArg : "");
76
+ if (!pluginName) {
77
+ console.log("\n🎨 VeloCMS Plugin Scaffolder\n");
78
+ pluginName = await prompt("Plugin name:", "my-plugin");
79
+ }
80
+ if (!pluginName) {
81
+ console.error("❌ Plugin name is required");
82
+ process.exit(1);
83
+ }
84
+ const pluginSlug = slugArg || (nonInteractive ? pluginName.toLowerCase().replace(/\s+/g, "-") : await prompt("Plugin slug (kebab-case):", pluginName.toLowerCase().replace(/\s+/g, "-")));
85
+ const pluginType = nonInteractive ? "integration" : await prompt("Plugin type (block|integration|widget):", "integration");
86
+ const pluginCategory = nonInteractive ? "writing" : await prompt("Plugin category (e.g., seo, commerce, writing, analytics):", "writing");
87
+ const authorName = nonInteractive ? "Plugin Author" : await prompt("Author name:", "Plugin Author");
88
+ const description = nonInteractive ? "A VeloCMS plugin" : await prompt("Plugin description:", "A VeloCMS plugin");
89
+ const config = {
90
+ name: pluginName,
91
+ slug: pluginSlug,
92
+ category: pluginCategory,
93
+ type: pluginType,
94
+ author: authorName,
95
+ description: description,
96
+ };
97
+ const outputDir = join(process.cwd(), pluginSlug);
98
+ if (existsSync(outputDir)) {
99
+ console.error(`❌ Directory ${outputDir} already exists`);
100
+ process.exit(1);
101
+ }
102
+ mkdirSync(outputDir, { recursive: true });
103
+ const templateDir = join(__dirname, "template");
104
+ if (!existsSync(templateDir)) {
105
+ console.error(`❌ Template directory not found at ${templateDir}`);
106
+ process.exit(1);
107
+ }
108
+ try {
109
+ const RENAME_MAP = {
110
+ "gitignore.template": ".gitignore",
111
+ "npmignore.template": ".npmignore",
112
+ };
113
+ function processDir(src, dest) {
114
+ const files = readdirSync(src, { withFileTypes: true });
115
+ for (const file of files) {
116
+ const srcPath = join(src, file.name);
117
+ const renamedTo = RENAME_MAP[file.name];
118
+ const destPath = renamedTo
119
+ ? join(dest, renamedTo)
120
+ : file.name.endsWith(".template")
121
+ ? join(dest, file.name.slice(0, -9))
122
+ : join(dest, file.name);
123
+ if (file.isDirectory()) {
124
+ mkdirSync(destPath, { recursive: true });
125
+ processDir(srcPath, destPath);
126
+ }
127
+ else if (file.name !== "manifest.json") {
128
+ let content = readFileSync(srcPath, "utf-8");
129
+ content = content
130
+ .replace(/{{PLUGIN_NAME}}/g, config.name)
131
+ .replace(/{{PLUGIN_SLUG}}/g, config.slug)
132
+ .replace(/{{PLUGIN_CATEGORY}}/g, config.category)
133
+ .replace(/{{PLUGIN_TYPE}}/g, config.type)
134
+ .replace(/{{AUTHOR_NAME}}/g, config.author)
135
+ .replace(/{{PLUGIN_DESCRIPTION}}/g, config.description);
136
+ writeFileSync(destPath, content);
137
+ }
138
+ }
139
+ }
140
+ processDir(templateDir, outputDir);
141
+ }
142
+ catch (err) {
143
+ console.error(`Failed to copy template: ${err}`);
144
+ process.exit(1);
145
+ }
146
+ const manifestContent = generateManifest(config, templateDir);
147
+ writeFileSync(join(outputDir, "manifest.json"), manifestContent);
148
+ console.log(`\n✔ Created plugin scaffold at ./${pluginSlug}`);
149
+ console.log(`✔ Run \`cd ${pluginSlug} && npm install\``);
150
+ console.log(`✔ Run \`npm run dev\` to test against a local VeloCMS instance\n`);
151
+ }
152
+ main().catch((err) => {
153
+ console.error(err);
154
+ process.exit(1);
155
+ });
@@ -0,0 +1,24 @@
1
+ {
2
+ "extends": [
3
+ "eslint:recommended"
4
+ ],
5
+ "parser": "@typescript-eslint/parser",
6
+ "parserOptions": {
7
+ "ecmaVersion": 2020,
8
+ "sourceType": "module"
9
+ },
10
+ "env": {
11
+ "es2020": true,
12
+ "node": true
13
+ },
14
+ "rules": {
15
+ "no-unused-vars": "off",
16
+ "@typescript-eslint/no-unused-vars": [
17
+ "warn",
18
+ {
19
+ "argsIgnorePattern": "^_"
20
+ }
21
+ ],
22
+ "no-console": "off"
23
+ }
24
+ }
@@ -0,0 +1,135 @@
1
+ # {{PLUGIN_NAME}}
2
+
3
+ {{PLUGIN_DESCRIPTION}}
4
+
5
+ ## Getting started
6
+
7
+ ```bash
8
+ npm install
9
+ npm run build
10
+ ```
11
+
12
+ ## Development
13
+
14
+ Watch mode:
15
+
16
+ ```bash
17
+ npm run dev
18
+ ```
19
+
20
+ ## Testing
21
+
22
+ This plugin uses `@velocms/plugin-sdk/test-helpers` so you can write unit tests
23
+ without a running VeloCMS instance:
24
+
25
+ ```typescript
26
+ import { mockVelocms } from "@velocms/plugin-sdk/test-helpers";
27
+ import { afterPostPublish } from "../src/index.js";
28
+
29
+ const ctx = mockVelocms({
30
+ tenant: { id: "t1", slug: "my-blog", locale: "en-US" },
31
+ });
32
+
33
+ await afterPostPublish({ post: { slug: "hello-world" } }, ctx);
34
+ // ctx._fetchCalls, ctx._logs, ctx._kvStore are available for assertions
35
+ ```
36
+
37
+ Run tests:
38
+
39
+ ```bash
40
+ npm run test
41
+ ```
42
+
43
+ ## Integration with VeloCMS
44
+
45
+ This plugin conforms to the [VeloCMS Plugin Manifest v2](https://velocms.org/developers/sdk#manifest).
46
+ The `manifest.json` file declares:
47
+
48
+ - **Type**: {{PLUGIN_TYPE}}
49
+ - **Category**: {{PLUGIN_CATEGORY}}
50
+ - **Author**: {{AUTHOR_NAME}}
51
+
52
+ ### Manifest field reference
53
+
54
+ | Field | Required | Description |
55
+ |---|---|---|
56
+ | `name` | Yes | npm-style scoped name e.g. `@myorg/my-plugin` |
57
+ | `displayName` | Yes | Human-readable title shown in marketplace |
58
+ | `version` | Yes | Semantic version e.g. `1.0.0` |
59
+ | `description` | Yes | One-line summary |
60
+ | `author.name` | Yes | Display name of the developer |
61
+ | `author.email` | Yes | Contact email for review communication |
62
+ | `type` | Yes | `block` / `integration` / `widget` |
63
+ | `category` | Yes | `seo` / `commerce` / `analytics` / `writing` / `social` / `media` / `other` |
64
+ | `capabilities.network.allowlist` | Conditional | Required if plugin makes HTTP requests — list of bare domains |
65
+ | `pricing.model` | Yes | `free` or `paid` |
66
+ | `pricing.priceCents` | Conditional | Required when `model === "paid"` — price in USD cents |
67
+ | `permissions_displayed_to_user` | Yes | Plain-English list of what the plugin does — shown in the install consent modal |
68
+ | `engines.velocms` | No | Semver range for VeloCMS engine compatibility e.g. `>=1.0.0` |
69
+ | `entry.runtime` | Yes | Path to the compiled runtime bundle e.g. `./dist/runtime.js` |
70
+ | `entry.admin` | No | Path to compiled admin UI component bundle |
71
+ | `license` | Yes | SPDX identifier e.g. `MIT`, `Apache-2.0` |
72
+
73
+ See `src/index.ts` for the hook implementation.
74
+
75
+ ## Publishing
76
+
77
+ 1. Build: `npm run build`
78
+ 2. Verify: `node dist/index.js` — confirm the plugin loads without error
79
+ 3. Log in to the VeloCMS developer portal: `https://velocms.org/developers`
80
+ 4. Create a developer account and complete Stripe Connect onboarding (for paid plugins)
81
+ 5. Submit for review: `https://velocms.org/plugins/submit`
82
+ - Upload `dist/` bundle
83
+ - Paste your `manifest.json`
84
+ - Wait for the automated review pipeline (Stage 1–4) then human review
85
+
86
+ ### Automated CI for plugin authors
87
+
88
+ Copy the following workflow to your repo as `.github/workflows/plugin-publish.yml`:
89
+
90
+ ```yaml
91
+ name: VeloCMS Plugin Publish
92
+
93
+ on:
94
+ push:
95
+ tags:
96
+ - "v*.*.*"
97
+
98
+ jobs:
99
+ build-and-submit:
100
+ runs-on: ubuntu-latest
101
+
102
+ steps:
103
+ - uses: actions/checkout@v4
104
+
105
+ - name: Set up Node.js
106
+ uses: actions/setup-node@v4
107
+ with:
108
+ node-version: "20"
109
+ cache: "npm"
110
+
111
+ - name: Install dependencies
112
+ run: npm ci
113
+
114
+ - name: Run tests
115
+ run: npm run test
116
+
117
+ - name: Build
118
+ run: npm run build
119
+
120
+ - name: Submit to VeloCMS marketplace
121
+ env:
122
+ VELOCMS_DEVELOPER_TOKEN: ${{ secrets.VELOCMS_DEVELOPER_TOKEN }}
123
+ run: |
124
+ VERSION="${GITHUB_REF#refs/tags/v}"
125
+ curl -X POST https://velocms.org/api/developer/plugin-submit \
126
+ -H "Authorization: Bearer $VELOCMS_DEVELOPER_TOKEN" \
127
+ -H "Content-Type: application/json" \
128
+ -d "{\"version\":\"$VERSION\",\"bundleUrl\":\"https://github.com/$GITHUB_REPOSITORY/releases/download/$GITHUB_REF_NAME/dist.zip\"}"
129
+ ```
130
+
131
+ > Set `VELOCMS_DEVELOPER_TOKEN` in your repository secrets (GitHub → Settings → Secrets).
132
+
133
+ ## License
134
+
135
+ MIT
@@ -0,0 +1,5 @@
1
+ node_modules/
2
+ dist/
3
+ *.tsbuildinfo
4
+ .DS_Store
5
+ *.log
@@ -0,0 +1,9 @@
1
+ src/
2
+ tests/
3
+ tsconfig.json
4
+ tsconfig.build.json
5
+ *.tsbuildinfo
6
+ .eslintrc.json
7
+ .DS_Store
8
+ *.log
9
+ node_modules/
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@example/{{PLUGIN_SLUG}}",
3
+ "version": "1.0.0",
4
+ "description": "{{PLUGIN_DESCRIPTION}}",
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsc -p tsconfig.build.json",
8
+ "typecheck": "tsc --noEmit",
9
+ "dev": "tsc --watch",
10
+ "test": "node --import tsx --test tests/*.test.ts"
11
+ },
12
+ "keywords": [
13
+ "velocms",
14
+ "plugin",
15
+ "{{PLUGIN_CATEGORY}}"
16
+ ],
17
+ "author": "{{AUTHOR_NAME}}",
18
+ "license": "MIT",
19
+ "dependencies": {
20
+ "@velocms/plugin-sdk": "^1.0.0-alpha.2"
21
+ },
22
+ "devDependencies": {
23
+ "typescript": "^5.0.0",
24
+ "tsx": "^4.0.0",
25
+ "@types/react": "^19.0.0"
26
+ }
27
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Admin UI component for {{PLUGIN_NAME}}
3
+ *
4
+ * Optional: only loaded in the admin dashboard if manifest.entry.admin
5
+ * is uncommented in manifest.json.
6
+ *
7
+ * This is a React component — return JSX for settings UI.
8
+ */
9
+
10
+ export default function AdminPanel() {
11
+ return (
12
+ <div style={{ padding: "1rem" }}>
13
+ <h2>{{PLUGIN_NAME}} Settings</h2>
14
+ <p>Configure your plugin here.</p>
15
+ {/* Add form fields, toggles, etc. */}
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * {{PLUGIN_NAME}}
3
+ *
4
+ * A minimal VeloCMS plugin scaffold showing how to:
5
+ * 1. Export a `manifest` object (required)
6
+ * 2. Implement hook handlers (see manifest.entry.runtime)
7
+ * 3. Export admin settings UI (optional, see manifest.entry.admin)
8
+ */
9
+
10
+ import type {
11
+ PluginManifest,
12
+ HookContext,
13
+ AfterPostPublishPayload,
14
+ } from "@velocms/plugin-sdk";
15
+
16
+ /**
17
+ * Plugin manifest — declares metadata, capabilities, and entry points.
18
+ * This is the canonical shape that the VeloCMS review pipeline validates.
19
+ */
20
+ export const manifest: PluginManifest = {
21
+ $schema: "https://velocms.org/schemas/plugin-v2.json",
22
+ name: "@example/{{PLUGIN_SLUG}}",
23
+ displayName: "{{PLUGIN_NAME}}",
24
+ version: "1.0.0",
25
+ description: "{{PLUGIN_DESCRIPTION}}",
26
+ author: {
27
+ name: "{{AUTHOR_NAME}}",
28
+ email: "author@example.com",
29
+ },
30
+ type: "{{PLUGIN_TYPE}}" as any,
31
+ category: "{{PLUGIN_CATEGORY}}",
32
+ icon: "./icon.png",
33
+ engines: {
34
+ velocms: ">=1.0.0",
35
+ },
36
+ capabilities: {
37
+ content: { read: true },
38
+ },
39
+ pricing: {
40
+ model: "free",
41
+ },
42
+ entry: {
43
+ runtime: "./dist/runtime.js",
44
+ // admin: "./dist/admin.js", // Uncomment to enable admin UI
45
+ },
46
+ permissions_displayed_to_user: [
47
+ "Read your posts and pages",
48
+ ],
49
+ license: "MIT",
50
+ };
51
+
52
+ /**
53
+ * Hook handler example.
54
+ *
55
+ * Replace this with actual logic for your plugin. VeloCMS matches exported
56
+ * function names against the SDK's `HookName` union — export a function
57
+ * named exactly like the hook you want to handle, e.g.:
58
+ * - `afterPostPublish`: fired when a post is published
59
+ * - `afterPostCreate` / `afterPostUpdate` / `afterPostDelete`
60
+ * - `afterMemberSignup` / `afterMemberUpdate`
61
+ * - `onAppStart`: fired once when the plugin runtime loads (init logic)
62
+ *
63
+ * Import the `HookName` union from `@velocms/plugin-sdk` for the full list.
64
+ */
65
+ export async function afterPostPublish(
66
+ payload: AfterPostPublishPayload,
67
+ ctx: HookContext
68
+ ): Promise<void> {
69
+ ctx.log.info("Post published", { slug: payload.post.slug });
70
+
71
+ // Example: validate content, send a webhook via ctx.fetch(), etc.
72
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * {{PLUGIN_NAME}} — tests
3
+ *
4
+ * Uses @velocms/plugin-sdk/test-helpers to write unit tests without
5
+ * a running VeloCMS instance. Replace with your own test cases.
6
+ */
7
+
8
+ import { strict as assert } from "node:assert";
9
+ import { test } from "node:test";
10
+ import { mockVelocms } from "@velocms/plugin-sdk/test-helpers";
11
+ import { manifest, afterPostPublish } from "../src/index.js";
12
+
13
+ test("manifest is exported with correct name", () => {
14
+ assert.ok(manifest);
15
+ assert.equal(manifest.name, "@example/{{PLUGIN_SLUG}}");
16
+ assert.equal(manifest.displayName, "{{PLUGIN_NAME}}");
17
+ });
18
+
19
+ test("manifest has required fields", () => {
20
+ assert.ok(manifest.version);
21
+ assert.ok(manifest.description);
22
+ assert.ok(manifest.author?.name);
23
+ assert.ok(manifest.entry?.runtime);
24
+ assert.ok(manifest.permissions_displayed_to_user?.length);
25
+ });
26
+
27
+ test("afterPostPublish executes without throwing", async () => {
28
+ const ctx = mockVelocms({
29
+ tenant: { id: "t1", slug: "{{PLUGIN_SLUG}}-test", locale: "en-US" },
30
+ settings: { blogName: "Test Blog", plan: "pro" },
31
+ });
32
+
33
+ const payload = { post: { slug: "test-post", title: "Test Post" } };
34
+
35
+ // Should resolve without error
36
+ await assert.doesNotReject(() => afterPostPublish(payload as never, ctx));
37
+ });
38
+
39
+ test("mockVelocms provides working kv store", async () => {
40
+ const ctx = mockVelocms({ kvStore: { existing_key: "existing_value" } });
41
+
42
+ // Pre-seeded key readable
43
+ assert.equal(await ctx.kv.get("existing_key"), "existing_value");
44
+
45
+ // Set + get roundtrip
46
+ await ctx.kv.set("new_key", "new_value");
47
+ assert.equal(await ctx.kv.get("new_key"), "new_value");
48
+
49
+ // Delete removes key
50
+ await ctx.kv.delete("new_key");
51
+ assert.equal(await ctx.kv.get("new_key"), null);
52
+ });
53
+
54
+ test("mockVelocms records log calls", async () => {
55
+ const ctx = mockVelocms();
56
+
57
+ ctx.log.info("test info message", { extra: "data" });
58
+ ctx.log.warn("test warn message");
59
+ ctx.log.error("test error message");
60
+
61
+ assert.equal(ctx._logs.length, 3);
62
+ assert.equal(ctx._logs[0]?.level, "info");
63
+ assert.equal(ctx._logs[0]?.message, "test info message");
64
+ assert.deepEqual(ctx._logs[0]?.data, { extra: "data" });
65
+ assert.equal(ctx._logs[1]?.level, "warn");
66
+ assert.equal(ctx._logs[2]?.level, "error");
67
+ });
68
+
69
+ test("mockVelocms records fetch calls", async () => {
70
+ const ctx = mockVelocms({
71
+ fetchResponse: { ok: true, status: 200, data: { success: true } },
72
+ });
73
+
74
+ const res = await ctx.fetch("https://api.example.com/webhook", {
75
+ method: "POST",
76
+ body: JSON.stringify({ event: "post_published" }),
77
+ });
78
+
79
+ assert.ok(res.ok);
80
+ assert.equal(ctx._fetchCalls.length, 1);
81
+ assert.equal(ctx._fetchCalls[0]?.url, "https://api.example.com/webhook");
82
+ assert.equal(ctx._fetchCalls[0]?.options?.method, "POST");
83
+ });
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "declaration": true,
5
+ "declarationMap": false,
6
+ "sourceMap": false,
7
+ "removeComments": true
8
+ },
9
+ "exclude": ["tests/**"]
10
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "lib": ["ES2020", "DOM"],
6
+ "jsx": "react-jsx",
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "moduleResolution": "node"
17
+ },
18
+ "include": ["src"],
19
+ "exclude": ["node_modules", "dist", "tests"]
20
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "create-velocms-plugin",
3
+ "version": "1.0.0",
4
+ "description": "Scaffolding tool for VeloCMS plugin development",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-velocms-plugin": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/cli.js",
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.build.json && node scripts/copy-template.mjs",
17
+ "typecheck": "tsc --noEmit",
18
+ "dev": "node dist/cli.js",
19
+ "prepublishOnly": "npm run build && npm run typecheck"
20
+ },
21
+ "keywords": [
22
+ "velocms",
23
+ "cms",
24
+ "plugin",
25
+ "cli",
26
+ "scaffold",
27
+ "scaffolding",
28
+ "generator",
29
+ "create-velocms-plugin"
30
+ ],
31
+ "license": "MIT",
32
+ "author": {
33
+ "name": "VeloCMS",
34
+ "url": "https://velocms.org"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/VeloCMS/create-velocms-plugin.git"
39
+ },
40
+ "homepage": "https://velocms.org/developers/sdk",
41
+ "bugs": {
42
+ "url": "https://github.com/VeloCMS/create-velocms-plugin/issues"
43
+ },
44
+ "devDependencies": {
45
+ "typescript": "^5.0.0"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ }
50
+ }