@makerkit/cli 1.3.17 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -54
- package/dist/index.js +1590 -703
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +1508 -0
- package/dist/mcp.js.map +1 -0
- package/package.json +22 -18
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,1508 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/mcp.ts
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import { config } from "dotenv";
|
|
8
|
+
import { z } from "zod/v3";
|
|
9
|
+
|
|
10
|
+
// src/plugins-model.ts
|
|
11
|
+
import { join as join2 } from "path";
|
|
12
|
+
import fs2 from "fs-extra";
|
|
13
|
+
import invariant from "tiny-invariant";
|
|
14
|
+
|
|
15
|
+
// src/utils/plugins-cache.ts
|
|
16
|
+
import { join } from "path";
|
|
17
|
+
import { homedir } from "os";
|
|
18
|
+
import fs from "fs-extra";
|
|
19
|
+
var CACHE_DIR = join(homedir(), ".makerkit");
|
|
20
|
+
var CACHE_FILE = join(CACHE_DIR, "plugins.json");
|
|
21
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
22
|
+
async function readCache() {
|
|
23
|
+
try {
|
|
24
|
+
if (!await fs.pathExists(CACHE_FILE)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return await fs.readJson(CACHE_FILE);
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function writeCache(plugins) {
|
|
33
|
+
try {
|
|
34
|
+
await fs.ensureDir(CACHE_DIR);
|
|
35
|
+
await fs.writeJson(
|
|
36
|
+
CACHE_FILE,
|
|
37
|
+
{ fetchedAt: Date.now(), plugins },
|
|
38
|
+
{ spaces: 2 }
|
|
39
|
+
);
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function fetchPluginRegistry(registryUrl, fallback) {
|
|
44
|
+
if (!registryUrl) {
|
|
45
|
+
return fallback;
|
|
46
|
+
}
|
|
47
|
+
const cached = await readCache();
|
|
48
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
49
|
+
return cached.plugins;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetch(registryUrl);
|
|
53
|
+
if (response.ok) {
|
|
54
|
+
const data = await response.json();
|
|
55
|
+
const plugins = data.plugins;
|
|
56
|
+
await writeCache(plugins);
|
|
57
|
+
return plugins;
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
return cached?.plugins ?? fallback;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/plugins-model.ts
|
|
65
|
+
var DEFAULT_PLUGINS = {
|
|
66
|
+
feedback: {
|
|
67
|
+
name: "Feedback",
|
|
68
|
+
id: "feedback",
|
|
69
|
+
description: "Add a feedback popup to your site.",
|
|
70
|
+
postInstallMessage: "Run database migrations: pnpm db:migrate",
|
|
71
|
+
variants: {
|
|
72
|
+
"next-supabase": {
|
|
73
|
+
envVars: [],
|
|
74
|
+
path: "packages/plugins/feedback"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
waitlist: {
|
|
79
|
+
name: "Waitlist",
|
|
80
|
+
id: "waitlist",
|
|
81
|
+
description: "Add a waitlist to your site.",
|
|
82
|
+
postInstallMessage: "Run database migrations: pnpm db:migrate",
|
|
83
|
+
variants: {
|
|
84
|
+
"next-supabase": {
|
|
85
|
+
envVars: [],
|
|
86
|
+
path: "packages/plugins/waitlist"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
testimonial: {
|
|
91
|
+
name: "Testimonial",
|
|
92
|
+
id: "testimonial",
|
|
93
|
+
description: "Add a testimonial widget to your site.",
|
|
94
|
+
postInstallMessage: "Run database migrations: pnpm db:migrate",
|
|
95
|
+
variants: {
|
|
96
|
+
"next-supabase": {
|
|
97
|
+
envVars: [],
|
|
98
|
+
path: "packages/plugins/testimonial"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
roadmap: {
|
|
103
|
+
name: "Roadmap",
|
|
104
|
+
id: "roadmap",
|
|
105
|
+
description: "Add a roadmap to your site.",
|
|
106
|
+
postInstallMessage: "Run database migrations: pnpm db:migrate",
|
|
107
|
+
variants: {
|
|
108
|
+
"next-supabase": {
|
|
109
|
+
envVars: [],
|
|
110
|
+
path: "packages/plugins/roadmap"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"google-analytics": {
|
|
115
|
+
name: "Google Analytics",
|
|
116
|
+
id: "google-analytics",
|
|
117
|
+
description: "Add Google Analytics to your site.",
|
|
118
|
+
variants: {
|
|
119
|
+
"next-supabase": {
|
|
120
|
+
envVars: [
|
|
121
|
+
{
|
|
122
|
+
key: "NEXT_PUBLIC_GOOGLE_ANALYTICS_ID",
|
|
123
|
+
description: "Google Analytics Measurement ID"
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
path: "packages/plugins/analytics/google-analytics"
|
|
127
|
+
},
|
|
128
|
+
"next-drizzle": {
|
|
129
|
+
envVars: [
|
|
130
|
+
{
|
|
131
|
+
key: "NEXT_PUBLIC_GOOGLE_ANALYTICS_ID",
|
|
132
|
+
description: "Google Analytics Measurement ID"
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
path: "packages/plugins/analytics/google-analytics"
|
|
136
|
+
},
|
|
137
|
+
"next-prisma": {
|
|
138
|
+
envVars: [
|
|
139
|
+
{
|
|
140
|
+
key: "NEXT_PUBLIC_GOOGLE_ANALYTICS_ID",
|
|
141
|
+
description: "Google Analytics Measurement ID"
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
path: "packages/plugins/analytics/google-analytics"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
honeybadger: {
|
|
149
|
+
name: "Honeybadger",
|
|
150
|
+
id: "honeybadger",
|
|
151
|
+
description: "Add Honeybadger Error Tracking to your site.",
|
|
152
|
+
variants: {
|
|
153
|
+
"next-supabase": {
|
|
154
|
+
envVars: [
|
|
155
|
+
{
|
|
156
|
+
key: "HONEYBADGER_API_KEY",
|
|
157
|
+
description: "Honeybadger private API key"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
key: "NEXT_PUBLIC_HONEYBADGER_ENVIRONMENT",
|
|
161
|
+
description: "Honeybadger environment"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
key: "NEXT_PUBLIC_HONEYBADGER_REVISION",
|
|
165
|
+
description: "Honeybadger log revision"
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
path: "packages/plugins/honeybadger"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
posthog: {
|
|
173
|
+
name: "PostHog",
|
|
174
|
+
id: "posthog",
|
|
175
|
+
description: "Add PostHog Analytics to your site.",
|
|
176
|
+
variants: {
|
|
177
|
+
"next-supabase": {
|
|
178
|
+
envVars: [
|
|
179
|
+
{
|
|
180
|
+
key: "NEXT_PUBLIC_POSTHOG_KEY",
|
|
181
|
+
description: "PostHog project API key"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
key: "NEXT_PUBLIC_POSTHOG_HOST",
|
|
185
|
+
description: "PostHog host URL",
|
|
186
|
+
defaultValue: "https://app.posthog.com"
|
|
187
|
+
}
|
|
188
|
+
],
|
|
189
|
+
path: "packages/plugins/analytics/posthog"
|
|
190
|
+
},
|
|
191
|
+
"next-drizzle": {
|
|
192
|
+
envVars: [
|
|
193
|
+
{
|
|
194
|
+
key: "NEXT_PUBLIC_POSTHOG_KEY",
|
|
195
|
+
description: "PostHog project API key"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
key: "NEXT_PUBLIC_POSTHOG_HOST",
|
|
199
|
+
description: "PostHog host URL",
|
|
200
|
+
defaultValue: "https://app.posthog.com"
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
path: "packages/plugins/analytics/posthog"
|
|
204
|
+
},
|
|
205
|
+
"next-prisma": {
|
|
206
|
+
envVars: [
|
|
207
|
+
{
|
|
208
|
+
key: "NEXT_PUBLIC_POSTHOG_KEY",
|
|
209
|
+
description: "PostHog project API key"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
key: "NEXT_PUBLIC_POSTHOG_HOST",
|
|
213
|
+
description: "PostHog host URL",
|
|
214
|
+
defaultValue: "https://app.posthog.com"
|
|
215
|
+
}
|
|
216
|
+
],
|
|
217
|
+
path: "packages/plugins/analytics/posthog"
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
umami: {
|
|
222
|
+
name: "Umami",
|
|
223
|
+
id: "umami",
|
|
224
|
+
description: "Add Umami Analytics to your site.",
|
|
225
|
+
variants: {
|
|
226
|
+
"next-supabase": {
|
|
227
|
+
envVars: [
|
|
228
|
+
{
|
|
229
|
+
key: "NEXT_PUBLIC_UMAMI_WEBSITE_ID",
|
|
230
|
+
description: "Umami website ID"
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
key: "NEXT_PUBLIC_UMAMI_HOST",
|
|
234
|
+
description: "Umami host URL"
|
|
235
|
+
}
|
|
236
|
+
],
|
|
237
|
+
path: "packages/plugins/analytics/umami"
|
|
238
|
+
},
|
|
239
|
+
"next-drizzle": {
|
|
240
|
+
envVars: [
|
|
241
|
+
{
|
|
242
|
+
key: "NEXT_PUBLIC_UMAMI_WEBSITE_ID",
|
|
243
|
+
description: "Umami website ID"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
key: "NEXT_PUBLIC_UMAMI_HOST",
|
|
247
|
+
description: "Umami host URL"
|
|
248
|
+
}
|
|
249
|
+
],
|
|
250
|
+
path: "packages/plugins/analytics/umami"
|
|
251
|
+
},
|
|
252
|
+
"next-prisma": {
|
|
253
|
+
envVars: [
|
|
254
|
+
{
|
|
255
|
+
key: "NEXT_PUBLIC_UMAMI_WEBSITE_ID",
|
|
256
|
+
description: "Umami website ID"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
key: "NEXT_PUBLIC_UMAMI_HOST",
|
|
260
|
+
description: "Umami host URL"
|
|
261
|
+
}
|
|
262
|
+
],
|
|
263
|
+
path: "packages/plugins/analytics/umami"
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
signoz: {
|
|
268
|
+
name: "SigNoz",
|
|
269
|
+
id: "signoz",
|
|
270
|
+
description: "Add SigNoz Monitoring to your app.",
|
|
271
|
+
variants: {
|
|
272
|
+
"next-supabase": {
|
|
273
|
+
envVars: [
|
|
274
|
+
{
|
|
275
|
+
key: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
276
|
+
description: "SigNoz OTLP endpoint URL"
|
|
277
|
+
}
|
|
278
|
+
],
|
|
279
|
+
path: "packages/plugins/signoz"
|
|
280
|
+
},
|
|
281
|
+
"next-drizzle": {
|
|
282
|
+
envVars: [
|
|
283
|
+
{
|
|
284
|
+
key: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
285
|
+
description: "SigNoz OTLP endpoint URL"
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
path: "packages/plugins/signoz"
|
|
289
|
+
},
|
|
290
|
+
"next-prisma": {
|
|
291
|
+
envVars: [
|
|
292
|
+
{
|
|
293
|
+
key: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
294
|
+
description: "SigNoz OTLP endpoint URL"
|
|
295
|
+
}
|
|
296
|
+
],
|
|
297
|
+
path: "packages/plugins/signoz"
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
paddle: {
|
|
302
|
+
name: "Paddle",
|
|
303
|
+
id: "paddle",
|
|
304
|
+
description: "Add Paddle Billing to your app.",
|
|
305
|
+
variants: {
|
|
306
|
+
"next-supabase": {
|
|
307
|
+
envVars: [
|
|
308
|
+
{
|
|
309
|
+
key: "NEXT_PUBLIC_PADDLE_CLIENT_TOKEN",
|
|
310
|
+
description: "Paddle client-side token"
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
key: "PADDLE_API_KEY",
|
|
314
|
+
description: "Paddle API key"
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
key: "PADDLE_WEBHOOK_SECRET",
|
|
318
|
+
description: "Paddle webhook secret"
|
|
319
|
+
}
|
|
320
|
+
],
|
|
321
|
+
path: "packages/plugins/paddle"
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
"supabase-cms": {
|
|
326
|
+
name: "Supabase CMS",
|
|
327
|
+
id: "supabase-cms",
|
|
328
|
+
description: "Add Supabase CMS provider to your app.",
|
|
329
|
+
postInstallMessage: "Run database migrations: pnpm db:migrate",
|
|
330
|
+
variants: {
|
|
331
|
+
"next-supabase": {
|
|
332
|
+
envVars: [],
|
|
333
|
+
path: "packages/plugins/supabase-cms"
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
"meshes-analytics": {
|
|
338
|
+
name: "Meshes Analytics",
|
|
339
|
+
id: "meshes-analytics",
|
|
340
|
+
description: "Add Meshes Analytics to your app.",
|
|
341
|
+
variants: {
|
|
342
|
+
"next-supabase": {
|
|
343
|
+
envVars: [
|
|
344
|
+
{
|
|
345
|
+
key: "NEXT_PUBLIC_MESHES_PUBLISHABLE_KEY",
|
|
346
|
+
description: "The Meshes publishable key"
|
|
347
|
+
}
|
|
348
|
+
],
|
|
349
|
+
path: "packages/plugins/meshes-analytics"
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
directus: {
|
|
354
|
+
name: "Directus CMS",
|
|
355
|
+
id: "directus-cms",
|
|
356
|
+
description: "Add Directus as your CMS.",
|
|
357
|
+
variants: {
|
|
358
|
+
"next-supabase": {
|
|
359
|
+
envVars: [
|
|
360
|
+
{
|
|
361
|
+
key: "NEXT_PUBLIC_DIRECTUS_URL",
|
|
362
|
+
description: "The Directus URL"
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
key: "DIRECTUS_ACCESS_TOKEN",
|
|
366
|
+
description: "The Directus access token"
|
|
367
|
+
}
|
|
368
|
+
],
|
|
369
|
+
path: "packages/plugins/directus"
|
|
370
|
+
},
|
|
371
|
+
"next-drizzle": {
|
|
372
|
+
envVars: [
|
|
373
|
+
{
|
|
374
|
+
key: "NEXT_PUBLIC_DIRECTUS_URL",
|
|
375
|
+
description: "The Directus URL"
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
key: "DIRECTUS_ACCESS_TOKEN",
|
|
379
|
+
description: "The Directus access token"
|
|
380
|
+
}
|
|
381
|
+
],
|
|
382
|
+
path: "packages/plugins/directus"
|
|
383
|
+
},
|
|
384
|
+
"next-prisma": {
|
|
385
|
+
envVars: [
|
|
386
|
+
{
|
|
387
|
+
key: "NEXT_PUBLIC_DIRECTUS_URL",
|
|
388
|
+
description: "The Directus URL"
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
key: "DIRECTUS_ACCESS_TOKEN",
|
|
392
|
+
description: "The Directus access token"
|
|
393
|
+
}
|
|
394
|
+
],
|
|
395
|
+
path: "packages/plugins/directus"
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
var PluginRegistry = class _PluginRegistry {
|
|
401
|
+
constructor(plugins) {
|
|
402
|
+
this.plugins = plugins;
|
|
403
|
+
}
|
|
404
|
+
static async load() {
|
|
405
|
+
const registryUrl = process.env.MAKERKIT_PLUGINS_REGISTRY_URL;
|
|
406
|
+
const plugins = await fetchPluginRegistry(registryUrl, DEFAULT_PLUGINS);
|
|
407
|
+
return new _PluginRegistry(plugins);
|
|
408
|
+
}
|
|
409
|
+
getPluginById(id) {
|
|
410
|
+
return this.plugins[id];
|
|
411
|
+
}
|
|
412
|
+
getPluginsForVariant(variant) {
|
|
413
|
+
return Object.values(this.plugins).filter((p) => variant in p.variants);
|
|
414
|
+
}
|
|
415
|
+
validatePlugin(pluginId, variant) {
|
|
416
|
+
const plugin = this.getPluginById(pluginId);
|
|
417
|
+
invariant(plugin, `Plugin "${pluginId}" not found`);
|
|
418
|
+
invariant(
|
|
419
|
+
plugin.variants[variant],
|
|
420
|
+
`Plugin "${pluginId}" is not available for the ${variant} variant`
|
|
421
|
+
);
|
|
422
|
+
return plugin;
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
function getEnvVars(plugin, variant) {
|
|
426
|
+
return plugin.variants[variant]?.envVars ?? [];
|
|
427
|
+
}
|
|
428
|
+
function getPath(plugin, variant) {
|
|
429
|
+
return plugin.variants[variant]?.path;
|
|
430
|
+
}
|
|
431
|
+
async function isInstalled(plugin, variant) {
|
|
432
|
+
const pluginPath = getPath(plugin, variant);
|
|
433
|
+
if (!pluginPath) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
const pkgJsonPath = join2(process.cwd(), pluginPath, "package.json");
|
|
437
|
+
if (!await fs2.pathExists(pkgJsonPath)) {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
const pkg = await fs2.readJson(pkgJsonPath);
|
|
442
|
+
return !!pkg.name && !!pkg.exports;
|
|
443
|
+
} catch {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/utils/base-store.ts
|
|
449
|
+
import { dirname, join as join3 } from "path";
|
|
450
|
+
import fs3 from "fs-extra";
|
|
451
|
+
function basesDir(pluginId) {
|
|
452
|
+
return join3(process.cwd(), "node_modules", ".cache", "makerkit", "bases", pluginId);
|
|
453
|
+
}
|
|
454
|
+
async function saveBaseVersions(pluginId, files) {
|
|
455
|
+
const dir = basesDir(pluginId);
|
|
456
|
+
for (const file of files) {
|
|
457
|
+
const targetPath = join3(dir, file.target);
|
|
458
|
+
await fs3.ensureDir(dirname(targetPath));
|
|
459
|
+
await fs3.writeFile(targetPath, file.content);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async function readBaseVersion(pluginId, fileTarget) {
|
|
463
|
+
const filePath = join3(basesDir(pluginId), fileTarget);
|
|
464
|
+
try {
|
|
465
|
+
return await fs3.readFile(filePath, "utf-8");
|
|
466
|
+
} catch {
|
|
467
|
+
return void 0;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
async function hasBaseVersions(pluginId) {
|
|
471
|
+
return fs3.pathExists(basesDir(pluginId));
|
|
472
|
+
}
|
|
473
|
+
function computeFileStatus(params) {
|
|
474
|
+
const { base, local, remote } = params;
|
|
475
|
+
if (local !== void 0 && local === remote) {
|
|
476
|
+
return "unchanged";
|
|
477
|
+
}
|
|
478
|
+
if (base === void 0) {
|
|
479
|
+
if (local === void 0) {
|
|
480
|
+
return "added";
|
|
481
|
+
}
|
|
482
|
+
return "no_base";
|
|
483
|
+
}
|
|
484
|
+
if (local === void 0) {
|
|
485
|
+
return "deleted_locally";
|
|
486
|
+
}
|
|
487
|
+
if (base === local) {
|
|
488
|
+
return "updated";
|
|
489
|
+
}
|
|
490
|
+
if (base === remote) {
|
|
491
|
+
return "unchanged";
|
|
492
|
+
}
|
|
493
|
+
return "conflict";
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// src/utils/git.ts
|
|
497
|
+
import { execaCommand } from "execa";
|
|
498
|
+
function getErrorOutput(error) {
|
|
499
|
+
if (error instanceof Error && "stderr" in error) {
|
|
500
|
+
const stderr = String(error.stderr);
|
|
501
|
+
if (stderr) return stderr;
|
|
502
|
+
}
|
|
503
|
+
if (error instanceof Error && "stdout" in error) {
|
|
504
|
+
const stdout = String(error.stdout);
|
|
505
|
+
if (stdout) return stdout;
|
|
506
|
+
}
|
|
507
|
+
return error instanceof Error ? error.message : String(error);
|
|
508
|
+
}
|
|
509
|
+
async function isGitClean() {
|
|
510
|
+
const { stdout } = await execaCommand("git status --porcelain");
|
|
511
|
+
return stdout.trim() === "";
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// src/utils/install-registry-files.ts
|
|
515
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
516
|
+
import fs4 from "fs-extra";
|
|
517
|
+
async function fetchRegistryItem(variant, pluginId, username) {
|
|
518
|
+
const url = `https://makerkit.dev/r/${variant}/${pluginId}.json?username=${username}`;
|
|
519
|
+
const response = await fetch(url);
|
|
520
|
+
if (!response.ok) {
|
|
521
|
+
throw new Error(
|
|
522
|
+
`Failed to fetch plugin registry for "${pluginId}" (${response.status}): ${response.statusText}`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
const item = await response.json();
|
|
526
|
+
if (!item.files || item.files.length === 0) {
|
|
527
|
+
throw new Error(`Plugin "${pluginId}" has no files in the registry.`);
|
|
528
|
+
}
|
|
529
|
+
return item;
|
|
530
|
+
}
|
|
531
|
+
async function installRegistryFiles(variant, pluginId, username) {
|
|
532
|
+
const item = await fetchRegistryItem(variant, pluginId, username);
|
|
533
|
+
const cwd = process.cwd();
|
|
534
|
+
for (const file of item.files) {
|
|
535
|
+
const targetPath = join4(cwd, file.target);
|
|
536
|
+
await fs4.ensureDir(dirname2(targetPath));
|
|
537
|
+
await fs4.writeFile(targetPath, file.content);
|
|
538
|
+
}
|
|
539
|
+
return item;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// src/utils/run-codemod.ts
|
|
543
|
+
import { execaCommand as execaCommand2 } from "execa";
|
|
544
|
+
var CODEMOD_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
545
|
+
async function runCodemod(variant, pluginId, options) {
|
|
546
|
+
try {
|
|
547
|
+
const localPath = process.env.MAKERKIT_CODEMODS_PATH;
|
|
548
|
+
const runner = process.env.MAKERKIT_PACKAGE_RUNNER ?? "npx --yes";
|
|
549
|
+
const command = localPath ? `${runner} codemod workflow run --allow-dirty -w ${localPath}/codemods/${variant}/${pluginId}` : `${runner} codemod --allow-dirty @makerkit/${variant}-${pluginId}`;
|
|
550
|
+
const { stdout, stderr } = await execaCommand2(command, {
|
|
551
|
+
stdio: options?.captureOutput ? "pipe" : "inherit",
|
|
552
|
+
timeout: CODEMOD_TIMEOUT_MS
|
|
553
|
+
});
|
|
554
|
+
return {
|
|
555
|
+
success: true,
|
|
556
|
+
output: stdout || stderr || ""
|
|
557
|
+
};
|
|
558
|
+
} catch (error) {
|
|
559
|
+
let message = "Unknown error during codemod";
|
|
560
|
+
if (error instanceof Error) {
|
|
561
|
+
message = error.message;
|
|
562
|
+
if ("stderr" in error && error.stderr) {
|
|
563
|
+
message += `
|
|
564
|
+
${error.stderr}`;
|
|
565
|
+
}
|
|
566
|
+
if ("timedOut" in error && error.timedOut) {
|
|
567
|
+
message = `Codemod timed out after ${CODEMOD_TIMEOUT_MS / 1e3}s (the workflow engine may have stalled after an error)`;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
success: false,
|
|
572
|
+
output: message
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/utils/username-cache.ts
|
|
578
|
+
import { join as join5 } from "path";
|
|
579
|
+
import { tmpdir } from "os";
|
|
580
|
+
import fs5 from "fs-extra";
|
|
581
|
+
import prompts from "prompts";
|
|
582
|
+
var USERNAME_FILE = join5(tmpdir(), "makerkit-username");
|
|
583
|
+
function getCachedUsername() {
|
|
584
|
+
try {
|
|
585
|
+
const username = fs5.readFileSync(USERNAME_FILE, "utf-8").trim();
|
|
586
|
+
return username.length > 0 ? username : void 0;
|
|
587
|
+
} catch {
|
|
588
|
+
return void 0;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function cacheUsername(username) {
|
|
592
|
+
fs5.writeFileSync(USERNAME_FILE, username, "utf-8");
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// src/utils/workspace.ts
|
|
596
|
+
import { join as join6 } from "path";
|
|
597
|
+
import fs6 from "fs-extra";
|
|
598
|
+
async function readDeps(pkgPath) {
|
|
599
|
+
if (!await fs6.pathExists(pkgPath)) {
|
|
600
|
+
return {};
|
|
601
|
+
}
|
|
602
|
+
const pkg = await fs6.readJson(pkgPath);
|
|
603
|
+
return {
|
|
604
|
+
...pkg.dependencies,
|
|
605
|
+
...pkg.devDependencies
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
async function detectVariant() {
|
|
609
|
+
const cwd = process.cwd();
|
|
610
|
+
const rootPkgPath = join6(cwd, "package.json");
|
|
611
|
+
if (!await fs6.pathExists(rootPkgPath)) {
|
|
612
|
+
throw new Error(
|
|
613
|
+
"No package.json found. Please run this command from a MakerKit project root."
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
const rootDeps = await readDeps(rootPkgPath);
|
|
617
|
+
if (!rootDeps["turbo"]) {
|
|
618
|
+
throw new Error(
|
|
619
|
+
'This does not appear to be a MakerKit Turbo monorepo. The "turbo" dependency was not found in package.json.'
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
const webDeps = await readDeps(join6(cwd, "apps", "web", "package.json"));
|
|
623
|
+
const dbDeps = await readDeps(
|
|
624
|
+
join6(cwd, "packages", "database", "package.json")
|
|
625
|
+
);
|
|
626
|
+
const hasSupabase = !!webDeps["@supabase/supabase-js"];
|
|
627
|
+
const hasReactRouter = !!webDeps["@react-router/node"];
|
|
628
|
+
const hasDrizzle = !!webDeps["drizzle-orm"] || !!dbDeps["drizzle-orm"];
|
|
629
|
+
const hasPrisma = !!webDeps["@prisma/client"] || !!dbDeps["@prisma/client"];
|
|
630
|
+
if (hasReactRouter && hasSupabase) {
|
|
631
|
+
return "react-router-supabase";
|
|
632
|
+
}
|
|
633
|
+
if (hasSupabase) {
|
|
634
|
+
return "next-supabase";
|
|
635
|
+
}
|
|
636
|
+
if (hasDrizzle) {
|
|
637
|
+
return "next-drizzle";
|
|
638
|
+
}
|
|
639
|
+
if (hasPrisma) {
|
|
640
|
+
return "next-prisma";
|
|
641
|
+
}
|
|
642
|
+
throw new Error(
|
|
643
|
+
"Unrecognized MakerKit project. Could not detect variant from dependencies."
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
async function validateProject() {
|
|
647
|
+
const variant = await detectVariant();
|
|
648
|
+
const rootPkg = await fs6.readJson(join6(process.cwd(), "package.json"));
|
|
649
|
+
return {
|
|
650
|
+
variant,
|
|
651
|
+
version: rootPkg.version ?? "unknown"
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/utils/add-plugin.ts
|
|
656
|
+
async function addPlugin(options) {
|
|
657
|
+
if (!options.skipGitCheck) {
|
|
658
|
+
const gitClean = await isGitClean();
|
|
659
|
+
if (!gitClean) {
|
|
660
|
+
return {
|
|
661
|
+
success: false,
|
|
662
|
+
reason: "Git working directory has uncommitted changes. Please commit or stash them before adding a plugin."
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
const { variant } = await validateProject();
|
|
667
|
+
const username = options.githubUsername?.trim() || getCachedUsername();
|
|
668
|
+
if (!username) {
|
|
669
|
+
return {
|
|
670
|
+
success: false,
|
|
671
|
+
reason: "No GitHub username cached and none provided. Call makerkit_init_registry first or pass githubUsername."
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
cacheUsername(username);
|
|
675
|
+
const registry = await PluginRegistry.load();
|
|
676
|
+
const plugin = registry.validatePlugin(options.pluginId, variant);
|
|
677
|
+
if (await isInstalled(plugin, variant)) {
|
|
678
|
+
return {
|
|
679
|
+
success: false,
|
|
680
|
+
reason: `Plugin "${plugin.name}" is already installed.`
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
const item = await installRegistryFiles(variant, options.pluginId, username);
|
|
684
|
+
await saveBaseVersions(options.pluginId, item.files);
|
|
685
|
+
const codemodResult = await runCodemod(variant, options.pluginId, {
|
|
686
|
+
captureOutput: options.captureCodemodOutput ?? true
|
|
687
|
+
});
|
|
688
|
+
const envVars = getEnvVars(plugin, variant);
|
|
689
|
+
return {
|
|
690
|
+
success: true,
|
|
691
|
+
pluginName: plugin.name,
|
|
692
|
+
pluginId: plugin.id,
|
|
693
|
+
variant,
|
|
694
|
+
envVars: envVars.map((e) => ({ key: e.key, description: e.description })),
|
|
695
|
+
postInstallMessage: plugin.postInstallMessage ?? null,
|
|
696
|
+
codemodOutput: codemodResult.output,
|
|
697
|
+
codemodWarning: codemodResult.success ? void 0 : `The automated codemod did not complete successfully. Plugin files were installed but some wiring steps may have failed.
|
|
698
|
+
${codemodResult.output}`
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/utils/apply-plugin-update.ts
|
|
703
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
704
|
+
import fs7 from "fs-extra";
|
|
705
|
+
async function applyPluginUpdate(options) {
|
|
706
|
+
const { variant } = await validateProject();
|
|
707
|
+
const username = options.githubUsername?.trim() || getCachedUsername();
|
|
708
|
+
if (!username) {
|
|
709
|
+
return {
|
|
710
|
+
success: false,
|
|
711
|
+
reason: "No GitHub username cached and none provided. Call makerkit_init_registry first or pass githubUsername."
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
cacheUsername(username);
|
|
715
|
+
const registry = await PluginRegistry.load();
|
|
716
|
+
registry.validatePlugin(options.pluginId, variant);
|
|
717
|
+
const item = await fetchRegistryItem(variant, options.pluginId, username);
|
|
718
|
+
const remoteByPath = new Map(item.files.map((f) => [f.target, f.content]));
|
|
719
|
+
const cwd = process.cwd();
|
|
720
|
+
const written = [];
|
|
721
|
+
const skipped = [];
|
|
722
|
+
const deleted = [];
|
|
723
|
+
for (const file of options.files) {
|
|
724
|
+
const targetPath = join7(cwd, file.path);
|
|
725
|
+
switch (file.action) {
|
|
726
|
+
case "write": {
|
|
727
|
+
if (file.content === void 0) {
|
|
728
|
+
return {
|
|
729
|
+
success: false,
|
|
730
|
+
reason: `File "${file.path}" has action "write" but no content provided.`
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
await fs7.ensureDir(dirname3(targetPath));
|
|
734
|
+
await fs7.writeFile(targetPath, file.content);
|
|
735
|
+
written.push(file.path);
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
case "skip": {
|
|
739
|
+
skipped.push(file.path);
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
case "delete": {
|
|
743
|
+
if (await fs7.pathExists(targetPath)) {
|
|
744
|
+
await fs7.remove(targetPath);
|
|
745
|
+
}
|
|
746
|
+
deleted.push(file.path);
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (file.action !== "delete") {
|
|
751
|
+
const remoteContent = remoteByPath.get(file.path);
|
|
752
|
+
if (remoteContent !== void 0) {
|
|
753
|
+
await saveBaseVersions(options.pluginId, [
|
|
754
|
+
{ path: "", content: remoteContent, type: "", target: file.path }
|
|
755
|
+
]);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
success: true,
|
|
761
|
+
pluginId: options.pluginId,
|
|
762
|
+
variant,
|
|
763
|
+
written,
|
|
764
|
+
skipped,
|
|
765
|
+
deleted
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/utils/check-plugin-update.ts
|
|
770
|
+
import { join as join8 } from "path";
|
|
771
|
+
import fs8 from "fs-extra";
|
|
772
|
+
async function checkPluginUpdate(options) {
|
|
773
|
+
const { variant } = await validateProject();
|
|
774
|
+
const username = options.githubUsername?.trim() || getCachedUsername();
|
|
775
|
+
if (!username) {
|
|
776
|
+
return {
|
|
777
|
+
success: false,
|
|
778
|
+
reason: "No GitHub username cached and none provided. Call makerkit_init_registry first or pass githubUsername."
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
cacheUsername(username);
|
|
782
|
+
const registry = await PluginRegistry.load();
|
|
783
|
+
registry.validatePlugin(options.pluginId, variant);
|
|
784
|
+
const item = await fetchRegistryItem(variant, options.pluginId, username);
|
|
785
|
+
const cwd = process.cwd();
|
|
786
|
+
const hasBase = await hasBaseVersions(options.pluginId);
|
|
787
|
+
const counts = {
|
|
788
|
+
unchanged: 0,
|
|
789
|
+
updated: 0,
|
|
790
|
+
conflict: 0,
|
|
791
|
+
added: 0,
|
|
792
|
+
deleted_locally: 0,
|
|
793
|
+
no_base: 0
|
|
794
|
+
};
|
|
795
|
+
const files = await Promise.all(
|
|
796
|
+
item.files.map(async (file) => {
|
|
797
|
+
const localPath = join8(cwd, file.target);
|
|
798
|
+
let local;
|
|
799
|
+
try {
|
|
800
|
+
local = await fs8.readFile(localPath, "utf-8");
|
|
801
|
+
} catch {
|
|
802
|
+
local = void 0;
|
|
803
|
+
}
|
|
804
|
+
const base = await readBaseVersion(options.pluginId, file.target);
|
|
805
|
+
const remote = file.content;
|
|
806
|
+
const status = computeFileStatus({ base, local, remote });
|
|
807
|
+
counts[status]++;
|
|
808
|
+
const result = {
|
|
809
|
+
path: file.target,
|
|
810
|
+
status
|
|
811
|
+
};
|
|
812
|
+
switch (status) {
|
|
813
|
+
case "unchanged":
|
|
814
|
+
break;
|
|
815
|
+
case "updated":
|
|
816
|
+
result.remote = remote;
|
|
817
|
+
break;
|
|
818
|
+
case "conflict":
|
|
819
|
+
result.base = base;
|
|
820
|
+
result.local = local;
|
|
821
|
+
result.remote = remote;
|
|
822
|
+
break;
|
|
823
|
+
case "no_base":
|
|
824
|
+
result.local = local;
|
|
825
|
+
result.remote = remote;
|
|
826
|
+
break;
|
|
827
|
+
case "added":
|
|
828
|
+
result.remote = remote;
|
|
829
|
+
break;
|
|
830
|
+
case "deleted_locally":
|
|
831
|
+
result.base = base;
|
|
832
|
+
result.remote = remote;
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
return result;
|
|
836
|
+
})
|
|
837
|
+
);
|
|
838
|
+
return {
|
|
839
|
+
success: true,
|
|
840
|
+
pluginId: options.pluginId,
|
|
841
|
+
variant,
|
|
842
|
+
hasBaseVersions: hasBase,
|
|
843
|
+
counts,
|
|
844
|
+
files
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// src/utils/create-project.ts
|
|
849
|
+
import { join as join10 } from "path";
|
|
850
|
+
import { execa } from "execa";
|
|
851
|
+
import fs10 from "fs-extra";
|
|
852
|
+
|
|
853
|
+
// src/utils/marker-file.ts
|
|
854
|
+
import { join as join9 } from "path";
|
|
855
|
+
import fs9 from "fs-extra";
|
|
856
|
+
|
|
857
|
+
// src/version.ts
|
|
858
|
+
var CLI_VERSION = "2.0.0-beta.1";
|
|
859
|
+
|
|
860
|
+
// src/utils/marker-file.ts
|
|
861
|
+
async function writeMarkerFile(projectPath, variant, kitRepo) {
|
|
862
|
+
const dir = join9(projectPath, ".makerkit");
|
|
863
|
+
await fs9.ensureDir(dir);
|
|
864
|
+
const config2 = {
|
|
865
|
+
version: 1,
|
|
866
|
+
variant,
|
|
867
|
+
kit_repo: kitRepo,
|
|
868
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
869
|
+
cli_version: CLI_VERSION
|
|
870
|
+
};
|
|
871
|
+
await fs9.writeJson(join9(dir, "config.json"), config2, { spaces: 2 });
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/utils/upstream.ts
|
|
875
|
+
import { execaCommand as execaCommand3 } from "execa";
|
|
876
|
+
var VARIANT_REPO_MAP = {
|
|
877
|
+
"next-supabase": "makerkit/next-supabase-saas-kit-turbo",
|
|
878
|
+
"next-drizzle": "makerkit/next-drizzle-saas-kit-turbo",
|
|
879
|
+
"next-prisma": "makerkit/next-prisma-saas-kit-turbo",
|
|
880
|
+
"react-router-supabase": "makerkit/react-router-supabase-saas-kit-turbo"
|
|
881
|
+
};
|
|
882
|
+
function sshUrl(repo) {
|
|
883
|
+
return `git@github.com:${repo}`;
|
|
884
|
+
}
|
|
885
|
+
function httpsUrl(repo) {
|
|
886
|
+
return `https://github.com/${repo}`;
|
|
887
|
+
}
|
|
888
|
+
async function hasSshAccess() {
|
|
889
|
+
try {
|
|
890
|
+
await execaCommand3("ssh -T git@github.com -o StrictHostKeyChecking=no", {
|
|
891
|
+
timeout: 1e4
|
|
892
|
+
});
|
|
893
|
+
return true;
|
|
894
|
+
} catch (error) {
|
|
895
|
+
const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : "";
|
|
896
|
+
return stderr.includes("successfully authenticated");
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
function getUpstreamUrl(variant, useSsh) {
|
|
900
|
+
const repo = VARIANT_REPO_MAP[variant];
|
|
901
|
+
return useSsh ? sshUrl(repo) : httpsUrl(repo);
|
|
902
|
+
}
|
|
903
|
+
function isUpstreamUrlValid(url, variant) {
|
|
904
|
+
const normalized = url.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
905
|
+
const repo = VARIANT_REPO_MAP[variant];
|
|
906
|
+
return normalized === sshUrl(repo) || normalized === httpsUrl(repo);
|
|
907
|
+
}
|
|
908
|
+
async function getUpstreamRemoteUrl() {
|
|
909
|
+
try {
|
|
910
|
+
const { stdout } = await execaCommand3("git remote get-url upstream");
|
|
911
|
+
return stdout.trim() || void 0;
|
|
912
|
+
} catch {
|
|
913
|
+
return void 0;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
async function setUpstreamRemote(url) {
|
|
917
|
+
const currentUrl = await getUpstreamRemoteUrl();
|
|
918
|
+
if (currentUrl) {
|
|
919
|
+
await execaCommand3(`git remote set-url upstream ${url}`);
|
|
920
|
+
} else {
|
|
921
|
+
await execaCommand3(`git remote add upstream ${url}`);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// src/utils/create-project.ts
|
|
926
|
+
async function createProject(options) {
|
|
927
|
+
const { variant, name, directory, githubToken } = options;
|
|
928
|
+
const projectPath = join10(directory, name);
|
|
929
|
+
const repo = VARIANT_REPO_MAP[variant];
|
|
930
|
+
if (await fs10.pathExists(projectPath)) {
|
|
931
|
+
throw new Error(
|
|
932
|
+
`Target directory "${projectPath}" already exists. Choose a different name or remove it first.`
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
if (!await fs10.pathExists(directory)) {
|
|
936
|
+
throw new Error(
|
|
937
|
+
`Parent directory "${directory}" does not exist.`
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
let cloneUrl;
|
|
941
|
+
if (githubToken) {
|
|
942
|
+
cloneUrl = `https://${githubToken}@github.com/${repo}`;
|
|
943
|
+
} else {
|
|
944
|
+
const useSsh = await hasSshAccess();
|
|
945
|
+
cloneUrl = useSsh ? `git@github.com:${repo}` : `https://github.com/${repo}`;
|
|
946
|
+
}
|
|
947
|
+
await execa("git", ["clone", cloneUrl, name], { cwd: directory });
|
|
948
|
+
if (githubToken) {
|
|
949
|
+
await execa(
|
|
950
|
+
"git",
|
|
951
|
+
["remote", "set-url", "origin", `https://github.com/${repo}`],
|
|
952
|
+
{ cwd: projectPath }
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
await execa("pnpm", ["install"], { cwd: projectPath });
|
|
956
|
+
await writeMarkerFile(projectPath, variant, repo);
|
|
957
|
+
return {
|
|
958
|
+
success: true,
|
|
959
|
+
projectPath,
|
|
960
|
+
variant,
|
|
961
|
+
kitRepo: repo,
|
|
962
|
+
message: `Project "${name}" created successfully with variant "${variant}".`
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// src/utils/get-project-status.ts
|
|
967
|
+
async function getProjectStatus(options) {
|
|
968
|
+
const { variant, version } = await validateProject();
|
|
969
|
+
const gitClean = await isGitClean();
|
|
970
|
+
const registryConfigured = !!getCachedUsername();
|
|
971
|
+
const registry = await PluginRegistry.load();
|
|
972
|
+
const plugins = registry.getPluginsForVariant(variant);
|
|
973
|
+
const pluginStatuses = await Promise.all(
|
|
974
|
+
plugins.map(async (p) => ({
|
|
975
|
+
id: p.id,
|
|
976
|
+
name: p.name,
|
|
977
|
+
installed: await isInstalled(p, variant)
|
|
978
|
+
}))
|
|
979
|
+
);
|
|
980
|
+
return {
|
|
981
|
+
variant,
|
|
982
|
+
version,
|
|
983
|
+
gitClean,
|
|
984
|
+
registryConfigured,
|
|
985
|
+
plugins: pluginStatuses
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// src/utils/init-registry.ts
|
|
990
|
+
async function initRegistry(options) {
|
|
991
|
+
const variant = await detectVariant();
|
|
992
|
+
cacheUsername(options.githubUsername);
|
|
993
|
+
return {
|
|
994
|
+
success: true,
|
|
995
|
+
variant,
|
|
996
|
+
username: options.githubUsername
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// src/utils/list-plugins.ts
|
|
1001
|
+
async function listPlugins(options) {
|
|
1002
|
+
const variant = await detectVariant();
|
|
1003
|
+
const registry = await PluginRegistry.load();
|
|
1004
|
+
const plugins = registry.getPluginsForVariant(variant);
|
|
1005
|
+
const pluginList = await Promise.all(
|
|
1006
|
+
plugins.map(async (p) => ({
|
|
1007
|
+
id: p.id,
|
|
1008
|
+
name: p.name,
|
|
1009
|
+
description: p.description,
|
|
1010
|
+
installed: await isInstalled(p, variant),
|
|
1011
|
+
envVars: getEnvVars(p, variant).map((e) => e.key),
|
|
1012
|
+
postInstallMessage: p.postInstallMessage ?? null
|
|
1013
|
+
}))
|
|
1014
|
+
);
|
|
1015
|
+
return { variant, plugins: pluginList };
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// src/utils/list-variants.ts
|
|
1019
|
+
var VARIANT_CATALOG = [
|
|
1020
|
+
{
|
|
1021
|
+
id: "next-supabase",
|
|
1022
|
+
name: "Next.js + Supabase",
|
|
1023
|
+
description: "Full-stack SaaS kit with Next.js App Router and Supabase",
|
|
1024
|
+
repo: VARIANT_REPO_MAP["next-supabase"],
|
|
1025
|
+
tech: ["Next.js", "Supabase", "Tailwind CSS", "shadcn/ui"],
|
|
1026
|
+
database: "PostgreSQL (Supabase)",
|
|
1027
|
+
auth: "Supabase Auth",
|
|
1028
|
+
status: "stable"
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
id: "next-drizzle",
|
|
1032
|
+
name: "Next.js + Drizzle",
|
|
1033
|
+
description: "Full-stack SaaS kit with Next.js and Drizzle ORM",
|
|
1034
|
+
repo: VARIANT_REPO_MAP["next-drizzle"],
|
|
1035
|
+
tech: ["Next.js", "Drizzle", "Tailwind CSS", "shadcn/ui"],
|
|
1036
|
+
database: "PostgreSQL",
|
|
1037
|
+
auth: "Better Auth",
|
|
1038
|
+
status: "stable"
|
|
1039
|
+
},
|
|
1040
|
+
{
|
|
1041
|
+
id: "next-prisma",
|
|
1042
|
+
name: "Next.js + Prisma",
|
|
1043
|
+
description: "Full-stack SaaS kit with Next.js and Prisma ORM",
|
|
1044
|
+
repo: VARIANT_REPO_MAP["next-prisma"],
|
|
1045
|
+
tech: ["Next.js", "Prisma", "Tailwind CSS", "shadcn/ui"],
|
|
1046
|
+
database: "PostgreSQL",
|
|
1047
|
+
auth: "Better Auth",
|
|
1048
|
+
status: "stable"
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
id: "react-router-supabase",
|
|
1052
|
+
name: "React Router + Supabase",
|
|
1053
|
+
description: "Full-stack SaaS kit with React Router and Supabase",
|
|
1054
|
+
repo: VARIANT_REPO_MAP["react-router-supabase"],
|
|
1055
|
+
tech: ["React Router", "Supabase", "Tailwind CSS", "shadcn/ui"],
|
|
1056
|
+
database: "PostgreSQL (Supabase)",
|
|
1057
|
+
auth: "Supabase Auth",
|
|
1058
|
+
status: "stable"
|
|
1059
|
+
}
|
|
1060
|
+
];
|
|
1061
|
+
function listVariants() {
|
|
1062
|
+
return { variants: VARIANT_CATALOG };
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// src/utils/project-pull.ts
|
|
1066
|
+
import { join as join11 } from "path";
|
|
1067
|
+
import { execa as execa2, execaCommand as execaCommand4 } from "execa";
|
|
1068
|
+
import fs11 from "fs-extra";
|
|
1069
|
+
async function projectPull(options) {
|
|
1070
|
+
const { variant } = await validateProject();
|
|
1071
|
+
const gitClean = await isGitClean();
|
|
1072
|
+
if (!gitClean) {
|
|
1073
|
+
return {
|
|
1074
|
+
success: false,
|
|
1075
|
+
reason: "Git working directory has uncommitted changes. Please commit or stash them before pulling upstream updates."
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
let currentUrl = await getUpstreamRemoteUrl();
|
|
1079
|
+
if (!currentUrl) {
|
|
1080
|
+
const useSsh = await hasSshAccess();
|
|
1081
|
+
const url = getUpstreamUrl(variant, useSsh);
|
|
1082
|
+
await setUpstreamRemote(url);
|
|
1083
|
+
currentUrl = url;
|
|
1084
|
+
} else if (!isUpstreamUrlValid(currentUrl, variant)) {
|
|
1085
|
+
const useSsh = currentUrl.startsWith("git@");
|
|
1086
|
+
const expectedUrl = getUpstreamUrl(variant, useSsh);
|
|
1087
|
+
return {
|
|
1088
|
+
success: false,
|
|
1089
|
+
reason: `Upstream remote points to "${currentUrl}" but expected "${expectedUrl}" for variant "${variant}". Please ask the user whether to update the upstream URL, then run: git remote set-url upstream <correct-url>`
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
await execaCommand4("git fetch upstream");
|
|
1093
|
+
try {
|
|
1094
|
+
const { stdout } = await execaCommand4(
|
|
1095
|
+
"git merge upstream/main --no-edit"
|
|
1096
|
+
);
|
|
1097
|
+
const alreadyUpToDate = stdout.includes("Already up to date");
|
|
1098
|
+
return {
|
|
1099
|
+
success: true,
|
|
1100
|
+
variant,
|
|
1101
|
+
upstreamUrl: currentUrl,
|
|
1102
|
+
alreadyUpToDate,
|
|
1103
|
+
message: alreadyUpToDate ? "Already up to date." : "Successfully merged upstream changes."
|
|
1104
|
+
};
|
|
1105
|
+
} catch (mergeError) {
|
|
1106
|
+
const output = getErrorOutput(mergeError);
|
|
1107
|
+
const isConflict = output.includes("CONFLICT") || output.includes("Automatic merge failed");
|
|
1108
|
+
if (!isConflict) {
|
|
1109
|
+
throw new Error(`Merge failed: ${output}`);
|
|
1110
|
+
}
|
|
1111
|
+
const { stdout: statusOutput } = await execaCommand4(
|
|
1112
|
+
"git diff --name-only --diff-filter=U"
|
|
1113
|
+
);
|
|
1114
|
+
const conflictPaths = statusOutput.trim().split("\n").filter(Boolean);
|
|
1115
|
+
const cwd = process.cwd();
|
|
1116
|
+
const conflicts = await Promise.all(
|
|
1117
|
+
conflictPaths.map(async (filePath) => {
|
|
1118
|
+
let conflicted;
|
|
1119
|
+
try {
|
|
1120
|
+
conflicted = await fs11.readFile(join11(cwd, filePath), "utf-8");
|
|
1121
|
+
} catch {
|
|
1122
|
+
conflicted = void 0;
|
|
1123
|
+
}
|
|
1124
|
+
let base;
|
|
1125
|
+
let ours;
|
|
1126
|
+
let theirs;
|
|
1127
|
+
try {
|
|
1128
|
+
const { stdout: b } = await execa2("git", [
|
|
1129
|
+
"show",
|
|
1130
|
+
`:1:${filePath}`
|
|
1131
|
+
]);
|
|
1132
|
+
base = b;
|
|
1133
|
+
} catch {
|
|
1134
|
+
base = void 0;
|
|
1135
|
+
}
|
|
1136
|
+
try {
|
|
1137
|
+
const { stdout: o } = await execa2("git", [
|
|
1138
|
+
"show",
|
|
1139
|
+
`:2:${filePath}`
|
|
1140
|
+
]);
|
|
1141
|
+
ours = o;
|
|
1142
|
+
} catch {
|
|
1143
|
+
ours = void 0;
|
|
1144
|
+
}
|
|
1145
|
+
try {
|
|
1146
|
+
const { stdout: t } = await execa2("git", [
|
|
1147
|
+
"show",
|
|
1148
|
+
`:3:${filePath}`
|
|
1149
|
+
]);
|
|
1150
|
+
theirs = t;
|
|
1151
|
+
} catch {
|
|
1152
|
+
theirs = void 0;
|
|
1153
|
+
}
|
|
1154
|
+
return { path: filePath, conflicted, base, ours, theirs };
|
|
1155
|
+
})
|
|
1156
|
+
);
|
|
1157
|
+
return {
|
|
1158
|
+
success: false,
|
|
1159
|
+
variant,
|
|
1160
|
+
upstreamUrl: currentUrl,
|
|
1161
|
+
hasConflicts: true,
|
|
1162
|
+
conflictCount: conflicts.length,
|
|
1163
|
+
conflicts,
|
|
1164
|
+
instructions: "Merge conflicts detected. For each conflict: review base, ours (local), and theirs (upstream) versions. Produce resolved content and call makerkit_project_resolve_conflicts. Ask the user for guidance when the intent behind local changes is unclear."
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// src/utils/resolve-conflicts.ts
|
|
1170
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
1171
|
+
import { execa as execa3, execaCommand as execaCommand5 } from "execa";
|
|
1172
|
+
import fs12 from "fs-extra";
|
|
1173
|
+
async function resolveConflicts(options) {
|
|
1174
|
+
const cwd = process.cwd();
|
|
1175
|
+
for (const file of options.files) {
|
|
1176
|
+
const targetPath = join12(cwd, file.path);
|
|
1177
|
+
await fs12.ensureDir(dirname4(targetPath));
|
|
1178
|
+
await fs12.writeFile(targetPath, file.content);
|
|
1179
|
+
}
|
|
1180
|
+
const paths = options.files.map((f) => f.path);
|
|
1181
|
+
await execa3("git", ["add", ...paths]);
|
|
1182
|
+
let remainingConflicts = [];
|
|
1183
|
+
try {
|
|
1184
|
+
const { stdout } = await execaCommand5(
|
|
1185
|
+
"git diff --name-only --diff-filter=U"
|
|
1186
|
+
);
|
|
1187
|
+
remainingConflicts = stdout.trim().split("\n").filter(Boolean);
|
|
1188
|
+
} catch {
|
|
1189
|
+
remainingConflicts = [];
|
|
1190
|
+
}
|
|
1191
|
+
if (remainingConflicts.length > 0) {
|
|
1192
|
+
return {
|
|
1193
|
+
success: false,
|
|
1194
|
+
resolved: paths,
|
|
1195
|
+
remaining: remainingConflicts,
|
|
1196
|
+
message: `${paths.length} file(s) resolved, but ${remainingConflicts.length} conflict(s) remain. Resolve the remaining files and call makerkit_project_resolve_conflicts again.`
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
if (options.commitMessage) {
|
|
1200
|
+
await execa3("git", ["commit", "-m", options.commitMessage]);
|
|
1201
|
+
} else {
|
|
1202
|
+
await execaCommand5("git commit --no-edit");
|
|
1203
|
+
}
|
|
1204
|
+
return {
|
|
1205
|
+
success: true,
|
|
1206
|
+
resolved: paths,
|
|
1207
|
+
message: "All conflicts resolved and merge commit created."
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// src/utils/with-project-dir.ts
|
|
1212
|
+
async function withProjectDir(projectPath, fn) {
|
|
1213
|
+
const original = process.cwd();
|
|
1214
|
+
try {
|
|
1215
|
+
process.chdir(projectPath);
|
|
1216
|
+
return await fn();
|
|
1217
|
+
} finally {
|
|
1218
|
+
process.chdir(original);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// src/mcp.ts
|
|
1223
|
+
config({ path: ".env.local" });
|
|
1224
|
+
function textContent(text) {
|
|
1225
|
+
return { content: [{ type: "text", text }] };
|
|
1226
|
+
}
|
|
1227
|
+
function errorContent(message) {
|
|
1228
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
1229
|
+
}
|
|
1230
|
+
var server = new McpServer({
|
|
1231
|
+
name: "makerkit-cli",
|
|
1232
|
+
version: CLI_VERSION
|
|
1233
|
+
});
|
|
1234
|
+
server.registerTool(
|
|
1235
|
+
"makerkit_status",
|
|
1236
|
+
{
|
|
1237
|
+
description: "Project introspection: detect variant, git status, registry config, and plugin install status",
|
|
1238
|
+
inputSchema: {
|
|
1239
|
+
projectPath: z.string().describe("Absolute path to the MakerKit project root")
|
|
1240
|
+
}
|
|
1241
|
+
},
|
|
1242
|
+
async ({ projectPath }) => {
|
|
1243
|
+
try {
|
|
1244
|
+
const result = await withProjectDir(projectPath, () => getProjectStatus({ projectPath }));
|
|
1245
|
+
return textContent(JSON.stringify(result, null, 2));
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
return errorContent(error instanceof Error ? error.message : "Unknown error");
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
);
|
|
1251
|
+
server.registerTool(
|
|
1252
|
+
"makerkit_list_variants",
|
|
1253
|
+
{
|
|
1254
|
+
description: "List available MakerKit kit variants with metadata for the project creation wizard",
|
|
1255
|
+
inputSchema: {}
|
|
1256
|
+
},
|
|
1257
|
+
async () => {
|
|
1258
|
+
try {
|
|
1259
|
+
return textContent(JSON.stringify(listVariants(), null, 2));
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
return errorContent(error instanceof Error ? error.message : "Unknown error");
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
);
|
|
1265
|
+
server.registerTool(
|
|
1266
|
+
"makerkit_create_project",
|
|
1267
|
+
{
|
|
1268
|
+
description: "Create a new MakerKit project: clones the selected kit variant, installs dependencies, and writes a .makerkit/config.json marker file.",
|
|
1269
|
+
inputSchema: {
|
|
1270
|
+
variant: z.enum(VARIANT_CATALOG.map((v) => v.id)).describe("Kit variant to create"),
|
|
1271
|
+
name: z.string().min(1).describe("Project directory name"),
|
|
1272
|
+
directory: z.string().describe("Absolute path to the parent directory where the project will be created"),
|
|
1273
|
+
github_token: z.string().optional().describe("Optional GitHub PAT for HTTPS cloning (token is stripped from remote after clone)")
|
|
1274
|
+
}
|
|
1275
|
+
},
|
|
1276
|
+
async ({ variant, name, directory, github_token }) => {
|
|
1277
|
+
try {
|
|
1278
|
+
if (!path.isAbsolute(directory)) {
|
|
1279
|
+
return errorContent(
|
|
1280
|
+
`"directory" must be an absolute path. Received: "${directory}"`
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
const result = await createProject({
|
|
1284
|
+
variant,
|
|
1285
|
+
name,
|
|
1286
|
+
directory,
|
|
1287
|
+
githubToken: github_token
|
|
1288
|
+
});
|
|
1289
|
+
return textContent(
|
|
1290
|
+
JSON.stringify(
|
|
1291
|
+
{
|
|
1292
|
+
success: result.success,
|
|
1293
|
+
project_path: result.projectPath,
|
|
1294
|
+
variant: result.variant,
|
|
1295
|
+
message: result.message
|
|
1296
|
+
},
|
|
1297
|
+
null,
|
|
1298
|
+
2
|
|
1299
|
+
)
|
|
1300
|
+
);
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
return errorContent(error instanceof Error ? error.message : "Unknown error");
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
);
|
|
1306
|
+
server.registerTool(
|
|
1307
|
+
"makerkit_list_plugins",
|
|
1308
|
+
{
|
|
1309
|
+
description: "List all available plugins for the detected project variant with install status and metadata",
|
|
1310
|
+
inputSchema: {
|
|
1311
|
+
projectPath: z.string().describe("Absolute path to the MakerKit project root")
|
|
1312
|
+
}
|
|
1313
|
+
},
|
|
1314
|
+
async ({ projectPath }) => {
|
|
1315
|
+
try {
|
|
1316
|
+
const result = await withProjectDir(projectPath, () => listPlugins({ projectPath }));
|
|
1317
|
+
return textContent(JSON.stringify(result, null, 2));
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
return errorContent(error instanceof Error ? error.message : "Unknown error");
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
);
|
|
1323
|
+
server.registerTool(
|
|
1324
|
+
"makerkit_add_plugin",
|
|
1325
|
+
{
|
|
1326
|
+
description: "Install a MakerKit plugin: runs codemod, adds env vars, and returns structured result",
|
|
1327
|
+
inputSchema: {
|
|
1328
|
+
projectPath: z.string().describe("Absolute path to the MakerKit project root"),
|
|
1329
|
+
pluginId: z.string().describe("Plugin identifier (e.g. feedback, waitlist, posthog)"),
|
|
1330
|
+
githubUsername: z.string().optional().describe("GitHub username for registry auth (skips interactive prompt)"),
|
|
1331
|
+
skipGitCheck: z.boolean().optional().describe("Skip git clean check (useful when installing multiple plugins in sequence)")
|
|
1332
|
+
}
|
|
1333
|
+
},
|
|
1334
|
+
async ({ projectPath, pluginId, githubUsername, skipGitCheck }) => {
|
|
1335
|
+
try {
|
|
1336
|
+
const result = await withProjectDir(
|
|
1337
|
+
projectPath,
|
|
1338
|
+
() => addPlugin({ projectPath, pluginId, githubUsername, skipGitCheck })
|
|
1339
|
+
);
|
|
1340
|
+
if (!result.success) {
|
|
1341
|
+
return errorContent(result.reason);
|
|
1342
|
+
}
|
|
1343
|
+
return textContent(JSON.stringify(result, null, 2));
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
return errorContent(error instanceof Error ? error.message : "Unknown error");
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
);
|
|
1349
|
+
server.registerTool(
|
|
1350
|
+
"makerkit_init_registry",
|
|
1351
|
+
{
|
|
1352
|
+
description: "Cache the GitHub username used for MakerKit plugin registry authentication",
|
|
1353
|
+
inputSchema: {
|
|
1354
|
+
projectPath: z.string().describe("Absolute path to the MakerKit project root"),
|
|
1355
|
+
githubUsername: z.string().describe("GitHub username registered with your MakerKit account")
|
|
1356
|
+
}
|
|
1357
|
+
},
|
|
1358
|
+
async ({ projectPath, githubUsername }) => {
|
|
1359
|
+
try {
|
|
1360
|
+
const result = await withProjectDir(
|
|
1361
|
+
projectPath,
|
|
1362
|
+
() => initRegistry({ projectPath, githubUsername })
|
|
1363
|
+
);
|
|
1364
|
+
return textContent(JSON.stringify(result, null, 2));
|
|
1365
|
+
} catch (error) {
|
|
1366
|
+
return errorContent(error instanceof Error ? error.message : "Unknown error");
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
);
|
|
1370
|
+
server.registerTool(
|
|
1371
|
+
"makerkit_check_update",
|
|
1372
|
+
{
|
|
1373
|
+
description: "Analyze a plugin update using three-way diff (base/local/remote). Returns per-file status and content for AI-powered merge resolution.",
|
|
1374
|
+
inputSchema: {
|
|
1375
|
+
projectPath: z.string().describe("Absolute path to the MakerKit project root"),
|
|
1376
|
+
pluginId: z.string().describe("Plugin identifier (e.g. feedback, waitlist)"),
|
|
1377
|
+
githubUsername: z.string().optional().describe("GitHub username for registry auth (uses cached if omitted)")
|
|
1378
|
+
}
|
|
1379
|
+
},
|
|
1380
|
+
async ({ projectPath, pluginId, githubUsername }) => {
|
|
1381
|
+
try {
|
|
1382
|
+
const result = await withProjectDir(
|
|
1383
|
+
projectPath,
|
|
1384
|
+
() => checkPluginUpdate({ projectPath, pluginId, githubUsername })
|
|
1385
|
+
);
|
|
1386
|
+
if (!result.success) {
|
|
1387
|
+
return errorContent(result.reason);
|
|
1388
|
+
}
|
|
1389
|
+
return textContent(
|
|
1390
|
+
JSON.stringify(
|
|
1391
|
+
{
|
|
1392
|
+
...result,
|
|
1393
|
+
note: "For conflict files, produce a merged version and pass it to makerkit_apply_update."
|
|
1394
|
+
},
|
|
1395
|
+
null,
|
|
1396
|
+
2
|
|
1397
|
+
)
|
|
1398
|
+
);
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
return errorContent(error instanceof Error ? error.message : "Unknown error");
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
);
|
|
1404
|
+
server.registerTool(
|
|
1405
|
+
"makerkit_apply_update",
|
|
1406
|
+
{
|
|
1407
|
+
description: "Apply AI-resolved plugin update files. Writes merged content to disk and updates base versions for future three-way merges.",
|
|
1408
|
+
inputSchema: {
|
|
1409
|
+
projectPath: z.string().describe("Absolute path to the MakerKit project root"),
|
|
1410
|
+
pluginId: z.string().describe("Plugin identifier"),
|
|
1411
|
+
files: z.array(
|
|
1412
|
+
z.object({
|
|
1413
|
+
path: z.string().describe("File path relative to project root (from check_update)"),
|
|
1414
|
+
content: z.string().optional().describe("Resolved file content (required for write action)"),
|
|
1415
|
+
action: z.enum(["write", "skip", "delete"]).describe(
|
|
1416
|
+
"write: write content to disk, skip: keep local version, delete: remove file from disk"
|
|
1417
|
+
)
|
|
1418
|
+
})
|
|
1419
|
+
).describe("Array of file resolutions"),
|
|
1420
|
+
installDependencies: z.boolean().optional().describe("Whether to install plugin dependencies (default true)"),
|
|
1421
|
+
githubUsername: z.string().optional().describe("GitHub username for registry auth (uses cached if omitted)")
|
|
1422
|
+
}
|
|
1423
|
+
},
|
|
1424
|
+
async ({ projectPath, pluginId, files, installDependencies, githubUsername }) => {
|
|
1425
|
+
try {
|
|
1426
|
+
const result = await withProjectDir(
|
|
1427
|
+
projectPath,
|
|
1428
|
+
() => applyPluginUpdate({ projectPath, pluginId, files, installDependencies, githubUsername })
|
|
1429
|
+
);
|
|
1430
|
+
if (!result.success) {
|
|
1431
|
+
return errorContent(result.reason);
|
|
1432
|
+
}
|
|
1433
|
+
return textContent(
|
|
1434
|
+
JSON.stringify(
|
|
1435
|
+
{
|
|
1436
|
+
...result,
|
|
1437
|
+
note: "Base versions updated. Run makerkit_check_update again to verify all files show as unchanged."
|
|
1438
|
+
},
|
|
1439
|
+
null,
|
|
1440
|
+
2
|
|
1441
|
+
)
|
|
1442
|
+
);
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
return errorContent(error instanceof Error ? error.message : "Unknown error");
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
);
|
|
1448
|
+
server.registerTool(
|
|
1449
|
+
"makerkit_project_pull",
|
|
1450
|
+
{
|
|
1451
|
+
description: "Pull latest upstream changes into a MakerKit project. Auto-detects kit variant, configures the upstream remote (SSH or HTTPS), fetches, and merges. Returns conflict details with base/local/remote content when merge conflicts occur so the AI can resolve them. After resolving, call makerkit_project_resolve_conflicts.",
|
|
1452
|
+
inputSchema: {
|
|
1453
|
+
projectPath: z.string().describe("Absolute path to the MakerKit project root")
|
|
1454
|
+
}
|
|
1455
|
+
},
|
|
1456
|
+
async ({ projectPath }) => {
|
|
1457
|
+
try {
|
|
1458
|
+
const result = await withProjectDir(projectPath, () => projectPull({ projectPath }));
|
|
1459
|
+
if (!result.success) {
|
|
1460
|
+
if ("hasConflicts" in result) {
|
|
1461
|
+
return textContent(JSON.stringify(result, null, 2));
|
|
1462
|
+
}
|
|
1463
|
+
return errorContent(result.reason);
|
|
1464
|
+
}
|
|
1465
|
+
return textContent(JSON.stringify(result, null, 2));
|
|
1466
|
+
} catch (error) {
|
|
1467
|
+
return errorContent(error instanceof Error ? error.message : "Unknown error");
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
);
|
|
1471
|
+
server.registerTool(
|
|
1472
|
+
"makerkit_project_resolve_conflicts",
|
|
1473
|
+
{
|
|
1474
|
+
description: "Resolve merge conflicts from makerkit_project_pull. Write resolved file contents, stage them, and complete the merge commit.",
|
|
1475
|
+
inputSchema: {
|
|
1476
|
+
projectPath: z.string().describe("Absolute path to the MakerKit project root"),
|
|
1477
|
+
files: z.array(
|
|
1478
|
+
z.object({
|
|
1479
|
+
path: z.string().describe("File path relative to project root"),
|
|
1480
|
+
content: z.string().describe("Resolved file content")
|
|
1481
|
+
})
|
|
1482
|
+
).describe("Array of resolved files"),
|
|
1483
|
+
commitMessage: z.string().optional().describe(
|
|
1484
|
+
"Custom merge commit message (defaults to auto-generated merge message)"
|
|
1485
|
+
)
|
|
1486
|
+
}
|
|
1487
|
+
},
|
|
1488
|
+
async ({ projectPath, files, commitMessage }) => {
|
|
1489
|
+
try {
|
|
1490
|
+
const result = await withProjectDir(
|
|
1491
|
+
projectPath,
|
|
1492
|
+
() => resolveConflicts({ projectPath, files, commitMessage })
|
|
1493
|
+
);
|
|
1494
|
+
if (!result.success) {
|
|
1495
|
+
return textContent(JSON.stringify(result, null, 2));
|
|
1496
|
+
}
|
|
1497
|
+
return textContent(JSON.stringify(result, null, 2));
|
|
1498
|
+
} catch (error) {
|
|
1499
|
+
return errorContent(error instanceof Error ? error.message : "Unknown error");
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
);
|
|
1503
|
+
async function main() {
|
|
1504
|
+
const transport = new StdioServerTransport();
|
|
1505
|
+
await server.connect(transport);
|
|
1506
|
+
}
|
|
1507
|
+
void main();
|
|
1508
|
+
//# sourceMappingURL=mcp.js.map
|