@object-ui/create-plugin 17.3.0 → 17.5.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 +90 -0
- package/README.md +3 -11
- package/dist/index.js +188 -155
- package/package.json +2 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,95 @@
|
|
|
1
1
|
# @object-ui/create-plugin
|
|
2
2
|
|
|
3
|
+
## 17.5.0
|
|
4
|
+
|
|
5
|
+
## 17.4.0
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- e473b6c: Anchor the scaffold's build-side `devDependencies` to this repo's real toolchain, and pin the whole generated manifest against drift
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
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.
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
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.
|
|
18
|
+
|
|
19
|
+
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.
|
|
20
|
+
|
|
21
|
+
- f4f42b4: create-plugin: make the scaffolded plugin's own test suite runnable
|
|
22
|
+
|
|
23
|
+
The generator wrote an example test importing `@testing-library/react` and
|
|
24
|
+
asserting with `toBeInTheDocument()`, plus a `test: 'vitest run'` script, while
|
|
25
|
+
declaring neither library and giving Vitest no DOM environment — so `pnpm test`
|
|
26
|
+
in a freshly scaffolded plugin failed on the very first run, at import
|
|
27
|
+
resolution.
|
|
28
|
+
|
|
29
|
+
The generated `package.json` now declares `@testing-library/react`,
|
|
30
|
+
`@testing-library/jest-dom` and `jsdom` (each range copied from this
|
|
31
|
+
monorepo's own manifest), the generated `vite.config.ts` gains a `test` block
|
|
32
|
+
with `environment: 'jsdom'`, `globals: true` and `setupFiles`, and a
|
|
33
|
+
`vitest.setup.ts` registering the jest-dom matchers is written alongside it.
|
|
34
|
+
The templates moved to `src/templates.ts` so the generated artifacts can be
|
|
35
|
+
pinned by unit tests without executing the CLI.
|
|
36
|
+
|
|
37
|
+
- c852682: Remove the scaffold's unused pinned icon dependency, and make its generated schema interface reachable
|
|
38
|
+
|
|
39
|
+
Two declared-but-unreachable artifacts in the generated plugin, both on the blind side of
|
|
40
|
+
the import gate objectui#3733 added — that gate rejects an import nothing declares, and
|
|
41
|
+
never looked for a declaration nothing imports.
|
|
42
|
+
|
|
43
|
+
**The generated `dependencies` no longer pin `lucide-react`** (objectui#3755). It was
|
|
44
|
+
declared at `^0.563.0` and imported by no generated source file, so every freshly
|
|
45
|
+
scaffolded plugin really installed lucide 0.563.x for code that never referenced it — two
|
|
46
|
+
majors behind the 23 in-repo declarations, all `^1.28.0`. Worse than ordinary caret drift:
|
|
47
|
+
a `0.x` caret does not cross minors, so `^0.563.0` is `>=0.563.0 <0.564.0` and could not
|
|
48
|
+
float even within `0.x`. It is removed rather than re-anchored because this repo declares
|
|
49
|
+
an icon library where it imports one — of the 24 manifests mentioning `lucide-react`, 23
|
|
50
|
+
import it, and none pre-declares it for code not yet written. An author who wants icons
|
|
51
|
+
runs `pnpm add lucide-react` and lands the current version by construction, with no anchor
|
|
52
|
+
table to maintain for an unused entry. The generated `dependencies` is now exactly the four
|
|
53
|
+
`workspace:*` platform packages, which cannot drift at all.
|
|
54
|
+
|
|
55
|
+
**The generated `src/index.tsx` now re-exports the schema interface** from `src/types.ts`
|
|
56
|
+
(objectui#3759). The generated `exports` map exposes exactly one key — `.` — so the entry
|
|
57
|
+
is a consumer's only door, and nothing walked through it to `src/types.ts`: no generated
|
|
58
|
+
source imported it, and the deep paths that would have reached it (`<pkg>/types`,
|
|
59
|
+
`<pkg>/dist/types`) are closed by that same map. The interface in it is the plugin's schema
|
|
60
|
+
contract, and it shipped dead — while the generator's own documentation page told authors to
|
|
61
|
+
"export your schema types … make it importable rather than internal". A named type-only
|
|
62
|
+
re-export, matching the four in-repo plugins that ship a `src/types.ts` and the worked
|
|
63
|
+
example in the plugin-development guide.
|
|
64
|
+
|
|
65
|
+
**That interface now extends `BaseSchema` from `@object-ui/types`** instead of re-declaring
|
|
66
|
+
a subset of the base node. Unreachable, a hand-rolled `{ type; id?; className? }` was only
|
|
67
|
+
dead weight; published, it would be a second dialect of a node the protocol already defines,
|
|
68
|
+
silently missing everything else `BaseSchema` carries (`name`, `label`, `visible`, …). Only
|
|
69
|
+
the `type` literal is narrowed locally, the same shape every in-repo plugin uses. This also
|
|
70
|
+
makes the generated `@object-ui/types` dependency a used declaration.
|
|
71
|
+
|
|
72
|
+
Both halves are pinned structurally rather than by string match, so the next dead artifact
|
|
73
|
+
fails a test instead of shipping: no versioned runtime dependency may be declared that no
|
|
74
|
+
generated source imports (`workspace:*` exempt — it cannot drift), and no generated `src/**`
|
|
75
|
+
module may be unreachable from the single entry the `exports` map exposes. Each of those
|
|
76
|
+
gates passes over an empty result on today's templates, so each is paired with a self-test
|
|
77
|
+
that plants the removed defect back and asserts the rule names it — a gate that is green
|
|
78
|
+
because it produces nothing is not a gate.
|
|
79
|
+
|
|
80
|
+
- c29ceff: Move the generator templates' dependency ranges onto the repo's current ones
|
|
81
|
+
|
|
82
|
+
The dependabot wave of 2026-08-10 bumped `lucide-react` to `1.29.0` and `vite`
|
|
83
|
+
to `8.2.1` in this repo's own manifests, but the ranges hard-coded in the
|
|
84
|
+
scaffold generators do not move with it — dependabot does not know the
|
|
85
|
+
templates exist. A project scaffolded by `objectui init` / `objectui dev` or by
|
|
86
|
+
`create-plugin` therefore declared a range the repo itself had already moved
|
|
87
|
+
past.
|
|
88
|
+
|
|
89
|
+
Three ranges are re-anchored: `lucide-react` `^1.28.0` → `^1.29.0` in the routed
|
|
90
|
+
app generator, and `vite` `^8.2.0` → `^8.2.1` in both the shared CLI scaffold
|
|
91
|
+
devDependencies and the create-plugin template.
|
|
92
|
+
|
|
3
93
|
## 17.3.0
|
|
4
94
|
|
|
5
95
|
## 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
|
-
- ✅
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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.0",
|
|
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.
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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(
|
|
161
|
-
name: '${
|
|
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
|
-
|
|
178
|
-
|
|
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.
|
|
118
|
+
${vars.description}
|
|
181
119
|
|
|
182
120
|
## Installation
|
|
183
121
|
|
|
184
122
|
\`\`\`bash
|
|
185
|
-
pnpm add ${vars.
|
|
123
|
+
pnpm add ${vars.packageName}
|
|
186
124
|
\`\`\`
|
|
187
125
|
|
|
188
126
|
## Usage
|
|
189
127
|
|
|
190
128
|
\`\`\`tsx
|
|
191
|
-
import { ${
|
|
129
|
+
import { ${vars.pascalName} } from '${vars.packageName}';
|
|
192
130
|
|
|
193
131
|
// Use the component
|
|
194
|
-
<${
|
|
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.
|
|
150
|
+
MIT \xA9 ${vars.author}
|
|
213
151
|
`;
|
|
214
|
-
|
|
215
|
-
|
|
152
|
+
}
|
|
153
|
+
function buildIndexFile(vars) {
|
|
154
|
+
return `/**
|
|
216
155
|
* ObjectUI
|
|
217
|
-
* Copyright (c) ${vars.
|
|
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 { ${
|
|
164
|
+
import { ${vars.pascalName} } from './${vars.pascalName}Impl';
|
|
226
165
|
|
|
227
|
-
export { ${
|
|
228
|
-
export type { ${
|
|
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 ${
|
|
232
|
-
return <${
|
|
171
|
+
const ${vars.pascalName}Renderer: React.FC<{ schema: any }> = ({ schema }) => {
|
|
172
|
+
return <${vars.pascalName} {...schema} />;
|
|
233
173
|
};
|
|
234
174
|
|
|
235
|
-
ComponentRegistry.register('${
|
|
236
|
-
label: '${
|
|
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
|
-
|
|
247
|
-
|
|
186
|
+
}
|
|
187
|
+
function buildImplFile(vars) {
|
|
188
|
+
return `/**
|
|
248
189
|
* ObjectUI
|
|
249
|
-
* Copyright (c) ${vars.
|
|
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 ${
|
|
198
|
+
export interface ${vars.pascalName}Props {
|
|
258
199
|
// Define your props here
|
|
259
200
|
className?: string;
|
|
260
201
|
}
|
|
261
202
|
|
|
262
203
|
/**
|
|
263
|
-
* ${
|
|
204
|
+
* ${vars.pascalName} component
|
|
264
205
|
*/
|
|
265
|
-
export const ${
|
|
206
|
+
export const ${vars.pascalName}: React.FC<${vars.pascalName}Props> = ({ className }) => {
|
|
266
207
|
return (
|
|
267
208
|
<div className={className}>
|
|
268
|
-
<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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
);
|
|
278
|
-
const typesFile = `/**
|
|
215
|
+
}
|
|
216
|
+
function buildTypesFile(vars) {
|
|
217
|
+
return `/**
|
|
279
218
|
* ObjectUI
|
|
280
|
-
* Copyright (c) ${vars.
|
|
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 ${
|
|
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 ${
|
|
290
|
-
type: '${
|
|
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
|
-
|
|
297
|
-
|
|
238
|
+
}
|
|
239
|
+
function buildTestFile(vars) {
|
|
240
|
+
return `/**
|
|
298
241
|
* ObjectUI
|
|
299
|
-
* Copyright (c) ${vars.
|
|
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 { ${
|
|
251
|
+
import { ${vars.pascalName} } from './${vars.pascalName}Impl';
|
|
309
252
|
|
|
310
|
-
describe('${
|
|
253
|
+
describe('${vars.pascalName}', () => {
|
|
311
254
|
it('should render', () => {
|
|
312
|
-
render(<${
|
|
313
|
-
expect(screen.getByText('${
|
|
255
|
+
render(<${vars.pascalName} />);
|
|
256
|
+
expect(screen.getByText('${vars.pascalName} Plugin')).toBeInTheDocument();
|
|
314
257
|
});
|
|
315
258
|
});
|
|
316
259
|
`;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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.
|
|
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
|
+
"version": "17.5.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"
|
|
@@ -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 ."
|