@object-ui/create-plugin 17.3.0 → 17.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,117 @@
1
1
  # @object-ui/create-plugin
2
2
 
3
+ ## 17.6.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 195b9e4: A scaffolded plugin's generated manifest now asks for the same `@testing-library/jest-dom` range this repo installs.
8
+
9
+ `src/templates.ts`'s `DEV_DEPENDENCIES` had fossilised one patch behind the repo
10
+ root: the template said `^7.0.0` while the root manifest had moved to `^7.0.1`.
11
+ `templates.test.ts`'s anchor rule caught it and was red on `main`.
12
+
13
+ Same defect class, same day and same dependabot wave as the `lucide-react` drift
14
+ in `@object-ui/cli`'s app generator, so both templates move together here — which
15
+ is how the previous occurrence of this incident was handled too (objectui#4098 /
16
+ PR objectui#4099 moved these same two templates in one PR). This one came from
17
+ the dev-dependencies group bump rather than the single-package bump, and it was
18
+ found only because the two ratchets live in different packages: the anchor rule
19
+ throws on its first mismatch, so nothing reports the second template until the
20
+ first is green.
21
+
22
+ The remaining seven anchored ranges in this template were swept against the same
23
+ wave and are all in sync.
24
+
25
+ ## 17.5.0
26
+
27
+ ## 17.4.0
28
+
29
+ ### Patch Changes
30
+
31
+ - e473b6c: Anchor the scaffold's build-side `devDependencies` to this repo's real toolchain, and pin the whole generated manifest against drift
32
+
33
+ A freshly scaffolded plugin declared a build stack one to two majors behind the one this monorepo actually builds and tests every in-tree plugin with: `vite ^7.3.1` against the repo's `^8.2.0`, `@vitejs/plugin-react ^4.2.1` against `^6.0.5`, `vite-plugin-dts ^4.5.4` against `^5.0.3`, `typescript ^5.9.3` against `^6.0.3`, `vitest ^4.0.18` against `^4.1.10`. Those five ranges were never sourced from anything — objectui#3716's end-to-end run of the generated artifact only ever exercised the versions installed in this repo, so the declared ranges were not the ones under test. All five now quote an in-repo anchor, the same way the three testing ranges already did.
34
+
35
+ Two anchors, because the root manifest does not declare everything. `create-plugin` writes into `<cwd>/packages/plugin-<name>`, so a generated plugin is a literal sibling of `packages/plugin-*`; those manifests anchor the two build-only tools the root omits (`@vitejs/plugin-react`, `vite-plugin-dts`), and the root anchors the rest.
36
+
37
+ The parity test now covers **every** entry of the generated `devDependencies` rather than the three testing ones, including a completeness check that fails when a dependency is added without naming its anchor — the five build ranges drifted precisely because nothing pinned them. It also asserts the two anchors agree wherever both declare a dependency, so which one is read cannot hide a drift.
38
+
39
+ The generated `vite.config.ts` resolves its library entry from `import.meta.dirname` instead of `__dirname`. vite 8 still defines `__dirname` under its default `bundle` config loader but warns on it ("unsupported by `configLoader: 'native'`, which is planned to become the default in a future major version of Vite ... Use `import.meta.dirname` instead"), and under `native` — which imports the config with Node's own ESM loader, where no `__dirname` exists — the generated config failed to load outright. `apps/console/vite.config.ts` was converted for the same reason in objectui#3384.
40
+
41
+ Not a peer-dependency fix: `@vitejs/plugin-react ^4.2.1` resolved to 4.7.0, whose vite peer had widened to `^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0` and accepted the declared `vite ^7.3.1`, so the old manifest installed cleanly. The cost was a scaffold lagging its own monorepo, not a failing install.
42
+
43
+ - f4f42b4: create-plugin: make the scaffolded plugin's own test suite runnable
44
+
45
+ The generator wrote an example test importing `@testing-library/react` and
46
+ asserting with `toBeInTheDocument()`, plus a `test: 'vitest run'` script, while
47
+ declaring neither library and giving Vitest no DOM environment — so `pnpm test`
48
+ in a freshly scaffolded plugin failed on the very first run, at import
49
+ resolution.
50
+
51
+ The generated `package.json` now declares `@testing-library/react`,
52
+ `@testing-library/jest-dom` and `jsdom` (each range copied from this
53
+ monorepo's own manifest), the generated `vite.config.ts` gains a `test` block
54
+ with `environment: 'jsdom'`, `globals: true` and `setupFiles`, and a
55
+ `vitest.setup.ts` registering the jest-dom matchers is written alongside it.
56
+ The templates moved to `src/templates.ts` so the generated artifacts can be
57
+ pinned by unit tests without executing the CLI.
58
+
59
+ - c852682: Remove the scaffold's unused pinned icon dependency, and make its generated schema interface reachable
60
+
61
+ Two declared-but-unreachable artifacts in the generated plugin, both on the blind side of
62
+ the import gate objectui#3733 added — that gate rejects an import nothing declares, and
63
+ never looked for a declaration nothing imports.
64
+
65
+ **The generated `dependencies` no longer pin `lucide-react`** (objectui#3755). It was
66
+ declared at `^0.563.0` and imported by no generated source file, so every freshly
67
+ scaffolded plugin really installed lucide 0.563.x for code that never referenced it — two
68
+ majors behind the 23 in-repo declarations, all `^1.28.0`. Worse than ordinary caret drift:
69
+ a `0.x` caret does not cross minors, so `^0.563.0` is `>=0.563.0 <0.564.0` and could not
70
+ float even within `0.x`. It is removed rather than re-anchored because this repo declares
71
+ an icon library where it imports one — of the 24 manifests mentioning `lucide-react`, 23
72
+ import it, and none pre-declares it for code not yet written. An author who wants icons
73
+ runs `pnpm add lucide-react` and lands the current version by construction, with no anchor
74
+ table to maintain for an unused entry. The generated `dependencies` is now exactly the four
75
+ `workspace:*` platform packages, which cannot drift at all.
76
+
77
+ **The generated `src/index.tsx` now re-exports the schema interface** from `src/types.ts`
78
+ (objectui#3759). The generated `exports` map exposes exactly one key — `.` — so the entry
79
+ is a consumer's only door, and nothing walked through it to `src/types.ts`: no generated
80
+ source imported it, and the deep paths that would have reached it (`<pkg>/types`,
81
+ `<pkg>/dist/types`) are closed by that same map. The interface in it is the plugin's schema
82
+ contract, and it shipped dead — while the generator's own documentation page told authors to
83
+ "export your schema types … make it importable rather than internal". A named type-only
84
+ re-export, matching the four in-repo plugins that ship a `src/types.ts` and the worked
85
+ example in the plugin-development guide.
86
+
87
+ **That interface now extends `BaseSchema` from `@object-ui/types`** instead of re-declaring
88
+ a subset of the base node. Unreachable, a hand-rolled `{ type; id?; className? }` was only
89
+ dead weight; published, it would be a second dialect of a node the protocol already defines,
90
+ silently missing everything else `BaseSchema` carries (`name`, `label`, `visible`, …). Only
91
+ the `type` literal is narrowed locally, the same shape every in-repo plugin uses. This also
92
+ makes the generated `@object-ui/types` dependency a used declaration.
93
+
94
+ Both halves are pinned structurally rather than by string match, so the next dead artifact
95
+ fails a test instead of shipping: no versioned runtime dependency may be declared that no
96
+ generated source imports (`workspace:*` exempt — it cannot drift), and no generated `src/**`
97
+ module may be unreachable from the single entry the `exports` map exposes. Each of those
98
+ gates passes over an empty result on today's templates, so each is paired with a self-test
99
+ that plants the removed defect back and asserts the rule names it — a gate that is green
100
+ because it produces nothing is not a gate.
101
+
102
+ - c29ceff: Move the generator templates' dependency ranges onto the repo's current ones
103
+
104
+ The dependabot wave of 2026-08-10 bumped `lucide-react` to `1.29.0` and `vite`
105
+ to `8.2.1` in this repo's own manifests, but the ranges hard-coded in the
106
+ scaffold generators do not move with it — dependabot does not know the
107
+ templates exist. A project scaffolded by `objectui init` / `objectui dev` or by
108
+ `create-plugin` therefore declared a range the repo itself had already moved
109
+ past.
110
+
111
+ Three ranges are re-anchored: `lucide-react` `^1.28.0` → `^1.29.0` in the routed
112
+ app generator, and `vite` `^8.2.0` → `^8.2.1` in both the shared CLI scaffold
113
+ devDependencies and the create-plugin template.
114
+
3
115
  ## 17.3.0
4
116
 
5
117
  ## 17.2.0
package/README.md CHANGED
@@ -29,6 +29,7 @@ packages/plugin-my-plugin/
29
29
  ├── package.json
30
30
  ├── tsconfig.json
31
31
  ├── vite.config.ts
32
+ ├── vitest.setup.ts # Registers the jest-dom matchers
32
33
  └── README.md
33
34
  ```
34
35
 
@@ -37,7 +38,8 @@ packages/plugin-my-plugin/
37
38
  - ✅ TypeScript support out of the box
38
39
  - ✅ Vite build configuration
39
40
  - ✅ Component registration with ComponentRegistry
40
- - ✅ Test setup with Vitest
41
+ - ✅ Runnable Vitest setup — jsdom environment, Testing Library and the jest-dom
42
+ matchers are all declared, so `pnpm test` is green on the first run
41
43
  - ✅ Proper package.json with workspace dependencies
42
44
  - ✅ README template
43
45
  - ✅ Type definitions
@@ -83,16 +85,6 @@ pnpm create @object-ui/plugin my-plugin --description "My awesome plugin" --auth
83
85
  pnpm test
84
86
  ```
85
87
 
86
- <!-- release-metadata:v3.3.0 -->
87
-
88
- ## Compatibility
89
-
90
- - **Node.js:** ≥ 18
91
- - **TypeScript:** ≥ 5.0 (strict mode)
92
- - **`@objectstack/spec`:** ^3.3.0
93
- - **`@objectstack/client`:** ^3.3.0
94
- - **Tailwind CSS:** ≥ 3.4 (for packages with UI)
95
-
96
88
  ## Links
97
89
 
98
90
  - 📚 [Documentation](https://www.objectui.org/docs/guide/plugin-development)
package/dist/index.js CHANGED
@@ -6,90 +6,32 @@ import chalk from "chalk";
6
6
  import prompts from "prompts";
7
7
  import * as path from "path";
8
8
  import fs from "fs-extra";
9
- import { fileURLToPath } from "url";
10
- var __filename = fileURLToPath(import.meta.url);
11
- var __dirname = path.dirname(__filename);
12
- var program = new Command();
13
- async function createPlugin(pluginName, options = {}) {
14
- console.log(chalk.blue("\n\u{1F680} ObjectUI Plugin Generator\n"));
15
- if (!pluginName) {
16
- const response = await prompts({
17
- type: "text",
18
- name: "name",
19
- message: "Plugin name (without prefix):",
20
- validate: (value) => {
21
- if (value.length === 0) return "Plugin name is required";
22
- if (!/^[a-z0-9-]+$/.test(value)) {
23
- return "Plugin name must contain only lowercase letters, numbers, and hyphens";
24
- }
25
- if (value.includes("..") || value.includes("/") || value.includes("\\")) {
26
- return 'Plugin name cannot contain path separators or ".."';
27
- }
28
- return true;
29
- }
30
- });
31
- pluginName = response.name;
32
- if (!pluginName) {
33
- console.log(chalk.red("\n\u274C Plugin name is required"));
34
- process.exit(1);
35
- }
36
- }
37
- if (!/^[a-z0-9-]+$/.test(pluginName)) {
38
- console.log(chalk.red("\n\u274C Plugin name must contain only lowercase letters, numbers, and hyphens"));
39
- process.exit(1);
40
- }
41
- if (pluginName.includes("..") || pluginName.includes("/") || pluginName.includes("\\") || path.isAbsolute(pluginName)) {
42
- console.log(chalk.red("\n\u274C Invalid plugin name: path traversal detected"));
43
- process.exit(1);
44
- }
45
- const cleanName = pluginName.replace(/^plugin-/, "").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
46
- if (cleanName.length === 0) {
47
- console.log(chalk.red("\n\u274C Plugin name cannot be empty after sanitization"));
48
- process.exit(1);
49
- }
50
- const fullPackageName = `plugin-${cleanName}`;
51
- const pascalCaseName = cleanName.split("-").filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
52
- const answers = await prompts([
53
- {
54
- type: "text",
55
- name: "description",
56
- message: "Plugin description:",
57
- initial: options.description || `${pascalCaseName} plugin for ObjectUI`
58
- },
59
- {
60
- type: "text",
61
- name: "author",
62
- message: "Author name:",
63
- initial: options.author || ""
64
- }
65
- ]);
66
- const targetDir = path.join(process.cwd(), "packages", fullPackageName);
67
- if (fs.existsSync(targetDir)) {
68
- console.log(chalk.red(`
69
- \u274C Directory already exists: ${targetDir}`));
70
- process.exit(1);
71
- }
72
- console.log(chalk.green(`
73
- \u2728 Creating plugin: ${fullPackageName}...
74
- `));
75
- fs.mkdirpSync(targetDir);
76
- fs.mkdirpSync(path.join(targetDir, "src"));
77
- const templateDir = path.join(__dirname, "..", "templates", "plugin");
78
- const vars = {
79
- PACKAGE_NAME: `@object-ui/${fullPackageName}`,
80
- PLUGIN_NAME: cleanName,
81
- PASCAL_NAME: pascalCaseName,
82
- DESCRIPTION: answers.description,
83
- AUTHOR: answers.author,
84
- VERSION: "0.1.0",
85
- YEAR: (/* @__PURE__ */ new Date()).getFullYear()
86
- };
87
- const packageJson = {
88
- name: vars.PACKAGE_NAME,
89
- version: vars.VERSION,
9
+
10
+ // src/templates.ts
11
+ var VITEST_SETUP_FILE = "vitest.setup.ts";
12
+ var DEV_DEPENDENCIES = {
13
+ "@testing-library/jest-dom": "^7.0.1",
14
+ "@testing-library/react": "^16.3.2",
15
+ "@vitejs/plugin-react": "^6.0.5",
16
+ jsdom: "^30.0.1",
17
+ typescript: "^6.0.3",
18
+ vite: "^8.2.1",
19
+ "vite-plugin-dts": "^5.0.3",
20
+ vitest: "^4.1.10"
21
+ };
22
+ var DEPENDENCIES = {
23
+ "@object-ui/components": "workspace:*",
24
+ "@object-ui/core": "workspace:*",
25
+ "@object-ui/react": "workspace:*",
26
+ "@object-ui/types": "workspace:*"
27
+ };
28
+ function buildPackageJson(vars) {
29
+ return {
30
+ name: vars.packageName,
31
+ version: vars.version,
90
32
  type: "module",
91
33
  license: "MIT",
92
- description: vars.DESCRIPTION,
34
+ description: vars.description,
93
35
  main: "dist/index.umd.cjs",
94
36
  module: "dist/index.js",
95
37
  types: "dist/index.d.ts",
@@ -105,30 +47,16 @@ async function createPlugin(pluginName, options = {}) {
105
47
  test: "vitest run",
106
48
  lint: "eslint ."
107
49
  },
108
- dependencies: {
109
- "@object-ui/components": "workspace:*",
110
- "@object-ui/core": "workspace:*",
111
- "@object-ui/react": "workspace:*",
112
- "@object-ui/types": "workspace:*",
113
- "lucide-react": "^0.563.0"
114
- },
50
+ dependencies: { ...DEPENDENCIES },
115
51
  peerDependencies: {
116
52
  react: "^18.0.0 || ^19.0.0",
117
53
  "react-dom": "^18.0.0 || ^19.0.0"
118
54
  },
119
- devDependencies: {
120
- "@vitejs/plugin-react": "^4.2.1",
121
- typescript: "^5.9.3",
122
- vite: "^7.3.1",
123
- "vite-plugin-dts": "^4.5.4",
124
- vitest: "^4.0.18"
125
- }
55
+ devDependencies: { ...DEV_DEPENDENCIES }
126
56
  };
127
- fs.writeFileSync(
128
- path.join(targetDir, "package.json"),
129
- JSON.stringify(packageJson, null, 2)
130
- );
131
- const tsconfig = {
57
+ }
58
+ function buildTsconfig() {
59
+ return {
132
60
  extends: "../../tsconfig.json",
133
61
  compilerOptions: {
134
62
  outDir: "./dist",
@@ -139,11 +67,9 @@ async function createPlugin(pluginName, options = {}) {
139
67
  include: ["src/**/*"],
140
68
  exclude: ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"]
141
69
  };
142
- fs.writeFileSync(
143
- path.join(targetDir, "tsconfig.json"),
144
- JSON.stringify(tsconfig, null, 2)
145
- );
146
- const viteConfig = `import { defineConfig } from 'vite';
70
+ }
71
+ function buildViteConfig(vars) {
72
+ return `import { defineConfig } from 'vite';
147
73
  import react from '@vitejs/plugin-react';
148
74
  import dts from 'vite-plugin-dts';
149
75
  import * as path from 'path';
@@ -157,8 +83,8 @@ export default defineConfig({
157
83
  ],
158
84
  build: {
159
85
  lib: {
160
- entry: path.resolve(__dirname, 'src/index.tsx'),
161
- name: '${pascalCaseName}',
86
+ entry: path.resolve(import.meta.dirname, 'src/index.tsx'),
87
+ name: '${vars.pascalName}',
162
88
  formats: ['es', 'umd'],
163
89
  fileName: (format) => \`index.\${format === 'es' ? 'js' : 'umd.cjs'}\`,
164
90
  },
@@ -172,26 +98,38 @@ export default defineConfig({
172
98
  },
173
99
  },
174
100
  },
101
+ test: {
102
+ globals: true,
103
+ environment: 'jsdom',
104
+ setupFiles: ['./${VITEST_SETUP_FILE}'],
105
+ },
175
106
  });
176
107
  `;
177
- fs.writeFileSync(path.join(targetDir, "vite.config.ts"), viteConfig);
178
- const readme = `# ${vars.PACKAGE_NAME}
108
+ }
109
+ function buildVitestSetup() {
110
+ return `// Registers @testing-library/jest-dom's matchers (\`toBeInTheDocument\` and
111
+ // friends) on Vitest's \`expect\`, which the example test in src/ relies on.
112
+ import '@testing-library/jest-dom/vitest';
113
+ `;
114
+ }
115
+ function buildReadme(vars) {
116
+ return `# ${vars.packageName}
179
117
 
180
- ${vars.DESCRIPTION}
118
+ ${vars.description}
181
119
 
182
120
  ## Installation
183
121
 
184
122
  \`\`\`bash
185
- pnpm add ${vars.PACKAGE_NAME}
123
+ pnpm add ${vars.packageName}
186
124
  \`\`\`
187
125
 
188
126
  ## Usage
189
127
 
190
128
  \`\`\`tsx
191
- import { ${pascalCaseName} } from '${vars.PACKAGE_NAME}';
129
+ import { ${vars.pascalName} } from '${vars.packageName}';
192
130
 
193
131
  // Use the component
194
- <${pascalCaseName} />
132
+ <${vars.pascalName} />
195
133
  \`\`\`
196
134
 
197
135
  ## Development
@@ -209,12 +147,13 @@ pnpm lint
209
147
 
210
148
  ## License
211
149
 
212
- MIT \xA9 ${vars.AUTHOR}
150
+ MIT \xA9 ${vars.author}
213
151
  `;
214
- fs.writeFileSync(path.join(targetDir, "README.md"), readme);
215
- const indexFile = `/**
152
+ }
153
+ function buildIndexFile(vars) {
154
+ return `/**
216
155
  * ObjectUI
217
- * Copyright (c) ${vars.YEAR}-present ObjectStack Inc.
156
+ * Copyright (c) ${vars.year}-present ObjectStack Inc.
218
157
  *
219
158
  * This source code is licensed under the MIT license found in the
220
159
  * LICENSE file in the root directory of this source tree.
@@ -222,18 +161,19 @@ MIT \xA9 ${vars.AUTHOR}
222
161
 
223
162
  import React from 'react';
224
163
  import { ComponentRegistry } from '@object-ui/core';
225
- import { ${pascalCaseName} } from './${pascalCaseName}Impl';
164
+ import { ${vars.pascalName} } from './${vars.pascalName}Impl';
226
165
 
227
- export { ${pascalCaseName} };
228
- export type { ${pascalCaseName}Props } from './${pascalCaseName}Impl';
166
+ export { ${vars.pascalName} };
167
+ export type { ${vars.pascalName}Props } from './${vars.pascalName}Impl';
168
+ export type { ${vars.pascalName}Schema } from './types';
229
169
 
230
170
  // Register component with ComponentRegistry
231
- const ${pascalCaseName}Renderer: React.FC<{ schema: any }> = ({ schema }) => {
232
- return <${pascalCaseName} {...schema} />;
171
+ const ${vars.pascalName}Renderer: React.FC<{ schema: any }> = ({ schema }) => {
172
+ return <${vars.pascalName} {...schema} />;
233
173
  };
234
174
 
235
- ComponentRegistry.register('${cleanName}', ${pascalCaseName}Renderer, {
236
- label: '${pascalCaseName}',
175
+ ComponentRegistry.register('${vars.pluginName}', ${vars.pascalName}Renderer, {
176
+ label: '${vars.pascalName}',
237
177
  category: 'plugin',
238
178
  inputs: [
239
179
  // Define your component inputs here
@@ -243,10 +183,11 @@ ComponentRegistry.register('${cleanName}', ${pascalCaseName}Renderer, {
243
183
  }
244
184
  });
245
185
  `;
246
- fs.writeFileSync(path.join(targetDir, "src", "index.tsx"), indexFile);
247
- const implFile = `/**
186
+ }
187
+ function buildImplFile(vars) {
188
+ return `/**
248
189
  * ObjectUI
249
- * Copyright (c) ${vars.YEAR}-present ObjectStack Inc.
190
+ * Copyright (c) ${vars.year}-present ObjectStack Inc.
250
191
  *
251
192
  * This source code is licensed under the MIT license found in the
252
193
  * LICENSE file in the root directory of this source tree.
@@ -254,49 +195,51 @@ ComponentRegistry.register('${cleanName}', ${pascalCaseName}Renderer, {
254
195
 
255
196
  import React from 'react';
256
197
 
257
- export interface ${pascalCaseName}Props {
198
+ export interface ${vars.pascalName}Props {
258
199
  // Define your props here
259
200
  className?: string;
260
201
  }
261
202
 
262
203
  /**
263
- * ${pascalCaseName} component
204
+ * ${vars.pascalName} component
264
205
  */
265
- export const ${pascalCaseName}: React.FC<${pascalCaseName}Props> = ({ className }) => {
206
+ export const ${vars.pascalName}: React.FC<${vars.pascalName}Props> = ({ className }) => {
266
207
  return (
267
208
  <div className={className}>
268
- <h2>${pascalCaseName} Plugin</h2>
209
+ <h2>${vars.pascalName} Plugin</h2>
269
210
  <p>Implement your plugin logic here.</p>
270
211
  </div>
271
212
  );
272
213
  };
273
214
  `;
274
- fs.writeFileSync(
275
- path.join(targetDir, "src", `${pascalCaseName}Impl.tsx`),
276
- implFile
277
- );
278
- const typesFile = `/**
215
+ }
216
+ function buildTypesFile(vars) {
217
+ return `/**
279
218
  * ObjectUI
280
- * Copyright (c) ${vars.YEAR}-present ObjectStack Inc.
219
+ * Copyright (c) ${vars.year}-present ObjectStack Inc.
281
220
  *
282
221
  * This source code is licensed under the MIT license found in the
283
222
  * LICENSE file in the root directory of this source tree.
284
223
  */
285
224
 
225
+ import type { BaseSchema } from '@object-ui/types';
226
+
286
227
  /**
287
- * Schema definition for ${pascalCaseName}
228
+ * Schema definition for ${vars.pascalName}.
229
+ *
230
+ * This is the contract between a metadata author and the renderer: it is
231
+ * re-exported from \`src/index.tsx\`, which is the package's only entry point.
288
232
  */
289
- export interface ${pascalCaseName}Schema {
290
- type: '${cleanName}';
291
- id?: string;
292
- className?: string;
233
+ export interface ${vars.pascalName}Schema extends BaseSchema {
234
+ type: '${vars.pluginName}';
293
235
  // Add schema properties here
294
236
  }
295
237
  `;
296
- fs.writeFileSync(path.join(targetDir, "src", "types.ts"), typesFile);
297
- const testFile = `/**
238
+ }
239
+ function buildTestFile(vars) {
240
+ return `/**
298
241
  * ObjectUI
299
- * Copyright (c) ${vars.YEAR}-present ObjectStack Inc.
242
+ * Copyright (c) ${vars.year}-present ObjectStack Inc.
300
243
  *
301
244
  * This source code is licensed under the MIT license found in the
302
245
  * LICENSE file in the root directory of this source tree.
@@ -305,26 +248,116 @@ export interface ${pascalCaseName}Schema {
305
248
  import { describe, it, expect } from 'vitest';
306
249
  import { render, screen } from '@testing-library/react';
307
250
  import React from 'react';
308
- import { ${pascalCaseName} } from './${pascalCaseName}Impl';
251
+ import { ${vars.pascalName} } from './${vars.pascalName}Impl';
309
252
 
310
- describe('${pascalCaseName}', () => {
253
+ describe('${vars.pascalName}', () => {
311
254
  it('should render', () => {
312
- render(<${pascalCaseName} />);
313
- expect(screen.getByText('${pascalCaseName} Plugin')).toBeInTheDocument();
255
+ render(<${vars.pascalName} />);
256
+ expect(screen.getByText('${vars.pascalName} Plugin')).toBeInTheDocument();
314
257
  });
315
258
  });
316
259
  `;
317
- fs.writeFileSync(
318
- path.join(targetDir, "src", `${pascalCaseName}Impl.test.tsx`),
319
- testFile
320
- );
260
+ }
261
+ function buildPluginFiles(vars) {
262
+ return {
263
+ "package.json": `${JSON.stringify(buildPackageJson(vars), null, 2)}`,
264
+ "tsconfig.json": `${JSON.stringify(buildTsconfig(), null, 2)}`,
265
+ "vite.config.ts": buildViteConfig(vars),
266
+ [VITEST_SETUP_FILE]: buildVitestSetup(),
267
+ "README.md": buildReadme(vars),
268
+ "src/index.tsx": buildIndexFile(vars),
269
+ [`src/${vars.pascalName}Impl.tsx`]: buildImplFile(vars),
270
+ "src/types.ts": buildTypesFile(vars),
271
+ [`src/${vars.pascalName}Impl.test.tsx`]: buildTestFile(vars)
272
+ };
273
+ }
274
+
275
+ // src/index.ts
276
+ var program = new Command();
277
+ async function createPlugin(pluginName, options = {}) {
278
+ console.log(chalk.blue("\n\u{1F680} ObjectUI Plugin Generator\n"));
279
+ if (!pluginName) {
280
+ const response = await prompts({
281
+ type: "text",
282
+ name: "name",
283
+ message: "Plugin name (without prefix):",
284
+ validate: (value) => {
285
+ if (value.length === 0) return "Plugin name is required";
286
+ if (!/^[a-z0-9-]+$/.test(value)) {
287
+ return "Plugin name must contain only lowercase letters, numbers, and hyphens";
288
+ }
289
+ if (value.includes("..") || value.includes("/") || value.includes("\\")) {
290
+ return 'Plugin name cannot contain path separators or ".."';
291
+ }
292
+ return true;
293
+ }
294
+ });
295
+ pluginName = response.name;
296
+ if (!pluginName) {
297
+ console.log(chalk.red("\n\u274C Plugin name is required"));
298
+ process.exit(1);
299
+ }
300
+ }
301
+ if (!/^[a-z0-9-]+$/.test(pluginName)) {
302
+ console.log(chalk.red("\n\u274C Plugin name must contain only lowercase letters, numbers, and hyphens"));
303
+ process.exit(1);
304
+ }
305
+ if (pluginName.includes("..") || pluginName.includes("/") || pluginName.includes("\\") || path.isAbsolute(pluginName)) {
306
+ console.log(chalk.red("\n\u274C Invalid plugin name: path traversal detected"));
307
+ process.exit(1);
308
+ }
309
+ const cleanName = pluginName.replace(/^plugin-/, "").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
310
+ if (cleanName.length === 0) {
311
+ console.log(chalk.red("\n\u274C Plugin name cannot be empty after sanitization"));
312
+ process.exit(1);
313
+ }
314
+ const fullPackageName = `plugin-${cleanName}`;
315
+ const pascalCaseName = cleanName.split("-").filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
316
+ const answers = await prompts([
317
+ {
318
+ type: "text",
319
+ name: "description",
320
+ message: "Plugin description:",
321
+ initial: options.description || `${pascalCaseName} plugin for ObjectUI`
322
+ },
323
+ {
324
+ type: "text",
325
+ name: "author",
326
+ message: "Author name:",
327
+ initial: options.author || ""
328
+ }
329
+ ]);
330
+ const targetDir = path.join(process.cwd(), "packages", fullPackageName);
331
+ if (fs.existsSync(targetDir)) {
332
+ console.log(chalk.red(`
333
+ \u274C Directory already exists: ${targetDir}`));
334
+ process.exit(1);
335
+ }
336
+ console.log(chalk.green(`
337
+ \u2728 Creating plugin: ${fullPackageName}...
338
+ `));
339
+ fs.mkdirpSync(targetDir);
340
+ const vars = {
341
+ packageName: `@object-ui/${fullPackageName}`,
342
+ pluginName: cleanName,
343
+ pascalName: pascalCaseName,
344
+ description: answers.description,
345
+ author: answers.author,
346
+ version: "0.1.0",
347
+ year: (/* @__PURE__ */ new Date()).getFullYear()
348
+ };
349
+ for (const [relativePath, contents] of Object.entries(buildPluginFiles(vars))) {
350
+ const filePath = path.join(targetDir, relativePath);
351
+ fs.mkdirpSync(path.dirname(filePath));
352
+ fs.writeFileSync(filePath, contents);
353
+ }
321
354
  console.log(chalk.green("\u2705 Plugin created successfully!\n"));
322
355
  console.log(chalk.blue("Next steps:\n"));
323
356
  console.log(chalk.gray(` cd packages/${fullPackageName}`));
324
357
  console.log(chalk.gray(" pnpm install"));
325
358
  console.log(chalk.gray(" pnpm build\n"));
326
359
  console.log(chalk.blue("To use the plugin:\n"));
327
- console.log(chalk.gray(` import { ${pascalCaseName} } from '${vars.PACKAGE_NAME}';
360
+ console.log(chalk.gray(` import { ${pascalCaseName} } from '${vars.packageName}';
328
361
  `));
329
362
  }
330
363
  program.name("create-plugin").description("Create a new ObjectUI plugin").argument("[plugin-name]", "Name of the plugin (without plugin- prefix)").option("-d, --description <description>", "Plugin description").option("-a, --author <author>", "Author name").action(createPlugin);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/create-plugin",
3
- "version": "17.3.0",
3
+ "version": "17.6.0",
4
4
  "description": "CLI tool to scaffold ObjectUI plugins",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -9,7 +9,6 @@
9
9
  },
10
10
  "files": [
11
11
  "dist",
12
- "templates",
13
12
  "README.md",
14
13
  "CHANGELOG.md",
15
14
  "LICENSE"
@@ -22,7 +21,7 @@
22
21
  },
23
22
  "devDependencies": {
24
23
  "@types/fs-extra": "^11.0.4",
25
- "@types/node": "^26.1.2",
24
+ "@types/node": "^26.2.0",
26
25
  "@types/prompts": "^2.4.9",
27
26
  "tsup": "^8.5.1",
28
27
  "typescript": "^6.0.3",
@@ -56,7 +55,7 @@
56
55
  },
57
56
  "scripts": {
58
57
  "build": "tsup",
59
- "type-check": "tsc --noEmit",
58
+ "type-check": "tsc --noEmit && tsc -p tsconfig.test.json",
60
59
  "dev": "tsup --watch",
61
60
  "test": "vitest run",
62
61
  "lint": "eslint ."