kubit-forge 0.0.1-canary.85d379a
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 +229 -0
- package/LICENSE +201 -0
- package/README.md +841 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +14294 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +772 -0
- package/dist/index.js +1979 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/README.md +243 -0
- package/dist/templates/config/README.md +94 -0
- package/dist/templates/config/config-templates.ts +138 -0
- package/dist/templates/config/defaults.ts +105 -0
- package/dist/templates/config/index.ts +6 -0
- package/dist/templates/features/.prettierignore.template +28 -0
- package/dist/templates/features/.prettierrc.template +10 -0
- package/dist/templates/features/README.md +111 -0
- package/dist/templates/features/bernova-foundations.ts.template +50 -0
- package/dist/templates/features/bernova-globalStyles.ts.template +33 -0
- package/dist/templates/features/bernova-mediaQueries.ts.template +6 -0
- package/dist/templates/features/bernova-theme.ts.template +1 -0
- package/dist/templates/features/bernova.config.json.template +58 -0
- package/dist/templates/features/changesets-config.json.template +11 -0
- package/dist/templates/features/changesets-readme.md.template +31 -0
- package/dist/templates/features/commitlint.config.js.template +23 -0
- package/dist/templates/features/cypress-support-commands.ts.template +22 -0
- package/dist/templates/features/cypress-support-e2e.ts.template +20 -0
- package/dist/templates/features/cypress.config.ts.template +23 -0
- package/dist/templates/features/eslint.config.js.template +13 -0
- package/dist/templates/features/husky-commit-msg.template +4 -0
- package/dist/templates/features/husky-pre-commit.template +4 -0
- package/dist/templates/features/index.ts +5 -0
- package/dist/templates/features/jest-setup.ts.template +1 -0
- package/dist/templates/features/jest.config.ts.template +23 -0
- package/dist/templates/features/lint-staged.config.js.template +5 -0
- package/dist/templates/features/playwright-example.spec.ts.template +14 -0
- package/dist/templates/features/playwright.config.ts.template +42 -0
- package/dist/templates/features/template-loader.ts +31 -0
- package/dist/templates/features/vitest-setup.ts.template +1 -0
- package/dist/templates/features/vitest.config.ts.template +11 -0
- package/dist/templates/generators/README.md +181 -0
- package/dist/templates/generators/index.ts +9 -0
- package/dist/templates/generators/plugin.ts +245 -0
- package/dist/templates/generators/react-app-bernova.ts +356 -0
- package/dist/templates/generators/react-component.ts +103 -0
- package/dist/templates/generators/react-hook.ts +56 -0
- package/dist/templates/generators/react-kubit-ui.ts +794 -0
- package/dist/templates/generators/react-page.ts +65 -0
- package/dist/templates-physical/kubit-full/.prettierrc +0 -0
- package/dist/templates-physical/kubit-full/README.md +223 -0
- package/dist/templates-physical/kubit-full/bernova.config.json +0 -0
- package/dist/templates-physical/kubit-full/eslint.config.js +0 -0
- package/dist/templates-physical/kubit-full/index.html +17 -0
- package/dist/templates-physical/kubit-full/package.json +53 -0
- package/dist/templates-physical/kubit-full/src/App.css +118 -0
- package/dist/templates-physical/kubit-full/src/App.tsx +75 -0
- package/dist/templates-physical/kubit-full/src/charts/ChartsShowcase.css +0 -0
- package/dist/templates-physical/kubit-full/src/charts/ChartsShowcase.tsx +0 -0
- package/dist/templates-physical/kubit-full/src/components/ComponentsShowcase.css +147 -0
- package/dist/templates-physical/kubit-full/src/components/ComponentsShowcase.tsx +0 -0
- package/dist/templates-physical/kubit-full/src/main.tsx +0 -0
- package/dist/templates-physical/kubit-full/src/styles/index.css +6 -0
- package/dist/templates-physical/kubit-full/src/styles/theme/components/button.ts +0 -0
- package/dist/templates-physical/kubit-full/src/styles/theme/components/card.ts +0 -0
- package/dist/templates-physical/kubit-full/src/styles/theme/components/input.ts +0 -0
- package/dist/templates-physical/kubit-full/src/styles/theme/foundations.ts +0 -0
- package/dist/templates-physical/kubit-full/src/styles/theme/globalStyles.ts +0 -0
- package/dist/templates-physical/kubit-full/src/styles/theme/mediaQueries.ts +0 -0
- package/dist/templates-physical/kubit-full/src/styles/theme/theme.ts +0 -0
- package/dist/templates-physical/kubit-full/src/test/setup.ts +0 -0
- package/dist/templates-physical/kubit-full/tsconfig.json +29 -0
- package/dist/templates-physical/kubit-full/tsconfig.node.json +11 -0
- package/dist/templates-physical/kubit-full/vite.config.ts +24 -0
- package/dist/templates-physical/kubit-full/vitest.config.ts +26 -0
- package/dist/templates-physical/react/.env.example +6 -0
- package/dist/templates-physical/react/.prettierrc +8 -0
- package/dist/templates-physical/react/README.md +81 -0
- package/dist/templates-physical/react/eslint.config.js +7 -0
- package/dist/templates-physical/react/index.html +13 -0
- package/dist/templates-physical/react/package.json +39 -0
- package/dist/templates-physical/react/src/app/App.module.css +39 -0
- package/dist/templates-physical/react/src/app/App.test.tsx +14 -0
- package/dist/templates-physical/react/src/app/App.tsx +17 -0
- package/dist/templates-physical/react/src/main.tsx +10 -0
- package/dist/templates-physical/react/src/styles/index.css +39 -0
- package/dist/templates-physical/react/src/test/setup.ts +57 -0
- package/dist/templates-physical/react/tsconfig.json +25 -0
- package/dist/templates-physical/react/tsconfig.node.json +10 -0
- package/dist/templates-physical/react/vite.config.ts +16 -0
- package/dist/templates-physical/react/vitest.config.ts +22 -0
- package/dist/templates-physical/react-bernova/.eslintrc.cjs +15 -0
- package/dist/templates-physical/react-bernova/.prettierrc +10 -0
- package/dist/templates-physical/react-bernova/README.md +150 -0
- package/dist/templates-physical/react-bernova/bernova.config.json +58 -0
- package/dist/templates-physical/react-bernova/eslint.config.js +7 -0
- package/dist/templates-physical/react-bernova/index.html +21 -0
- package/dist/templates-physical/react-bernova/package.json +43 -0
- package/dist/templates-physical/react-bernova/src/App.css +89 -0
- package/dist/templates-physical/react-bernova/src/App.tsx +51 -0
- package/dist/templates-physical/react-bernova/src/index.css +54 -0
- package/dist/templates-physical/react-bernova/src/main.tsx +10 -0
- package/dist/templates-physical/react-bernova/src/styles/foundations.ts +91 -0
- package/dist/templates-physical/react-bernova/src/styles/globalStyles.ts +118 -0
- package/dist/templates-physical/react-bernova/src/styles/mediaQueries.ts +18 -0
- package/dist/templates-physical/react-bernova/src/styles/theme.ts +94 -0
- package/dist/templates-physical/react-bernova/src/test/setup.ts +9 -0
- package/dist/templates-physical/react-bernova/tsconfig.json +31 -0
- package/dist/templates-physical/react-bernova/tsconfig.node.json +10 -0
- package/dist/templates-physical/react-bernova/vite.config.ts +20 -0
- package/dist/templates-physical/react-bernova/vitest.config.ts +18 -0
- package/dist/templates-physical/vanilla/.env.example +6 -0
- package/dist/templates-physical/vanilla/.prettierrc +8 -0
- package/dist/templates-physical/vanilla/README.md +76 -0
- package/dist/templates-physical/vanilla/eslint.config.js +7 -0
- package/dist/templates-physical/vanilla/index.html +13 -0
- package/dist/templates-physical/vanilla/package.json +28 -0
- package/dist/templates-physical/vanilla/src/app.module.css +39 -0
- package/dist/templates-physical/vanilla/src/app.test.ts +20 -0
- package/dist/templates-physical/vanilla/src/app.ts +31 -0
- package/dist/templates-physical/vanilla/src/main.ts +9 -0
- package/dist/templates-physical/vanilla/src/styles/main.css +39 -0
- package/dist/templates-physical/vanilla/tsconfig.json +23 -0
- package/dist/templates-physical/vanilla/vite.config.ts +14 -0
- package/dist/templates-physical/vanilla/vitest.config.ts +14 -0
- package/package.json +131 -0
- package/templates/kubit-full/.prettierrc +0 -0
- package/templates/kubit-full/README.md +223 -0
- package/templates/kubit-full/bernova.config.json +0 -0
- package/templates/kubit-full/eslint.config.js +0 -0
- package/templates/kubit-full/index.html +17 -0
- package/templates/kubit-full/package.json +53 -0
- package/templates/kubit-full/src/App.css +118 -0
- package/templates/kubit-full/src/App.tsx +75 -0
- package/templates/kubit-full/src/charts/ChartsShowcase.css +0 -0
- package/templates/kubit-full/src/charts/ChartsShowcase.tsx +0 -0
- package/templates/kubit-full/src/components/ComponentsShowcase.css +147 -0
- package/templates/kubit-full/src/components/ComponentsShowcase.tsx +0 -0
- package/templates/kubit-full/src/main.tsx +0 -0
- package/templates/kubit-full/src/styles/index.css +6 -0
- package/templates/kubit-full/src/styles/theme/components/button.ts +0 -0
- package/templates/kubit-full/src/styles/theme/components/card.ts +0 -0
- package/templates/kubit-full/src/styles/theme/components/input.ts +0 -0
- package/templates/kubit-full/src/styles/theme/foundations.ts +0 -0
- package/templates/kubit-full/src/styles/theme/globalStyles.ts +0 -0
- package/templates/kubit-full/src/styles/theme/mediaQueries.ts +0 -0
- package/templates/kubit-full/src/styles/theme/theme.ts +0 -0
- package/templates/kubit-full/src/test/setup.ts +0 -0
- package/templates/kubit-full/tsconfig.json +29 -0
- package/templates/kubit-full/tsconfig.node.json +11 -0
- package/templates/kubit-full/vite.config.ts +24 -0
- package/templates/kubit-full/vitest.config.ts +26 -0
- package/templates/react/.env.example +6 -0
- package/templates/react/.prettierrc +8 -0
- package/templates/react/README.md +81 -0
- package/templates/react/eslint.config.js +7 -0
- package/templates/react/index.html +13 -0
- package/templates/react/package.json +39 -0
- package/templates/react/src/app/App.module.css +39 -0
- package/templates/react/src/app/App.test.tsx +14 -0
- package/templates/react/src/app/App.tsx +17 -0
- package/templates/react/src/main.tsx +10 -0
- package/templates/react/src/styles/index.css +39 -0
- package/templates/react/src/test/setup.ts +57 -0
- package/templates/react/tsconfig.json +25 -0
- package/templates/react/tsconfig.node.json +10 -0
- package/templates/react/vite.config.ts +16 -0
- package/templates/react/vitest.config.ts +22 -0
- package/templates/react-bernova/.eslintrc.cjs +15 -0
- package/templates/react-bernova/.prettierrc +10 -0
- package/templates/react-bernova/README.md +150 -0
- package/templates/react-bernova/bernova.config.json +58 -0
- package/templates/react-bernova/eslint.config.js +7 -0
- package/templates/react-bernova/index.html +21 -0
- package/templates/react-bernova/package.json +43 -0
- package/templates/react-bernova/src/App.css +89 -0
- package/templates/react-bernova/src/App.tsx +51 -0
- package/templates/react-bernova/src/index.css +54 -0
- package/templates/react-bernova/src/main.tsx +10 -0
- package/templates/react-bernova/src/styles/foundations.ts +91 -0
- package/templates/react-bernova/src/styles/globalStyles.ts +118 -0
- package/templates/react-bernova/src/styles/mediaQueries.ts +18 -0
- package/templates/react-bernova/src/styles/theme.ts +94 -0
- package/templates/react-bernova/src/test/setup.ts +9 -0
- package/templates/react-bernova/tsconfig.json +31 -0
- package/templates/react-bernova/tsconfig.node.json +10 -0
- package/templates/react-bernova/vite.config.ts +20 -0
- package/templates/react-bernova/vitest.config.ts +18 -0
- package/templates/vanilla/.env.example +6 -0
- package/templates/vanilla/.prettierrc +8 -0
- package/templates/vanilla/README.md +76 -0
- package/templates/vanilla/eslint.config.js +7 -0
- package/templates/vanilla/index.html +13 -0
- package/templates/vanilla/package.json +28 -0
- package/templates/vanilla/src/app.module.css +39 -0
- package/templates/vanilla/src/app.test.ts +20 -0
- package/templates/vanilla/src/app.ts +31 -0
- package/templates/vanilla/src/main.ts +9 -0
- package/templates/vanilla/src/styles/main.css +39 -0
- package/templates/vanilla/tsconfig.json +23 -0
- package/templates/vanilla/vite.config.ts +14 -0
- package/templates/vanilla/vitest.config.ts +14 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1979 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/plugin-manager.ts
|
|
4
|
+
import { existsSync as existsSync2 } from "fs";
|
|
5
|
+
import { join as join2 } from "path";
|
|
6
|
+
|
|
7
|
+
// src/core/plugin-dependency-resolver.ts
|
|
8
|
+
var PluginDependencyResolver = class {
|
|
9
|
+
logger;
|
|
10
|
+
plugins = /* @__PURE__ */ new Map();
|
|
11
|
+
constructor(logger) {
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Register plugin metadata
|
|
16
|
+
*/
|
|
17
|
+
register(metadata) {
|
|
18
|
+
this.plugins.set(metadata.name, metadata);
|
|
19
|
+
this.logger.debug(`Registered plugin metadata: ${metadata.name}@${metadata.version}`);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve dependencies and determine load order
|
|
23
|
+
*/
|
|
24
|
+
resolve(pluginNames) {
|
|
25
|
+
const errors = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
const missing = [];
|
|
28
|
+
const conflicts = [];
|
|
29
|
+
const graph = this.buildDependencyGraph(pluginNames);
|
|
30
|
+
for (const pluginName of pluginNames) {
|
|
31
|
+
const metadata = this.plugins.get(pluginName);
|
|
32
|
+
if (!metadata) {
|
|
33
|
+
warnings.push(`Plugin '${pluginName}' not found (not installed or not registered)`);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (metadata.dependencies) {
|
|
37
|
+
for (const dep of metadata.dependencies) {
|
|
38
|
+
if (!this.plugins.has(dep.name)) {
|
|
39
|
+
if (dep.optional) {
|
|
40
|
+
warnings.push(
|
|
41
|
+
`Optional dependency '${dep.name}' for '${pluginName}' is not installed`
|
|
42
|
+
);
|
|
43
|
+
} else {
|
|
44
|
+
errors.push(`Required dependency '${dep.name}' for '${pluginName}' is not installed`);
|
|
45
|
+
missing.push(dep);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (metadata.peerDependencies) {
|
|
51
|
+
for (const peer of metadata.peerDependencies) {
|
|
52
|
+
if (!this.plugins.has(peer.name)) {
|
|
53
|
+
warnings.push(`Peer dependency '${peer.name}' for '${pluginName}' is not installed`);
|
|
54
|
+
if (!peer.optional) {
|
|
55
|
+
missing.push(peer);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (metadata.conflicts) {
|
|
61
|
+
for (const conflict of metadata.conflicts) {
|
|
62
|
+
if (this.plugins.has(conflict)) {
|
|
63
|
+
errors.push(`Plugin '${pluginName}' conflicts with '${conflict}'`);
|
|
64
|
+
conflicts.push({ conflictsWith: conflict, plugin: pluginName });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const circular = this.detectCircularDependencies(graph);
|
|
70
|
+
if (circular.length > 0) {
|
|
71
|
+
errors.push(`Circular dependencies detected: ${circular.join(" -> ")}`);
|
|
72
|
+
}
|
|
73
|
+
let loadOrder = [];
|
|
74
|
+
if (errors.length === 0) {
|
|
75
|
+
try {
|
|
76
|
+
loadOrder = this.topologicalSort(graph);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
errors.push(
|
|
79
|
+
`Failed to resolve load order: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
conflicts,
|
|
85
|
+
errors,
|
|
86
|
+
loadOrder,
|
|
87
|
+
missing,
|
|
88
|
+
warnings
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Build dependency graph
|
|
93
|
+
*/
|
|
94
|
+
buildDependencyGraph(pluginNames) {
|
|
95
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
96
|
+
const edges = /* @__PURE__ */ new Map();
|
|
97
|
+
const visited = /* @__PURE__ */ new Set();
|
|
98
|
+
const queue = [...pluginNames];
|
|
99
|
+
while (queue.length > 0) {
|
|
100
|
+
const current = queue.shift();
|
|
101
|
+
if (visited.has(current)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
visited.add(current);
|
|
105
|
+
const metadata = this.plugins.get(current);
|
|
106
|
+
if (!metadata) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
nodes.set(current, metadata);
|
|
110
|
+
if (metadata.dependencies) {
|
|
111
|
+
for (const dep of metadata.dependencies) {
|
|
112
|
+
if (!edges.has(current)) {
|
|
113
|
+
edges.set(current, /* @__PURE__ */ new Set());
|
|
114
|
+
}
|
|
115
|
+
edges.get(current).add(dep.name);
|
|
116
|
+
if (!visited.has(dep.name)) {
|
|
117
|
+
queue.push(dep.name);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (metadata.peerDependencies) {
|
|
122
|
+
for (const peer of metadata.peerDependencies) {
|
|
123
|
+
if (!edges.has(current)) {
|
|
124
|
+
edges.set(current, /* @__PURE__ */ new Set());
|
|
125
|
+
}
|
|
126
|
+
edges.get(current).add(peer.name);
|
|
127
|
+
if (!visited.has(peer.name)) {
|
|
128
|
+
queue.push(peer.name);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return { edges, nodes };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Detect circular dependencies
|
|
137
|
+
*/
|
|
138
|
+
detectCircularDependencies(graph) {
|
|
139
|
+
const visited = /* @__PURE__ */ new Set();
|
|
140
|
+
const recursionStack = /* @__PURE__ */ new Set();
|
|
141
|
+
const path = [];
|
|
142
|
+
const dfs = (node) => {
|
|
143
|
+
visited.add(node);
|
|
144
|
+
recursionStack.add(node);
|
|
145
|
+
path.push(node);
|
|
146
|
+
const dependencies = graph.edges.get(node) || /* @__PURE__ */ new Set();
|
|
147
|
+
for (const dep of dependencies) {
|
|
148
|
+
if (!visited.has(dep)) {
|
|
149
|
+
if (dfs(dep)) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
} else if (recursionStack.has(dep)) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
recursionStack.delete(node);
|
|
157
|
+
path.pop();
|
|
158
|
+
return false;
|
|
159
|
+
};
|
|
160
|
+
for (const node of graph.nodes.keys()) {
|
|
161
|
+
if (!visited.has(node)) {
|
|
162
|
+
if (dfs(node)) {
|
|
163
|
+
return path;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Topological sort to determine load order
|
|
171
|
+
*/
|
|
172
|
+
topologicalSort(graph) {
|
|
173
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
174
|
+
const result = [];
|
|
175
|
+
for (const node of graph.nodes.keys()) {
|
|
176
|
+
inDegree.set(node, 0);
|
|
177
|
+
}
|
|
178
|
+
for (const dependencies of graph.edges.values()) {
|
|
179
|
+
for (const dep of dependencies) {
|
|
180
|
+
if (graph.nodes.has(dep)) {
|
|
181
|
+
inDegree.set(dep, (inDegree.get(dep) || 0) + 1);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const queue = [];
|
|
186
|
+
for (const [node, degree] of inDegree.entries()) {
|
|
187
|
+
if (degree === 0) {
|
|
188
|
+
queue.push(node);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
while (queue.length > 0) {
|
|
192
|
+
const current = queue.shift();
|
|
193
|
+
result.push(current);
|
|
194
|
+
const dependencies = graph.edges.get(current) || /* @__PURE__ */ new Set();
|
|
195
|
+
for (const dep of dependencies) {
|
|
196
|
+
if (!graph.nodes.has(dep)) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const newDegree = (inDegree.get(dep) || 0) - 1;
|
|
200
|
+
inDegree.set(dep, newDegree);
|
|
201
|
+
if (newDegree === 0) {
|
|
202
|
+
queue.push(dep);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (result.length !== graph.nodes.size) {
|
|
207
|
+
throw new Error("Circular dependency detected");
|
|
208
|
+
}
|
|
209
|
+
return result.reverse();
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get dependency tree for a plugin
|
|
213
|
+
*/
|
|
214
|
+
getDependencyTree(pluginName) {
|
|
215
|
+
const metadata = this.plugins.get(pluginName);
|
|
216
|
+
if (!metadata) {
|
|
217
|
+
return { dependencies: [], name: pluginName, version: "unknown" };
|
|
218
|
+
}
|
|
219
|
+
const tree = {
|
|
220
|
+
dependencies: [],
|
|
221
|
+
name: metadata.name,
|
|
222
|
+
version: metadata.version
|
|
223
|
+
};
|
|
224
|
+
if (metadata.dependencies) {
|
|
225
|
+
for (const dep of metadata.dependencies) {
|
|
226
|
+
const depTree = this.getDependencyTree(dep.name);
|
|
227
|
+
tree.dependencies.push(depTree);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return tree;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get all dependencies (flat list)
|
|
234
|
+
*/
|
|
235
|
+
getAllDependencies(pluginName) {
|
|
236
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
237
|
+
const visited = /* @__PURE__ */ new Set();
|
|
238
|
+
const traverse = (name) => {
|
|
239
|
+
if (visited.has(name)) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
visited.add(name);
|
|
243
|
+
const metadata = this.plugins.get(name);
|
|
244
|
+
if (!metadata) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (metadata.dependencies) {
|
|
248
|
+
for (const dep of metadata.dependencies) {
|
|
249
|
+
dependencies.add(dep.name);
|
|
250
|
+
traverse(dep.name);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (metadata.peerDependencies) {
|
|
254
|
+
for (const peer of metadata.peerDependencies) {
|
|
255
|
+
dependencies.add(peer.name);
|
|
256
|
+
traverse(peer.name);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
traverse(pluginName);
|
|
261
|
+
return dependencies;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Check if plugin can be safely unloaded
|
|
265
|
+
*/
|
|
266
|
+
canUnload(pluginName) {
|
|
267
|
+
const blockedBy = [];
|
|
268
|
+
for (const [name] of this.plugins.entries()) {
|
|
269
|
+
if (name === pluginName) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const deps = this.getAllDependencies(name);
|
|
273
|
+
if (deps.has(pluginName)) {
|
|
274
|
+
blockedBy.push(name);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
blockedBy,
|
|
279
|
+
canUnload: blockedBy.length === 0
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get plugins that depend on a specific plugin
|
|
284
|
+
*/
|
|
285
|
+
getDependents(pluginName) {
|
|
286
|
+
const dependents = [];
|
|
287
|
+
for (const [name] of this.plugins.entries()) {
|
|
288
|
+
if (name === pluginName) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const metadata = this.plugins.get(name);
|
|
292
|
+
if (!metadata) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (metadata.dependencies) {
|
|
296
|
+
const hasDep = metadata.dependencies.some((dep) => dep.name === pluginName);
|
|
297
|
+
if (hasDep) {
|
|
298
|
+
dependents.push(name);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (metadata.peerDependencies) {
|
|
302
|
+
const hasPeer = metadata.peerDependencies.some((peer) => peer.name === pluginName);
|
|
303
|
+
if (hasPeer) {
|
|
304
|
+
dependents.push(name);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return dependents;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Clear all registered plugins
|
|
312
|
+
*/
|
|
313
|
+
clear() {
|
|
314
|
+
this.plugins.clear();
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get statistics
|
|
318
|
+
*/
|
|
319
|
+
getStats() {
|
|
320
|
+
let totalDeps = 0;
|
|
321
|
+
let totalPeerDeps = 0;
|
|
322
|
+
let totalConflicts = 0;
|
|
323
|
+
for (const metadata of this.plugins.values()) {
|
|
324
|
+
totalDeps += metadata.dependencies?.length || 0;
|
|
325
|
+
totalPeerDeps += metadata.peerDependencies?.length || 0;
|
|
326
|
+
totalConflicts += metadata.conflicts?.length || 0;
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
totalConflicts,
|
|
330
|
+
totalDependencies: totalDeps,
|
|
331
|
+
totalPeerDependencies: totalPeerDeps,
|
|
332
|
+
totalPlugins: this.plugins.size
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// src/core/plugin-registry.ts
|
|
338
|
+
var PluginRegistry = class {
|
|
339
|
+
logger;
|
|
340
|
+
constructor(logger, _registryUrl = "https://registry.npmjs.org") {
|
|
341
|
+
this.logger = logger;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Search for plugins matching query
|
|
345
|
+
*
|
|
346
|
+
* @param query - Search query
|
|
347
|
+
* @param options - Search options
|
|
348
|
+
* @returns Array of matching plugins
|
|
349
|
+
*/
|
|
350
|
+
async search(query, options = {}) {
|
|
351
|
+
this.logger.debug(`Searching plugins: ${query}`);
|
|
352
|
+
const mockPlugins = [
|
|
353
|
+
{
|
|
354
|
+
author: "Kubit Team",
|
|
355
|
+
capabilities: ["fs:write", "shell:run"],
|
|
356
|
+
category: "tooling",
|
|
357
|
+
description: "Bernova Design System integration for Kubit projects",
|
|
358
|
+
downloads: 15e3,
|
|
359
|
+
kubitVersion: "^2.0.0",
|
|
360
|
+
name: "@kubit/plugin-bernova",
|
|
361
|
+
tags: ["css", "bernova", "design-system", "styling"],
|
|
362
|
+
verified: true,
|
|
363
|
+
version: "1.2.0"
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
author: "Kubit Team",
|
|
367
|
+
capabilities: ["fs:write", "shell:run", "net:localhost"],
|
|
368
|
+
category: "tooling",
|
|
369
|
+
description: "Storybook integration with hot reload",
|
|
370
|
+
downloads: 12e3,
|
|
371
|
+
kubitVersion: "^2.0.0",
|
|
372
|
+
name: "@kubit/plugin-storybook",
|
|
373
|
+
tags: ["storybook", "components", "ui"],
|
|
374
|
+
verified: true,
|
|
375
|
+
version: "2.0.1"
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
author: "Kubit Team",
|
|
379
|
+
capabilities: ["fs:read", "fs:write"],
|
|
380
|
+
category: "utility",
|
|
381
|
+
description: "Internationalization support with i18next",
|
|
382
|
+
downloads: 8e3,
|
|
383
|
+
kubitVersion: "^2.0.0",
|
|
384
|
+
name: "@kubit/plugin-i18n",
|
|
385
|
+
tags: ["i18n", "translation", "localization"],
|
|
386
|
+
verified: true,
|
|
387
|
+
version: "1.5.0"
|
|
388
|
+
}
|
|
389
|
+
];
|
|
390
|
+
let results = mockPlugins.filter(
|
|
391
|
+
(p) => p.name.toLowerCase().includes(query.toLowerCase()) || p.description.toLowerCase().includes(query.toLowerCase()) || p.tags?.some((t) => t.toLowerCase().includes(query.toLowerCase()))
|
|
392
|
+
);
|
|
393
|
+
if (options.verified !== void 0) {
|
|
394
|
+
results = results.filter((p) => p.verified === options.verified);
|
|
395
|
+
}
|
|
396
|
+
if (options.category) {
|
|
397
|
+
results = results.filter((p) => p.category === options.category);
|
|
398
|
+
}
|
|
399
|
+
return results;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Get detailed information about a plugin
|
|
403
|
+
*
|
|
404
|
+
* @param name - Plugin name
|
|
405
|
+
* @returns Plugin information or null if not found
|
|
406
|
+
*/
|
|
407
|
+
async getInfo(name) {
|
|
408
|
+
this.logger.debug(`Fetching plugin info: ${name}`);
|
|
409
|
+
const mockPlugin = {
|
|
410
|
+
author: "Plugin Author",
|
|
411
|
+
capabilities: ["fs:read"],
|
|
412
|
+
category: "utility",
|
|
413
|
+
description: "Plugin description",
|
|
414
|
+
downloads: 1e3,
|
|
415
|
+
homepage: `https://github.com/kubit-ui/${name}`,
|
|
416
|
+
kubitVersion: "^2.0.0",
|
|
417
|
+
name,
|
|
418
|
+
repository: `https://github.com/kubit-ui/${name}`,
|
|
419
|
+
tags: ["utility"],
|
|
420
|
+
updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
421
|
+
verified: false,
|
|
422
|
+
version: "1.0.0"
|
|
423
|
+
};
|
|
424
|
+
return mockPlugin;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Check if a plugin is verified
|
|
428
|
+
*
|
|
429
|
+
* @param name - Plugin name
|
|
430
|
+
* @returns True if verified
|
|
431
|
+
*/
|
|
432
|
+
async isVerified(name) {
|
|
433
|
+
const info = await this.getInfo(name);
|
|
434
|
+
return info?.verified || false;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Get compatible plugins for current kubit-forge version
|
|
438
|
+
*
|
|
439
|
+
* @param kubitVersion - Current kubit-forge version
|
|
440
|
+
* @returns Array of compatible plugins
|
|
441
|
+
*/
|
|
442
|
+
async getCompatible(_kubitVersion) {
|
|
443
|
+
return this.search("", {});
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Get featured/recommended plugins
|
|
447
|
+
*
|
|
448
|
+
* @returns Array of featured plugins
|
|
449
|
+
*/
|
|
450
|
+
async getFeatured() {
|
|
451
|
+
return this.search("", { verified: true });
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
// src/core/plugin-sandbox.ts
|
|
456
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
457
|
+
import { join } from "path";
|
|
458
|
+
var PluginSandbox = class {
|
|
459
|
+
logger;
|
|
460
|
+
options;
|
|
461
|
+
worker;
|
|
462
|
+
isRunning = false;
|
|
463
|
+
constructor(logger, options = {}) {
|
|
464
|
+
this.logger = logger;
|
|
465
|
+
this.options = {
|
|
466
|
+
allowedCapabilities: options.allowedCapabilities || [],
|
|
467
|
+
allowFileSystem: options.allowFileSystem ?? true,
|
|
468
|
+
allowNetwork: options.allowNetwork ?? false,
|
|
469
|
+
allowProcessEnv: options.allowProcessEnv ?? true,
|
|
470
|
+
memoryLimit: options.memoryLimit || 512 * 1024 * 1024,
|
|
471
|
+
// 512MB
|
|
472
|
+
timeout: options.timeout || 3e4
|
|
473
|
+
// 30 seconds
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Execute plugin in sandboxed environment
|
|
478
|
+
*/
|
|
479
|
+
async execute(pluginPath, method, args) {
|
|
480
|
+
if (this.isRunning) {
|
|
481
|
+
throw new Error("Sandbox is already running");
|
|
482
|
+
}
|
|
483
|
+
this.isRunning = true;
|
|
484
|
+
try {
|
|
485
|
+
if (!existsSync(pluginPath)) {
|
|
486
|
+
throw new Error(`Plugin not found: ${pluginPath}`);
|
|
487
|
+
}
|
|
488
|
+
const sandboxContext = this.createSandboxContext();
|
|
489
|
+
const result = await this.executeInWorker(pluginPath, method, args, sandboxContext);
|
|
490
|
+
return result;
|
|
491
|
+
} finally {
|
|
492
|
+
this.isRunning = false;
|
|
493
|
+
this.cleanup();
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Create restricted context for plugin
|
|
498
|
+
*/
|
|
499
|
+
createSandboxContext() {
|
|
500
|
+
const context = {
|
|
501
|
+
console: {
|
|
502
|
+
error: (...args) => this.logger.error(args.join(" ")),
|
|
503
|
+
log: (...args) => this.logger.info(args.join(" ")),
|
|
504
|
+
warn: (...args) => this.logger.warn(args.join(" "))
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
if (this.options.allowProcessEnv) {
|
|
508
|
+
context.process = {
|
|
509
|
+
arch: process.arch,
|
|
510
|
+
cwd: process.cwd,
|
|
511
|
+
env: { ...process.env },
|
|
512
|
+
platform: process.platform
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
if (this.options.allowFileSystem) {
|
|
516
|
+
context.fs = this.createRestrictedFS();
|
|
517
|
+
}
|
|
518
|
+
if (this.options.allowNetwork) {
|
|
519
|
+
context.fetch = fetch;
|
|
520
|
+
}
|
|
521
|
+
return context;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Create restricted filesystem API
|
|
525
|
+
*/
|
|
526
|
+
createRestrictedFS() {
|
|
527
|
+
const cwd = process.cwd();
|
|
528
|
+
return {
|
|
529
|
+
exists: (path) => {
|
|
530
|
+
const fullPath = join(cwd, path);
|
|
531
|
+
if (!fullPath.startsWith(cwd)) {
|
|
532
|
+
throw new Error("Access denied: Path outside project directory");
|
|
533
|
+
}
|
|
534
|
+
return existsSync(fullPath);
|
|
535
|
+
},
|
|
536
|
+
mkdir: (path) => {
|
|
537
|
+
const fullPath = join(cwd, path);
|
|
538
|
+
if (!fullPath.startsWith(cwd)) {
|
|
539
|
+
throw new Error("Access denied: Path outside project directory");
|
|
540
|
+
}
|
|
541
|
+
return mkdirSync(fullPath, { recursive: true });
|
|
542
|
+
},
|
|
543
|
+
readFile: (path) => {
|
|
544
|
+
const fullPath = join(cwd, path);
|
|
545
|
+
if (!fullPath.startsWith(cwd)) {
|
|
546
|
+
throw new Error("Access denied: Path outside project directory");
|
|
547
|
+
}
|
|
548
|
+
return readFileSync(fullPath, "utf-8");
|
|
549
|
+
},
|
|
550
|
+
writeFile: (path, content) => {
|
|
551
|
+
const fullPath = join(cwd, path);
|
|
552
|
+
if (!fullPath.startsWith(cwd)) {
|
|
553
|
+
throw new Error("Access denied: Path outside project directory");
|
|
554
|
+
}
|
|
555
|
+
return writeFileSync(fullPath, content, "utf-8");
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Execute plugin method in worker thread
|
|
561
|
+
*/
|
|
562
|
+
async executeInWorker(pluginPath, method, args, _context) {
|
|
563
|
+
const timeout = setTimeout(() => {
|
|
564
|
+
if (this.worker) {
|
|
565
|
+
this.worker.terminate();
|
|
566
|
+
}
|
|
567
|
+
}, this.options.timeout);
|
|
568
|
+
try {
|
|
569
|
+
const pluginModule = await import(pluginPath);
|
|
570
|
+
const plugin = pluginModule.default || pluginModule;
|
|
571
|
+
const pluginInstance = plugin.default || plugin;
|
|
572
|
+
if (!pluginInstance[method]) {
|
|
573
|
+
clearTimeout(timeout);
|
|
574
|
+
throw new Error(`Method '${method}' not found in plugin`);
|
|
575
|
+
}
|
|
576
|
+
const result = pluginInstance[method](...args);
|
|
577
|
+
if (result instanceof Promise) {
|
|
578
|
+
return result.then((res) => {
|
|
579
|
+
clearTimeout(timeout);
|
|
580
|
+
return res;
|
|
581
|
+
}).catch((err) => {
|
|
582
|
+
clearTimeout(timeout);
|
|
583
|
+
throw err;
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
clearTimeout(timeout);
|
|
587
|
+
return result;
|
|
588
|
+
} catch (error) {
|
|
589
|
+
clearTimeout(timeout);
|
|
590
|
+
throw error;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Check if capability is allowed
|
|
595
|
+
*/
|
|
596
|
+
hasCapability(capability) {
|
|
597
|
+
return this.options.allowedCapabilities.includes(capability);
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Get sandbox statistics
|
|
601
|
+
*/
|
|
602
|
+
getStats() {
|
|
603
|
+
return {
|
|
604
|
+
allowedCapabilities: this.options.allowedCapabilities,
|
|
605
|
+
allowFileSystem: this.options.allowFileSystem,
|
|
606
|
+
allowNetwork: this.options.allowNetwork,
|
|
607
|
+
isRunning: this.isRunning,
|
|
608
|
+
memoryLimit: this.options.memoryLimit,
|
|
609
|
+
timeout: this.options.timeout
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Cleanup resources
|
|
614
|
+
*/
|
|
615
|
+
cleanup() {
|
|
616
|
+
if (this.worker) {
|
|
617
|
+
this.worker.terminate();
|
|
618
|
+
this.worker = void 0;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Terminate sandbox
|
|
623
|
+
*/
|
|
624
|
+
terminate() {
|
|
625
|
+
this.cleanup();
|
|
626
|
+
this.isRunning = false;
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
var SandboxManager = class {
|
|
630
|
+
sandboxes = /* @__PURE__ */ new Map();
|
|
631
|
+
logger;
|
|
632
|
+
constructor(logger) {
|
|
633
|
+
this.logger = logger;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Create sandbox for plugin
|
|
637
|
+
*/
|
|
638
|
+
createSandbox(pluginName, options = {}) {
|
|
639
|
+
if (this.sandboxes.has(pluginName)) {
|
|
640
|
+
throw new Error(`Sandbox already exists for plugin '${pluginName}'`);
|
|
641
|
+
}
|
|
642
|
+
const sandbox = new PluginSandbox(this.logger, options);
|
|
643
|
+
this.sandboxes.set(pluginName, sandbox);
|
|
644
|
+
this.logger.debug(`Created sandbox for plugin '${pluginName}'`);
|
|
645
|
+
return sandbox;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Get sandbox for plugin
|
|
649
|
+
*/
|
|
650
|
+
getSandbox(pluginName) {
|
|
651
|
+
return this.sandboxes.get(pluginName);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Remove sandbox
|
|
655
|
+
*/
|
|
656
|
+
removeSandbox(pluginName) {
|
|
657
|
+
const sandbox = this.sandboxes.get(pluginName);
|
|
658
|
+
if (sandbox) {
|
|
659
|
+
sandbox.terminate();
|
|
660
|
+
this.sandboxes.delete(pluginName);
|
|
661
|
+
this.logger.debug(`Removed sandbox for plugin '${pluginName}'`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Terminate all sandboxes
|
|
666
|
+
*/
|
|
667
|
+
terminateAll() {
|
|
668
|
+
for (const [pluginName, sandbox] of this.sandboxes.entries()) {
|
|
669
|
+
sandbox.terminate();
|
|
670
|
+
this.logger.debug(`Terminated sandbox for plugin '${pluginName}'`);
|
|
671
|
+
}
|
|
672
|
+
this.sandboxes.clear();
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Get statistics for all sandboxes
|
|
676
|
+
*/
|
|
677
|
+
getStats() {
|
|
678
|
+
const stats = {};
|
|
679
|
+
for (const [pluginName, sandbox] of this.sandboxes.entries()) {
|
|
680
|
+
stats[pluginName] = sandbox.getStats();
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
sandboxes: stats,
|
|
684
|
+
totalSandboxes: this.sandboxes.size
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
// src/core/plugin-validator.ts
|
|
690
|
+
var PluginValidator = class {
|
|
691
|
+
/**
|
|
692
|
+
* Validate a plugin
|
|
693
|
+
*/
|
|
694
|
+
validate(plugin) {
|
|
695
|
+
const errors = [];
|
|
696
|
+
const warnings = [];
|
|
697
|
+
const securityIssues = [];
|
|
698
|
+
if (!plugin.name) {
|
|
699
|
+
errors.push("Plugin must have a name");
|
|
700
|
+
} else {
|
|
701
|
+
if (!/^[@a-z0-9-]+\/[a-z0-9-]+$|^[a-z0-9-]+$/.test(plugin.name)) {
|
|
702
|
+
errors.push(
|
|
703
|
+
'Plugin name must be in format "name" or "@scope/name" with lowercase letters, numbers, and hyphens'
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (!plugin.version) {
|
|
708
|
+
errors.push("Plugin must have a version");
|
|
709
|
+
} else {
|
|
710
|
+
if (!/^\d+\.\d+\.\d+(-[a-z0-9.-]+)?(\+[a-z0-9.-]+)?$/i.test(plugin.version)) {
|
|
711
|
+
errors.push("Plugin version must follow semver format (e.g., 1.0.0)");
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (plugin.capabilities) {
|
|
715
|
+
const validCapabilities = [
|
|
716
|
+
"fs:read",
|
|
717
|
+
"fs:write",
|
|
718
|
+
"shell:run",
|
|
719
|
+
"net:localhost",
|
|
720
|
+
"process:env"
|
|
721
|
+
];
|
|
722
|
+
for (const cap of plugin.capabilities) {
|
|
723
|
+
if (!validCapabilities.includes(cap)) {
|
|
724
|
+
errors.push(`Invalid capability: ${cap}`);
|
|
725
|
+
}
|
|
726
|
+
if (cap === "fs:write") {
|
|
727
|
+
securityIssues.push("Plugin requests file system write access");
|
|
728
|
+
}
|
|
729
|
+
if (cap === "shell:run") {
|
|
730
|
+
securityIssues.push("Plugin requests shell command execution");
|
|
731
|
+
}
|
|
732
|
+
if (cap === "process:env") {
|
|
733
|
+
warnings.push("Plugin requests access to environment variables");
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
const validHooks = [
|
|
738
|
+
"onLoad",
|
|
739
|
+
"onConfigLoaded",
|
|
740
|
+
"onProjectDetected",
|
|
741
|
+
"beforeCommand",
|
|
742
|
+
"afterCommand",
|
|
743
|
+
"beforeTask",
|
|
744
|
+
"afterTask"
|
|
745
|
+
];
|
|
746
|
+
for (const hook of validHooks) {
|
|
747
|
+
if (hook in plugin && typeof plugin[hook] !== "function") {
|
|
748
|
+
errors.push(`Hook '${hook}' must be a function`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
const validRegistrations = [
|
|
752
|
+
"registerCommands",
|
|
753
|
+
"registerTasks",
|
|
754
|
+
"registerGenerators",
|
|
755
|
+
"registerProviders"
|
|
756
|
+
];
|
|
757
|
+
for (const method of validRegistrations) {
|
|
758
|
+
if (method in plugin && typeof plugin[method] !== "function") {
|
|
759
|
+
errors.push(`Registration method '${method}' must be a function`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (!plugin.capabilities || plugin.capabilities.length === 0) {
|
|
763
|
+
warnings.push("Plugin does not declare any capabilities");
|
|
764
|
+
}
|
|
765
|
+
return {
|
|
766
|
+
errors,
|
|
767
|
+
securityIssues,
|
|
768
|
+
valid: errors.length === 0,
|
|
769
|
+
warnings
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Check if plugin requires user approval based on capabilities
|
|
774
|
+
*/
|
|
775
|
+
requiresApproval(plugin) {
|
|
776
|
+
if (!plugin.capabilities) {
|
|
777
|
+
return false;
|
|
778
|
+
}
|
|
779
|
+
const dangerousCapabilities = ["fs:write", "shell:run"];
|
|
780
|
+
return plugin.capabilities.some((cap) => dangerousCapabilities.includes(cap));
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Get security level of plugin
|
|
784
|
+
*/
|
|
785
|
+
getSecurityLevel(plugin) {
|
|
786
|
+
if (!plugin.capabilities || plugin.capabilities.length === 0) {
|
|
787
|
+
return "safe";
|
|
788
|
+
}
|
|
789
|
+
const dangerousCapabilities = ["fs:write", "shell:run"];
|
|
790
|
+
const hasDangerous = plugin.capabilities.some((cap) => dangerousCapabilities.includes(cap));
|
|
791
|
+
if (hasDangerous) {
|
|
792
|
+
return "dangerous";
|
|
793
|
+
}
|
|
794
|
+
const moderateCapabilities = ["process:env", "net:localhost"];
|
|
795
|
+
const hasModerate = plugin.capabilities.some((cap) => moderateCapabilities.includes(cap));
|
|
796
|
+
if (hasModerate) {
|
|
797
|
+
return "moderate";
|
|
798
|
+
}
|
|
799
|
+
return "safe";
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Validate command registration
|
|
803
|
+
*/
|
|
804
|
+
validateCommand(command) {
|
|
805
|
+
const errors = [];
|
|
806
|
+
if (!command.name || typeof command.name !== "string") {
|
|
807
|
+
errors.push("Command must have a name (string)");
|
|
808
|
+
}
|
|
809
|
+
if (!command.description || typeof command.description !== "string") {
|
|
810
|
+
errors.push("Command must have a description (string)");
|
|
811
|
+
}
|
|
812
|
+
if (!command.action || typeof command.action !== "function") {
|
|
813
|
+
errors.push("Command must have an action (function)");
|
|
814
|
+
}
|
|
815
|
+
if (command.options && !Array.isArray(command.options)) {
|
|
816
|
+
errors.push("Command options must be an array");
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
errors,
|
|
820
|
+
valid: errors.length === 0
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Validate task registration
|
|
825
|
+
*/
|
|
826
|
+
validateTask(task) {
|
|
827
|
+
const errors = [];
|
|
828
|
+
if (!task.name || typeof task.name !== "string") {
|
|
829
|
+
errors.push("Task must have a name (string)");
|
|
830
|
+
}
|
|
831
|
+
if (!task.description || typeof task.description !== "string") {
|
|
832
|
+
errors.push("Task must have a description (string)");
|
|
833
|
+
}
|
|
834
|
+
if (!task.run || typeof task.run !== "function") {
|
|
835
|
+
errors.push("Task must have a run function");
|
|
836
|
+
}
|
|
837
|
+
return {
|
|
838
|
+
errors,
|
|
839
|
+
valid: errors.length === 0
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Validate generator registration
|
|
844
|
+
*/
|
|
845
|
+
validateGenerator(generator) {
|
|
846
|
+
const errors = [];
|
|
847
|
+
if (!generator.kind || typeof generator.kind !== "string") {
|
|
848
|
+
errors.push("Generator must have a kind (string)");
|
|
849
|
+
}
|
|
850
|
+
if (!generator.description || typeof generator.description !== "string") {
|
|
851
|
+
errors.push("Generator must have a description (string)");
|
|
852
|
+
}
|
|
853
|
+
if (!generator.generate || typeof generator.generate !== "function") {
|
|
854
|
+
errors.push("Generator must have a generate function");
|
|
855
|
+
}
|
|
856
|
+
return {
|
|
857
|
+
errors,
|
|
858
|
+
valid: errors.length === 0
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Validate provider registration
|
|
863
|
+
*/
|
|
864
|
+
validateProvider(provider) {
|
|
865
|
+
const errors = [];
|
|
866
|
+
if (!provider.command || typeof provider.command !== "string") {
|
|
867
|
+
errors.push("Provider must have a command (string)");
|
|
868
|
+
}
|
|
869
|
+
if (typeof provider.priority !== "number") {
|
|
870
|
+
errors.push("Provider must have a priority (number)");
|
|
871
|
+
}
|
|
872
|
+
if (!provider.canHandle || typeof provider.canHandle !== "function") {
|
|
873
|
+
errors.push("Provider must have a canHandle function");
|
|
874
|
+
}
|
|
875
|
+
if (!provider.handler || typeof provider.handler !== "function") {
|
|
876
|
+
errors.push("Provider must have a handler function");
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
errors,
|
|
880
|
+
valid: errors.length === 0
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
// src/core/plugin-version-manager.ts
|
|
886
|
+
import { satisfies, valid, coerce, gt, lt, eq } from "semver";
|
|
887
|
+
var PluginVersionManager = class {
|
|
888
|
+
logger;
|
|
889
|
+
installedVersions = /* @__PURE__ */ new Map();
|
|
890
|
+
requiredVersions = /* @__PURE__ */ new Map();
|
|
891
|
+
latestVersions = /* @__PURE__ */ new Map();
|
|
892
|
+
constructor(logger) {
|
|
893
|
+
this.logger = logger;
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Register installed plugin version
|
|
897
|
+
*/
|
|
898
|
+
registerInstalled(pluginName, version) {
|
|
899
|
+
const normalized = this.normalizeVersion(version);
|
|
900
|
+
if (!normalized) {
|
|
901
|
+
this.logger.warn(`Invalid version for plugin '${pluginName}': ${version}`);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
this.installedVersions.set(pluginName, normalized);
|
|
905
|
+
this.logger.debug(`Registered plugin '${pluginName}' version ${normalized}`);
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Set required version for plugin
|
|
909
|
+
*/
|
|
910
|
+
setRequired(pluginName, versionRange) {
|
|
911
|
+
this.requiredVersions.set(pluginName, versionRange);
|
|
912
|
+
this.logger.debug(`Set required version for '${pluginName}': ${versionRange}`);
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Set latest available version
|
|
916
|
+
*/
|
|
917
|
+
setLatest(pluginName, version) {
|
|
918
|
+
const normalized = this.normalizeVersion(version);
|
|
919
|
+
if (normalized) {
|
|
920
|
+
this.latestVersions.set(pluginName, normalized);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Check if plugin version satisfies requirement
|
|
925
|
+
*/
|
|
926
|
+
satisfies(pluginName, versionRange) {
|
|
927
|
+
const installed = this.installedVersions.get(pluginName);
|
|
928
|
+
if (!installed) {
|
|
929
|
+
return false;
|
|
930
|
+
}
|
|
931
|
+
try {
|
|
932
|
+
return satisfies(installed, versionRange);
|
|
933
|
+
} catch {
|
|
934
|
+
this.logger.warn(`Invalid version range for '${pluginName}': ${versionRange}`);
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Check compatibility of plugin
|
|
940
|
+
*/
|
|
941
|
+
checkCompatibility(pluginName) {
|
|
942
|
+
const issues = [];
|
|
943
|
+
const warnings = [];
|
|
944
|
+
const recommendations = [];
|
|
945
|
+
const installed = this.installedVersions.get(pluginName);
|
|
946
|
+
const required = this.requiredVersions.get(pluginName);
|
|
947
|
+
const latest = this.latestVersions.get(pluginName);
|
|
948
|
+
if (!installed) {
|
|
949
|
+
issues.push(`Plugin '${pluginName}' is not installed`);
|
|
950
|
+
return { compatible: false, issues, recommendations, warnings };
|
|
951
|
+
}
|
|
952
|
+
if (required && !this.satisfies(pluginName, required)) {
|
|
953
|
+
issues.push(
|
|
954
|
+
`Plugin '${pluginName}' version ${installed} does not satisfy requirement ${required}`
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
if (latest && gt(latest, installed)) {
|
|
958
|
+
warnings.push(`Plugin '${pluginName}' has an update available: ${installed} \u2192 ${latest}`);
|
|
959
|
+
recommendations.push(`Run: kubit-forge plugin:update ${pluginName}`);
|
|
960
|
+
}
|
|
961
|
+
if (latest) {
|
|
962
|
+
const installedMajor = parseInt(installed.split(".")[0], 10);
|
|
963
|
+
const latestMajor = parseInt(latest.split(".")[0], 10);
|
|
964
|
+
if (latestMajor > installedMajor + 1) {
|
|
965
|
+
warnings.push(
|
|
966
|
+
`Plugin '${pluginName}' is ${latestMajor - installedMajor} major versions behind`
|
|
967
|
+
);
|
|
968
|
+
recommendations.push(`Consider upgrading to version ${latest}`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return {
|
|
972
|
+
compatible: issues.length === 0,
|
|
973
|
+
issues,
|
|
974
|
+
recommendations,
|
|
975
|
+
warnings
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Get plugin version info
|
|
980
|
+
*/
|
|
981
|
+
getVersionInfo(pluginName) {
|
|
982
|
+
const installed = this.installedVersions.get(pluginName);
|
|
983
|
+
const required = this.requiredVersions.get(pluginName);
|
|
984
|
+
const latest = this.latestVersions.get(pluginName);
|
|
985
|
+
const compatible = required ? this.satisfies(pluginName, required) : true;
|
|
986
|
+
const updateAvailable = installed && latest ? gt(latest, installed) : false;
|
|
987
|
+
return {
|
|
988
|
+
compatible,
|
|
989
|
+
installedVersion: installed,
|
|
990
|
+
latestVersion: latest,
|
|
991
|
+
name: pluginName,
|
|
992
|
+
requiredVersion: required,
|
|
993
|
+
updateAvailable,
|
|
994
|
+
version: installed || "not-installed"
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Get all plugin versions
|
|
999
|
+
*/
|
|
1000
|
+
getAllVersions() {
|
|
1001
|
+
const allPlugins = /* @__PURE__ */ new Set([
|
|
1002
|
+
...this.installedVersions.keys(),
|
|
1003
|
+
...this.requiredVersions.keys(),
|
|
1004
|
+
...this.latestVersions.keys()
|
|
1005
|
+
]);
|
|
1006
|
+
return Array.from(allPlugins).map((plugin) => this.getVersionInfo(plugin));
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Check if update is available
|
|
1010
|
+
*/
|
|
1011
|
+
hasUpdate(pluginName) {
|
|
1012
|
+
const installed = this.installedVersions.get(pluginName);
|
|
1013
|
+
const latest = this.latestVersions.get(pluginName);
|
|
1014
|
+
if (!installed || !latest) {
|
|
1015
|
+
return false;
|
|
1016
|
+
}
|
|
1017
|
+
return gt(latest, installed);
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Get plugins with updates available
|
|
1021
|
+
*/
|
|
1022
|
+
getUpdatablePlugins() {
|
|
1023
|
+
return this.getAllVersions().filter((p) => p.updateAvailable);
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Get incompatible plugins
|
|
1027
|
+
*/
|
|
1028
|
+
getIncompatiblePlugins() {
|
|
1029
|
+
return this.getAllVersions().filter((p) => !p.compatible);
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Resolve version conflicts
|
|
1033
|
+
*/
|
|
1034
|
+
resolveConflicts(constraints) {
|
|
1035
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1036
|
+
const conflicts = [];
|
|
1037
|
+
const byPlugin = /* @__PURE__ */ new Map();
|
|
1038
|
+
for (const constraint of constraints) {
|
|
1039
|
+
if (!byPlugin.has(constraint.plugin)) {
|
|
1040
|
+
byPlugin.set(constraint.plugin, []);
|
|
1041
|
+
}
|
|
1042
|
+
byPlugin.get(constraint.plugin).push(constraint);
|
|
1043
|
+
}
|
|
1044
|
+
for (const [plugin, pluginConstraints] of byPlugin.entries()) {
|
|
1045
|
+
if (pluginConstraints.length === 1) {
|
|
1046
|
+
resolved.set(plugin, pluginConstraints[0].version);
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
const versions = pluginConstraints.map((c) => c.version);
|
|
1050
|
+
const compatible = this.findCompatibleVersion(versions);
|
|
1051
|
+
if (compatible) {
|
|
1052
|
+
resolved.set(plugin, compatible);
|
|
1053
|
+
} else {
|
|
1054
|
+
conflicts.push(
|
|
1055
|
+
`Cannot resolve version for '${plugin}': ${versions.join(", ")} are incompatible`
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return { conflicts, resolved };
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Find compatible version from multiple constraints
|
|
1063
|
+
*/
|
|
1064
|
+
findCompatibleVersion(versionRanges) {
|
|
1065
|
+
const candidates = [];
|
|
1066
|
+
for (const range of versionRanges) {
|
|
1067
|
+
const normalized = this.normalizeVersion(range);
|
|
1068
|
+
if (normalized && candidates.length === 0) {
|
|
1069
|
+
candidates.push(normalized);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
for (const candidate of candidates) {
|
|
1073
|
+
const satisfiesAll = versionRanges.every((range) => {
|
|
1074
|
+
try {
|
|
1075
|
+
return satisfies(candidate, range);
|
|
1076
|
+
} catch {
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
if (satisfiesAll) {
|
|
1081
|
+
return candidate;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Normalize version string
|
|
1088
|
+
*/
|
|
1089
|
+
normalizeVersion(version) {
|
|
1090
|
+
if (valid(version)) {
|
|
1091
|
+
return version;
|
|
1092
|
+
}
|
|
1093
|
+
const coerced = coerce(version);
|
|
1094
|
+
if (coerced) {
|
|
1095
|
+
return coerced.version;
|
|
1096
|
+
}
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Compare two versions
|
|
1101
|
+
*/
|
|
1102
|
+
compare(version1, version2) {
|
|
1103
|
+
const v1 = this.normalizeVersion(version1);
|
|
1104
|
+
const v2 = this.normalizeVersion(version2);
|
|
1105
|
+
if (!v1 || !v2) {
|
|
1106
|
+
return 0;
|
|
1107
|
+
}
|
|
1108
|
+
if (gt(v1, v2)) {
|
|
1109
|
+
return 1;
|
|
1110
|
+
}
|
|
1111
|
+
if (lt(v1, v2)) {
|
|
1112
|
+
return -1;
|
|
1113
|
+
}
|
|
1114
|
+
return 0;
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Check if version is equal
|
|
1118
|
+
*/
|
|
1119
|
+
isEqual(version1, version2) {
|
|
1120
|
+
const v1 = this.normalizeVersion(version1);
|
|
1121
|
+
const v2 = this.normalizeVersion(version2);
|
|
1122
|
+
if (!v1 || !v2) {
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1125
|
+
return eq(v1, v2);
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Get version statistics
|
|
1129
|
+
*/
|
|
1130
|
+
getStats() {
|
|
1131
|
+
const all = this.getAllVersions();
|
|
1132
|
+
return {
|
|
1133
|
+
compatible: all.filter((p) => p.compatible).length,
|
|
1134
|
+
incompatible: all.filter((p) => !p.compatible).length,
|
|
1135
|
+
installed: all.filter((p) => p.installedVersion).length,
|
|
1136
|
+
totalPlugins: all.length,
|
|
1137
|
+
updatable: all.filter((p) => p.updateAvailable).length
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Clear all version data
|
|
1142
|
+
*/
|
|
1143
|
+
clear() {
|
|
1144
|
+
this.installedVersions.clear();
|
|
1145
|
+
this.requiredVersions.clear();
|
|
1146
|
+
this.latestVersions.clear();
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
// src/core/plugin-manager.ts
|
|
1151
|
+
var PluginManager = class {
|
|
1152
|
+
plugins = /* @__PURE__ */ new Map();
|
|
1153
|
+
commands = /* @__PURE__ */ new Map();
|
|
1154
|
+
tasks = /* @__PURE__ */ new Map();
|
|
1155
|
+
generators = /* @__PURE__ */ new Map();
|
|
1156
|
+
providers = /* @__PURE__ */ new Map();
|
|
1157
|
+
logger;
|
|
1158
|
+
cwd;
|
|
1159
|
+
validator;
|
|
1160
|
+
registry;
|
|
1161
|
+
sandboxManager;
|
|
1162
|
+
versionManager;
|
|
1163
|
+
dependencyResolver;
|
|
1164
|
+
constructor(logger, cwd) {
|
|
1165
|
+
this.logger = logger;
|
|
1166
|
+
this.cwd = cwd;
|
|
1167
|
+
this.validator = new PluginValidator();
|
|
1168
|
+
this.registry = new PluginRegistry(logger);
|
|
1169
|
+
this.sandboxManager = new SandboxManager(logger);
|
|
1170
|
+
this.versionManager = new PluginVersionManager(logger);
|
|
1171
|
+
this.dependencyResolver = new PluginDependencyResolver(logger);
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Load plugins from config or auto-detect
|
|
1175
|
+
*/
|
|
1176
|
+
async loadPlugins(pluginNames, ctx, options = {}) {
|
|
1177
|
+
if (options.checkDependencies !== false) {
|
|
1178
|
+
const resolution = this.dependencyResolver.resolve(pluginNames);
|
|
1179
|
+
if (resolution.errors.length > 0) {
|
|
1180
|
+
this.logger.error("Plugin dependency resolution failed:");
|
|
1181
|
+
resolution.errors.forEach((err) => this.logger.error(` - ${err}`));
|
|
1182
|
+
throw new Error("Cannot load plugins due to dependency errors");
|
|
1183
|
+
}
|
|
1184
|
+
if (resolution.warnings.length > 0) {
|
|
1185
|
+
resolution.warnings.forEach((warn) => this.logger.warn(warn));
|
|
1186
|
+
}
|
|
1187
|
+
pluginNames = resolution.loadOrder;
|
|
1188
|
+
this.logger.debug(`Plugin load order: ${pluginNames.join(" -> ")}`);
|
|
1189
|
+
}
|
|
1190
|
+
for (const pluginName of pluginNames) {
|
|
1191
|
+
try {
|
|
1192
|
+
await this.loadPlugin(pluginName, ctx, options);
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
this.logger.warn(
|
|
1195
|
+
`Failed to load plugin '${pluginName}': ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Load a single plugin
|
|
1202
|
+
*/
|
|
1203
|
+
async loadPlugin(pluginName, ctx, options = {}) {
|
|
1204
|
+
let pluginModule;
|
|
1205
|
+
try {
|
|
1206
|
+
const pluginPath = join2(this.cwd, "node_modules", pluginName);
|
|
1207
|
+
if (existsSync2(pluginPath)) {
|
|
1208
|
+
pluginModule = await import(pluginPath);
|
|
1209
|
+
} else {
|
|
1210
|
+
pluginModule = await import(pluginName);
|
|
1211
|
+
}
|
|
1212
|
+
} catch {
|
|
1213
|
+
throw new Error(`Cannot resolve plugin '${pluginName}'`);
|
|
1214
|
+
}
|
|
1215
|
+
const plugin = pluginModule.default || pluginModule;
|
|
1216
|
+
if (!plugin.name || !plugin.version) {
|
|
1217
|
+
throw new Error(`Invalid plugin '${pluginName}': missing name or version`);
|
|
1218
|
+
}
|
|
1219
|
+
this.versionManager.registerInstalled(plugin.name, plugin.version);
|
|
1220
|
+
const versionCheck = this.versionManager.checkCompatibility(plugin.name);
|
|
1221
|
+
if (!versionCheck.compatible) {
|
|
1222
|
+
this.logger.error(`Plugin '${plugin.name}' version compatibility check failed:`);
|
|
1223
|
+
versionCheck.issues.forEach((issue) => this.logger.error(` - ${issue}`));
|
|
1224
|
+
throw new Error(`Plugin '${plugin.name}' is not compatible`);
|
|
1225
|
+
}
|
|
1226
|
+
if (versionCheck.warnings.length > 0) {
|
|
1227
|
+
versionCheck.warnings.forEach((warn) => this.logger.warn(warn));
|
|
1228
|
+
}
|
|
1229
|
+
if (options.validate !== false) {
|
|
1230
|
+
const validation = this.validator.validate(plugin);
|
|
1231
|
+
if (!validation.valid) {
|
|
1232
|
+
throw new Error(
|
|
1233
|
+
`Plugin '${plugin.name}' validation failed:
|
|
1234
|
+
${validation.errors.join("\n")}`
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
if (validation.warnings.length > 0) {
|
|
1238
|
+
for (const warning of validation.warnings) {
|
|
1239
|
+
this.logger.warn(`Plugin '${plugin.name}': ${warning}`);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
if (!options.skipSecurityCheck && validation.securityIssues.length > 0) {
|
|
1243
|
+
this.logger.warn(`
|
|
1244
|
+
Security notice for plugin '${plugin.name}':`);
|
|
1245
|
+
for (const issue of validation.securityIssues) {
|
|
1246
|
+
this.logger.warn(` - ${issue}`);
|
|
1247
|
+
}
|
|
1248
|
+
const securityLevel = this.validator.getSecurityLevel(plugin);
|
|
1249
|
+
if (securityLevel === "dangerous") {
|
|
1250
|
+
this.logger.warn(
|
|
1251
|
+
"\nThis plugin requires dangerous permissions. Only install if you trust the source."
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
this.plugins.set(plugin.name, plugin);
|
|
1257
|
+
if (plugin.onLoad) {
|
|
1258
|
+
await plugin.onLoad(ctx);
|
|
1259
|
+
}
|
|
1260
|
+
if (plugin.registerCommands) {
|
|
1261
|
+
const commands = plugin.registerCommands();
|
|
1262
|
+
for (const cmd of commands) {
|
|
1263
|
+
this.commands.set(cmd.name, cmd);
|
|
1264
|
+
this.logger.debug(`Registered command '${cmd.name}' from plugin '${plugin.name}'`);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
if (plugin.registerTasks) {
|
|
1268
|
+
const tasks = plugin.registerTasks();
|
|
1269
|
+
for (const task of tasks) {
|
|
1270
|
+
this.tasks.set(task.name, task);
|
|
1271
|
+
this.logger.debug(`Registered task '${task.name}' from plugin '${plugin.name}'`);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
if (plugin.registerGenerators) {
|
|
1275
|
+
const generators = plugin.registerGenerators();
|
|
1276
|
+
for (const gen of generators) {
|
|
1277
|
+
this.generators.set(gen.kind, gen);
|
|
1278
|
+
this.logger.debug(`Registered generator '${gen.kind}' from plugin '${plugin.name}'`);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
if (plugin.registerProviders) {
|
|
1282
|
+
const providers = plugin.registerProviders();
|
|
1283
|
+
for (const provider of providers) {
|
|
1284
|
+
if (!this.providers.has(provider.command)) {
|
|
1285
|
+
this.providers.set(provider.command, []);
|
|
1286
|
+
}
|
|
1287
|
+
this.providers.get(provider.command).push(provider);
|
|
1288
|
+
this.logger.debug(
|
|
1289
|
+
`Registered provider for '${provider.command}' from plugin '${plugin.name}'`
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
this.logger.debug(`Loaded plugin '${plugin.name}' v${plugin.version}`);
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Get all loaded plugins
|
|
1297
|
+
*/
|
|
1298
|
+
getPlugins() {
|
|
1299
|
+
return Array.from(this.plugins.values());
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Get command by name
|
|
1303
|
+
*/
|
|
1304
|
+
getCommand(name) {
|
|
1305
|
+
return this.commands.get(name);
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Get all commands
|
|
1309
|
+
*/
|
|
1310
|
+
getCommands() {
|
|
1311
|
+
return Array.from(this.commands.values());
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Get task by name
|
|
1315
|
+
*/
|
|
1316
|
+
getTask(name) {
|
|
1317
|
+
return this.tasks.get(name);
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Get all tasks
|
|
1321
|
+
*/
|
|
1322
|
+
getTasks() {
|
|
1323
|
+
return Array.from(this.tasks.values());
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Get generator by kind
|
|
1327
|
+
*/
|
|
1328
|
+
getGenerator(kind) {
|
|
1329
|
+
return this.generators.get(kind);
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Get all generators
|
|
1333
|
+
*/
|
|
1334
|
+
getGenerators() {
|
|
1335
|
+
return Array.from(this.generators.values());
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Get best provider for a command
|
|
1339
|
+
*/
|
|
1340
|
+
getProvider(command, ctx) {
|
|
1341
|
+
const providers = this.providers.get(command) || [];
|
|
1342
|
+
const capable = providers.filter((p) => p.canHandle(ctx));
|
|
1343
|
+
if (capable.length === 0) {
|
|
1344
|
+
return void 0;
|
|
1345
|
+
}
|
|
1346
|
+
capable.sort((a, b) => b.priority - a.priority);
|
|
1347
|
+
return capable[0];
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Call beforeCommand hooks
|
|
1351
|
+
*/
|
|
1352
|
+
async callBeforeCommand(command, ctx) {
|
|
1353
|
+
for (const plugin of this.plugins.values()) {
|
|
1354
|
+
if (plugin.beforeCommand) {
|
|
1355
|
+
await plugin.beforeCommand(command, ctx);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Call afterCommand hooks
|
|
1361
|
+
*/
|
|
1362
|
+
async callAfterCommand(command, ctx, result) {
|
|
1363
|
+
for (const plugin of this.plugins.values()) {
|
|
1364
|
+
if (plugin.afterCommand) {
|
|
1365
|
+
await plugin.afterCommand(command, ctx, result);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Call beforeTask hooks
|
|
1371
|
+
*/
|
|
1372
|
+
async callBeforeTask(task, ctx) {
|
|
1373
|
+
for (const plugin of this.plugins.values()) {
|
|
1374
|
+
if (plugin.beforeTask) {
|
|
1375
|
+
await plugin.beforeTask(task, ctx);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Call afterTask hooks
|
|
1381
|
+
*/
|
|
1382
|
+
async callAfterTask(task, ctx, result) {
|
|
1383
|
+
for (const plugin of this.plugins.values()) {
|
|
1384
|
+
if (plugin.afterTask) {
|
|
1385
|
+
await plugin.afterTask(task, ctx, result);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Search for plugins in registry
|
|
1391
|
+
*/
|
|
1392
|
+
async searchPlugins(query, options) {
|
|
1393
|
+
return this.registry.search(query, options);
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Get plugin info from registry
|
|
1397
|
+
*/
|
|
1398
|
+
async getPluginInfo(name) {
|
|
1399
|
+
return this.registry.getInfo(name);
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Get featured plugins
|
|
1403
|
+
*/
|
|
1404
|
+
async getFeaturedPlugins() {
|
|
1405
|
+
return this.registry.getFeatured();
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Check if plugin is loaded
|
|
1409
|
+
*/
|
|
1410
|
+
isLoaded(pluginName) {
|
|
1411
|
+
return this.plugins.has(pluginName);
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Get loaded plugin by name
|
|
1415
|
+
*/
|
|
1416
|
+
getPlugin(pluginName) {
|
|
1417
|
+
return this.plugins.get(pluginName);
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Unload a plugin
|
|
1421
|
+
*/
|
|
1422
|
+
async unloadPlugin(pluginName) {
|
|
1423
|
+
const plugin = this.plugins.get(pluginName);
|
|
1424
|
+
if (!plugin) {
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
if (plugin.registerCommands) {
|
|
1428
|
+
const commands = plugin.registerCommands();
|
|
1429
|
+
for (const cmd of commands) {
|
|
1430
|
+
this.commands.delete(cmd.name);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
if (plugin.registerTasks) {
|
|
1434
|
+
const tasks = plugin.registerTasks();
|
|
1435
|
+
for (const task of tasks) {
|
|
1436
|
+
this.tasks.delete(task.name);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
if (plugin.registerGenerators) {
|
|
1440
|
+
const generators = plugin.registerGenerators();
|
|
1441
|
+
for (const gen of generators) {
|
|
1442
|
+
this.generators.delete(gen.kind);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
if (plugin.registerProviders) {
|
|
1446
|
+
const providers = plugin.registerProviders();
|
|
1447
|
+
for (const provider of providers) {
|
|
1448
|
+
const list = this.providers.get(provider.command);
|
|
1449
|
+
if (list) {
|
|
1450
|
+
const index = list.indexOf(provider);
|
|
1451
|
+
if (index !== -1) {
|
|
1452
|
+
list.splice(index, 1);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
this.plugins.delete(pluginName);
|
|
1458
|
+
this.logger.debug(`Unloaded plugin '${pluginName}'`);
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Get plugin statistics
|
|
1462
|
+
*/
|
|
1463
|
+
getStats() {
|
|
1464
|
+
return {
|
|
1465
|
+
commands: this.commands.size,
|
|
1466
|
+
dependencies: this.dependencyResolver.getStats(),
|
|
1467
|
+
generators: this.generators.size,
|
|
1468
|
+
plugins: this.plugins.size,
|
|
1469
|
+
providers: Array.from(this.providers.values()).reduce((sum, list) => sum + list.length, 0),
|
|
1470
|
+
tasks: this.tasks.size,
|
|
1471
|
+
versions: this.versionManager.getStats()
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Get version manager
|
|
1476
|
+
*/
|
|
1477
|
+
getVersionManager() {
|
|
1478
|
+
return this.versionManager;
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Get dependency resolver
|
|
1482
|
+
*/
|
|
1483
|
+
getDependencyResolver() {
|
|
1484
|
+
return this.dependencyResolver;
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Get sandbox manager
|
|
1488
|
+
*/
|
|
1489
|
+
getSandboxManager() {
|
|
1490
|
+
return this.sandboxManager;
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Register plugin metadata for dependency resolution
|
|
1494
|
+
*/
|
|
1495
|
+
registerPluginMetadata(metadata) {
|
|
1496
|
+
this.dependencyResolver.register(metadata);
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Check plugin updates
|
|
1500
|
+
*/
|
|
1501
|
+
async checkUpdates() {
|
|
1502
|
+
const updates = [];
|
|
1503
|
+
for (const plugin of this.plugins.values()) {
|
|
1504
|
+
try {
|
|
1505
|
+
const info = await this.registry.getInfo(plugin.name);
|
|
1506
|
+
if (info && info.version) {
|
|
1507
|
+
this.versionManager.setLatest(plugin.name, info.version);
|
|
1508
|
+
if (this.versionManager.hasUpdate(plugin.name)) {
|
|
1509
|
+
updates.push({
|
|
1510
|
+
current: plugin.version,
|
|
1511
|
+
latest: info.version,
|
|
1512
|
+
name: plugin.name
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
} catch {
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
return updates;
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
|
|
1523
|
+
// src/types/index.ts
|
|
1524
|
+
import { z } from "zod";
|
|
1525
|
+
var KubitConfigSchema = z.object({
|
|
1526
|
+
commands: z.object({
|
|
1527
|
+
buildProvider: z.string().optional(),
|
|
1528
|
+
devProvider: z.string().optional()
|
|
1529
|
+
}).optional(),
|
|
1530
|
+
env: z.object({
|
|
1531
|
+
required: z.array(z.string()).optional(),
|
|
1532
|
+
schema: z.string().optional()
|
|
1533
|
+
}).optional(),
|
|
1534
|
+
kubit: z.object({
|
|
1535
|
+
cliVersion: z.string().optional()
|
|
1536
|
+
}).optional(),
|
|
1537
|
+
paths: z.object({
|
|
1538
|
+
dist: z.string().default("dist"),
|
|
1539
|
+
envExample: z.string().default(".env.example"),
|
|
1540
|
+
envLocal: z.string().default(".env.local"),
|
|
1541
|
+
src: z.string().default("src")
|
|
1542
|
+
}).optional(),
|
|
1543
|
+
plugins: z.object({
|
|
1544
|
+
enabled: z.array(z.string()).optional()
|
|
1545
|
+
}).optional(),
|
|
1546
|
+
project: z.object({
|
|
1547
|
+
devPort: z.number().default(5173),
|
|
1548
|
+
language: z.enum(["ts", "js"]).default("ts"),
|
|
1549
|
+
name: z.string(),
|
|
1550
|
+
nodeVersion: z.string().default("20"),
|
|
1551
|
+
packageManager: z.enum(["pnpm", "npm", "yarn"]).default("pnpm"),
|
|
1552
|
+
stack: z.enum(["react", "vanilla"]),
|
|
1553
|
+
type: z.enum(["web", "library"]).default("web")
|
|
1554
|
+
}),
|
|
1555
|
+
quality: z.object({
|
|
1556
|
+
format: z.boolean().default(true),
|
|
1557
|
+
lint: z.boolean().default(true),
|
|
1558
|
+
typecheck: z.boolean().default(true),
|
|
1559
|
+
unitTest: z.boolean().default(true)
|
|
1560
|
+
}).optional()
|
|
1561
|
+
});
|
|
1562
|
+
|
|
1563
|
+
// src/utils/config-loader.ts
|
|
1564
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
1565
|
+
import { join as join3 } from "path";
|
|
1566
|
+
import { parse as parseToml } from "smol-toml";
|
|
1567
|
+
|
|
1568
|
+
// src/templates/config/defaults.ts
|
|
1569
|
+
var PROJECT_DEFAULTS = {
|
|
1570
|
+
nodeVersion: "20",
|
|
1571
|
+
devPort: 5173,
|
|
1572
|
+
packageManager: "pnpm"
|
|
1573
|
+
};
|
|
1574
|
+
var PATHS_DEFAULTS = {
|
|
1575
|
+
src: "src",
|
|
1576
|
+
dist: "dist",
|
|
1577
|
+
envExample: ".env.example",
|
|
1578
|
+
envLocal: ".env.local"
|
|
1579
|
+
};
|
|
1580
|
+
var QUALITY_DEFAULTS = {
|
|
1581
|
+
lint: true,
|
|
1582
|
+
format: true,
|
|
1583
|
+
typecheck: true,
|
|
1584
|
+
unitTest: true
|
|
1585
|
+
};
|
|
1586
|
+
var QUALITY_MINIMAL = {
|
|
1587
|
+
lint: false,
|
|
1588
|
+
format: false,
|
|
1589
|
+
typecheck: false,
|
|
1590
|
+
unitTest: false
|
|
1591
|
+
};
|
|
1592
|
+
var PLUGIN_DEFAULTS = {
|
|
1593
|
+
enabled: []
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1596
|
+
// src/templates/config/config-templates.ts
|
|
1597
|
+
var ConfigTemplateLoader = class {
|
|
1598
|
+
/**
|
|
1599
|
+
* Build a complete default configuration
|
|
1600
|
+
*/
|
|
1601
|
+
static buildDefault(options = {}) {
|
|
1602
|
+
return {
|
|
1603
|
+
kubit: {
|
|
1604
|
+
cliVersion: "^4.0.0"
|
|
1605
|
+
},
|
|
1606
|
+
project: this.buildProjectConfig(options),
|
|
1607
|
+
paths: this.buildPathsConfig(),
|
|
1608
|
+
quality: this.buildQualityConfig(options),
|
|
1609
|
+
plugins: this.buildPluginsConfig()
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Build project configuration with defaults
|
|
1614
|
+
*/
|
|
1615
|
+
static buildProjectConfig(options = {}) {
|
|
1616
|
+
return {
|
|
1617
|
+
name: options.name || "my-project",
|
|
1618
|
+
type: options.type || "web",
|
|
1619
|
+
stack: options.stack || "vanilla",
|
|
1620
|
+
language: options.language || "js",
|
|
1621
|
+
packageManager: options.packageManager || PROJECT_DEFAULTS.packageManager,
|
|
1622
|
+
nodeVersion: options.nodeVersion || PROJECT_DEFAULTS.nodeVersion,
|
|
1623
|
+
devPort: options.devPort || PROJECT_DEFAULTS.devPort
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Build paths configuration with defaults
|
|
1628
|
+
*/
|
|
1629
|
+
static buildPathsConfig(overrides) {
|
|
1630
|
+
return {
|
|
1631
|
+
...PATHS_DEFAULTS,
|
|
1632
|
+
...overrides
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Build quality configuration with defaults
|
|
1637
|
+
*/
|
|
1638
|
+
static buildQualityConfig(options = {}) {
|
|
1639
|
+
const baseDefaults = options.minimal ? QUALITY_MINIMAL : QUALITY_DEFAULTS;
|
|
1640
|
+
return {
|
|
1641
|
+
...baseDefaults,
|
|
1642
|
+
// Auto-adjust typecheck based on language
|
|
1643
|
+
typecheck: options.language === "ts" ? baseDefaults.typecheck : false
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Build plugins configuration with defaults
|
|
1648
|
+
*/
|
|
1649
|
+
static buildPluginsConfig(plugins) {
|
|
1650
|
+
return {
|
|
1651
|
+
enabled: plugins || PLUGIN_DEFAULTS.enabled
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Get project defaults
|
|
1656
|
+
*/
|
|
1657
|
+
static getProjectDefaults() {
|
|
1658
|
+
return { ...PROJECT_DEFAULTS };
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Get paths defaults
|
|
1662
|
+
*/
|
|
1663
|
+
static getPathsDefaults() {
|
|
1664
|
+
return { ...PATHS_DEFAULTS };
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Get quality defaults
|
|
1668
|
+
*/
|
|
1669
|
+
static getQualityDefaults(minimal = false) {
|
|
1670
|
+
return minimal ? { ...QUALITY_MINIMAL } : { ...QUALITY_DEFAULTS };
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Get plugin defaults
|
|
1674
|
+
*/
|
|
1675
|
+
static getPluginDefaults() {
|
|
1676
|
+
return { ...PLUGIN_DEFAULTS };
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Merge partial config with defaults
|
|
1680
|
+
*/
|
|
1681
|
+
static mergeWithDefaults(partial, options = {}) {
|
|
1682
|
+
const defaults = this.buildDefault(options);
|
|
1683
|
+
return {
|
|
1684
|
+
kubit: partial.kubit || defaults.kubit,
|
|
1685
|
+
project: { ...defaults.project, ...partial.project || {} },
|
|
1686
|
+
paths: { ...defaults.paths, ...partial.paths || {} },
|
|
1687
|
+
quality: { ...defaults.quality, ...partial.quality || {} },
|
|
1688
|
+
plugins: { ...defaults.plugins, ...partial.plugins || {} }
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1692
|
+
|
|
1693
|
+
// src/utils/config-loader.ts
|
|
1694
|
+
var ConfigLoader = class {
|
|
1695
|
+
cwd;
|
|
1696
|
+
constructor(cwd = process.cwd()) {
|
|
1697
|
+
this.cwd = cwd;
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Load kubit config from project
|
|
1701
|
+
* Supports: kubit.config.toml, kubit.config.json
|
|
1702
|
+
*/
|
|
1703
|
+
async load(configPath) {
|
|
1704
|
+
const configFile = configPath ? join3(this.cwd, configPath) : this.findConfigFile();
|
|
1705
|
+
if (!configFile) {
|
|
1706
|
+
return null;
|
|
1707
|
+
}
|
|
1708
|
+
try {
|
|
1709
|
+
const content = readFileSync2(configFile, "utf-8");
|
|
1710
|
+
let rawConfig;
|
|
1711
|
+
if (configFile.endsWith(".toml")) {
|
|
1712
|
+
rawConfig = parseToml(content);
|
|
1713
|
+
} else if (configFile.endsWith(".json")) {
|
|
1714
|
+
rawConfig = JSON.parse(content);
|
|
1715
|
+
} else {
|
|
1716
|
+
throw new Error(`Unsupported config format: ${configFile}`);
|
|
1717
|
+
}
|
|
1718
|
+
const config = KubitConfigSchema.parse(rawConfig);
|
|
1719
|
+
return config;
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
throw new Error(
|
|
1722
|
+
`Failed to load config from ${configFile}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Auto-detect project configuration when no config file exists
|
|
1728
|
+
*/
|
|
1729
|
+
async autoDetect() {
|
|
1730
|
+
const packageJsonPath = join3(this.cwd, "package.json");
|
|
1731
|
+
if (!existsSync3(packageJsonPath)) {
|
|
1732
|
+
const projectName = this.cwd.split("/").pop() || "my-project";
|
|
1733
|
+
return ConfigTemplateLoader.buildDefault({
|
|
1734
|
+
minimal: true,
|
|
1735
|
+
name: projectName
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
|
|
1739
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
1740
|
+
let stack = "vanilla";
|
|
1741
|
+
if (deps.react) {
|
|
1742
|
+
stack = "react";
|
|
1743
|
+
}
|
|
1744
|
+
const language = existsSync3(join3(this.cwd, "tsconfig.json")) ? "ts" : "js";
|
|
1745
|
+
let packageManager = "npm";
|
|
1746
|
+
if (existsSync3(join3(this.cwd, "pnpm-lock.yaml"))) {
|
|
1747
|
+
packageManager = "pnpm";
|
|
1748
|
+
} else if (existsSync3(join3(this.cwd, "yarn.lock"))) {
|
|
1749
|
+
packageManager = "yarn";
|
|
1750
|
+
} else if (existsSync3(join3(this.cwd, "package-lock.json"))) {
|
|
1751
|
+
packageManager = "npm";
|
|
1752
|
+
}
|
|
1753
|
+
const config = ConfigTemplateLoader.buildDefault({
|
|
1754
|
+
language,
|
|
1755
|
+
name: packageJson.name || "my-app",
|
|
1756
|
+
packageManager,
|
|
1757
|
+
stack,
|
|
1758
|
+
type: "web"
|
|
1759
|
+
});
|
|
1760
|
+
config.quality = {
|
|
1761
|
+
...config.quality,
|
|
1762
|
+
format: true,
|
|
1763
|
+
lint: true,
|
|
1764
|
+
typecheck: language === "ts",
|
|
1765
|
+
unitTest: !!deps.vitest || !!deps.jest
|
|
1766
|
+
};
|
|
1767
|
+
return config;
|
|
1768
|
+
}
|
|
1769
|
+
findConfigFile() {
|
|
1770
|
+
const candidates = ["kubit.config.toml", "kubit.config.json"];
|
|
1771
|
+
for (const candidate of candidates) {
|
|
1772
|
+
const path = join3(this.cwd, candidate);
|
|
1773
|
+
if (existsSync3(path)) {
|
|
1774
|
+
return path;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
return null;
|
|
1778
|
+
}
|
|
1779
|
+
};
|
|
1780
|
+
|
|
1781
|
+
// src/utils/logger.ts
|
|
1782
|
+
import chalk2 from "chalk";
|
|
1783
|
+
|
|
1784
|
+
// src/utils/theme.ts
|
|
1785
|
+
import chalk from "chalk";
|
|
1786
|
+
var colors = {
|
|
1787
|
+
bold: chalk.bold,
|
|
1788
|
+
boolean: chalk.hex("#06b6d4"),
|
|
1789
|
+
// Cyan
|
|
1790
|
+
code: chalk.hex("#e879f9"),
|
|
1791
|
+
dim: chalk.dim,
|
|
1792
|
+
error: chalk.hex("#ef4444"),
|
|
1793
|
+
// Red
|
|
1794
|
+
info: chalk.hex("#3b82f6"),
|
|
1795
|
+
// Blue
|
|
1796
|
+
keyword: chalk.hex("#a855f7"),
|
|
1797
|
+
// Purple
|
|
1798
|
+
// Special
|
|
1799
|
+
link: chalk.hex("#3b82f6").underline,
|
|
1800
|
+
// UI colors
|
|
1801
|
+
muted: chalk.hex("#6b7280"),
|
|
1802
|
+
// Gray
|
|
1803
|
+
number: chalk.hex("#f97316"),
|
|
1804
|
+
// Orange
|
|
1805
|
+
// Primary brand colors
|
|
1806
|
+
primary: chalk.hex("#6366f1"),
|
|
1807
|
+
// Indigo - main brand color
|
|
1808
|
+
secondary: chalk.hex("#8b5cf6"),
|
|
1809
|
+
// Purple - accent color
|
|
1810
|
+
// Syntax highlighting
|
|
1811
|
+
string: chalk.hex("#22c55e"),
|
|
1812
|
+
// Green
|
|
1813
|
+
// Semantic colors
|
|
1814
|
+
success: chalk.hex("#10b981"),
|
|
1815
|
+
// Green
|
|
1816
|
+
warning: chalk.hex("#f59e0b")
|
|
1817
|
+
// Amber
|
|
1818
|
+
};
|
|
1819
|
+
var symbols = {
|
|
1820
|
+
bullet: "\u2022",
|
|
1821
|
+
ellipsis: "...",
|
|
1822
|
+
error: "\u2717",
|
|
1823
|
+
info: "i",
|
|
1824
|
+
line: "\u2500",
|
|
1825
|
+
pointer: "\u203A",
|
|
1826
|
+
step: "\u2192",
|
|
1827
|
+
success: "\u2713",
|
|
1828
|
+
warning: "!"
|
|
1829
|
+
};
|
|
1830
|
+
|
|
1831
|
+
// src/utils/logger.ts
|
|
1832
|
+
var ConsoleLogger = class {
|
|
1833
|
+
useColor;
|
|
1834
|
+
isVerbose;
|
|
1835
|
+
isQuiet;
|
|
1836
|
+
useJson;
|
|
1837
|
+
constructor(options = {}) {
|
|
1838
|
+
this.useColor = !options.noColor;
|
|
1839
|
+
this.isVerbose = options.verbose || false;
|
|
1840
|
+
this.isQuiet = options.quiet || false;
|
|
1841
|
+
this.useJson = options.json || false;
|
|
1842
|
+
}
|
|
1843
|
+
info(message) {
|
|
1844
|
+
if (this.isQuiet) {
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
if (this.useJson) {
|
|
1848
|
+
console.log(JSON.stringify({ level: "info", message }));
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
console.log(this.useColor ? colors.info(symbols.info) : "i", message);
|
|
1852
|
+
}
|
|
1853
|
+
success(message) {
|
|
1854
|
+
if (this.isQuiet) {
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
if (this.useJson) {
|
|
1858
|
+
console.log(JSON.stringify({ level: "success", message }));
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
console.log(this.useColor ? colors.success(symbols.success) : symbols.success, message);
|
|
1862
|
+
}
|
|
1863
|
+
warn(message) {
|
|
1864
|
+
if (this.useJson) {
|
|
1865
|
+
console.log(JSON.stringify({ level: "warning", message }));
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
console.warn(this.useColor ? colors.warning(symbols.warning) : symbols.warning, message);
|
|
1869
|
+
}
|
|
1870
|
+
error(message, error) {
|
|
1871
|
+
if (this.useJson) {
|
|
1872
|
+
console.log(
|
|
1873
|
+
JSON.stringify({
|
|
1874
|
+
error: error?.message,
|
|
1875
|
+
level: "error",
|
|
1876
|
+
message,
|
|
1877
|
+
stack: error?.stack
|
|
1878
|
+
})
|
|
1879
|
+
);
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1882
|
+
console.error(this.useColor ? colors.error(symbols.error) : symbols.error, message);
|
|
1883
|
+
if (error && this.isVerbose) {
|
|
1884
|
+
console.error(chalk2.gray(error.stack || error.message));
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
debug(message) {
|
|
1888
|
+
if (!this.isVerbose) {
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
if (this.useJson) {
|
|
1892
|
+
console.log(JSON.stringify({ level: "debug", message }));
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
console.log(this.useColor ? colors.muted(symbols.pointer) : symbols.pointer, message);
|
|
1896
|
+
}
|
|
1897
|
+
step(message) {
|
|
1898
|
+
if (this.isQuiet) {
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
if (this.useJson) {
|
|
1902
|
+
console.log(JSON.stringify({ level: "step", message }));
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
console.log(this.useColor ? colors.primary(symbols.step) : symbols.step, message);
|
|
1906
|
+
}
|
|
1907
|
+
json(data) {
|
|
1908
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
|
|
1912
|
+
// src/utils/task-runner.ts
|
|
1913
|
+
import { execa } from "execa";
|
|
1914
|
+
var DefaultTaskRunner = class {
|
|
1915
|
+
tasks = /* @__PURE__ */ new Map();
|
|
1916
|
+
logger;
|
|
1917
|
+
constructor(logger) {
|
|
1918
|
+
this.logger = logger;
|
|
1919
|
+
}
|
|
1920
|
+
registerTask(name, task) {
|
|
1921
|
+
this.tasks.set(name, task);
|
|
1922
|
+
}
|
|
1923
|
+
async run(command, args = [], options = {}) {
|
|
1924
|
+
const startTime = Date.now();
|
|
1925
|
+
const cwd = options.cwd || process.cwd();
|
|
1926
|
+
try {
|
|
1927
|
+
if (!options.silent) {
|
|
1928
|
+
this.logger.debug(`Running: ${command} ${args.join(" ")}`);
|
|
1929
|
+
}
|
|
1930
|
+
const result = await execa(command, args, {
|
|
1931
|
+
all: true,
|
|
1932
|
+
cwd,
|
|
1933
|
+
env: { ...process.env, ...options.env },
|
|
1934
|
+
reject: false
|
|
1935
|
+
});
|
|
1936
|
+
const duration = Date.now() - startTime;
|
|
1937
|
+
if (result.exitCode === 0) {
|
|
1938
|
+
return {
|
|
1939
|
+
duration,
|
|
1940
|
+
exitCode: result.exitCode,
|
|
1941
|
+
output: options.captureOutput ? result.all : void 0,
|
|
1942
|
+
status: "ok"
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
return {
|
|
1946
|
+
duration,
|
|
1947
|
+
exitCode: result.exitCode,
|
|
1948
|
+
message: `Command failed with exit code ${result.exitCode}`,
|
|
1949
|
+
output: result.all,
|
|
1950
|
+
status: "error"
|
|
1951
|
+
};
|
|
1952
|
+
} catch (error) {
|
|
1953
|
+
const duration = Date.now() - startTime;
|
|
1954
|
+
return {
|
|
1955
|
+
duration,
|
|
1956
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
1957
|
+
status: "error"
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
async runTask(taskName) {
|
|
1962
|
+
const task = this.tasks.get(taskName);
|
|
1963
|
+
if (!task) {
|
|
1964
|
+
return {
|
|
1965
|
+
message: `Task '${taskName}' not found`,
|
|
1966
|
+
status: "error"
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
return await task();
|
|
1970
|
+
}
|
|
1971
|
+
};
|
|
1972
|
+
export {
|
|
1973
|
+
ConfigLoader,
|
|
1974
|
+
ConsoleLogger,
|
|
1975
|
+
DefaultTaskRunner,
|
|
1976
|
+
KubitConfigSchema,
|
|
1977
|
+
PluginManager
|
|
1978
|
+
};
|
|
1979
|
+
//# sourceMappingURL=index.js.map
|