multisite-cms-mcp 1.6.2 → 1.7.1
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 +55 -4
- package/dist/index.js +34 -5
- package/dist/lib/context-fetcher.d.ts +67 -0
- package/dist/lib/context-fetcher.d.ts.map +1 -0
- package/dist/lib/context-fetcher.js +190 -0
- package/dist/tools/deploy-package.d.ts.map +1 -1
- package/dist/tools/deploy-package.js +82 -0
- package/dist/tools/get-conversion-guide.d.ts.map +1 -1
- package/dist/tools/get-conversion-guide.js +85 -5
- package/dist/tools/get-field-types.d.ts.map +1 -1
- package/dist/tools/get-field-types.js +5 -0
- package/dist/tools/get-started.d.ts +21 -0
- package/dist/tools/get-started.d.ts.map +1 -0
- package/dist/tools/get-started.js +624 -0
- package/dist/tools/sync-schema.d.ts.map +1 -1
- package/dist/tools/sync-schema.js +50 -0
- package/dist/tools/validate-manifest.d.ts.map +1 -1
- package/dist/tools/validate-manifest.js +48 -11
- package/dist/tools/validate-package.d.ts.map +1 -1
- package/dist/tools/validate-package.js +52 -6
- package/dist/tools/validate-template.d.ts.map +1 -1
- package/dist/tools/validate-template.js +93 -10
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -27,12 +27,34 @@ Fast Mode is a modern CMS platform that turns static HTML websites into fully ed
|
|
|
27
27
|
|
|
28
28
|
This MCP server provides tools for AI assistants (Claude, Cursor, etc.) to help you convert websites to Fast Mode format and deploy them.
|
|
29
29
|
|
|
30
|
+
### IMPORTANT: Start with get_started
|
|
31
|
+
|
|
32
|
+
The `get_started` tool is your entry point for any task. It automatically:
|
|
33
|
+
- Checks authentication status
|
|
34
|
+
- Lists your projects with status
|
|
35
|
+
- Shows collection/field details
|
|
36
|
+
- Returns the exact workflow for your task
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
get_started(intent: "add_content", projectId: "my-project")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Available intents:**
|
|
43
|
+
| Intent | Use Case |
|
|
44
|
+
|--------|----------|
|
|
45
|
+
| `explore` | See what projects and content exist |
|
|
46
|
+
| `add_content` | Create/edit CMS items (blog posts, team, etc.) |
|
|
47
|
+
| `update_schema` | Add collections or fields |
|
|
48
|
+
| `convert` | Build a new website from scratch |
|
|
49
|
+
| `deploy` | Push changes live |
|
|
50
|
+
|
|
30
51
|
### Validation Tools (No Auth Required)
|
|
31
52
|
|
|
32
53
|
These tools work without authentication — perfect for converting and validating websites before deploying:
|
|
33
54
|
|
|
34
55
|
| Tool | Description |
|
|
35
56
|
|------|-------------|
|
|
57
|
+
| `get_started` | **CALL FIRST** - Checks state and returns exact workflow |
|
|
36
58
|
| `get_field_types` | Get available field types for creating custom fields |
|
|
37
59
|
| `validate_manifest` | Validate your manifest.json file |
|
|
38
60
|
| `validate_template` | Check HTML templates for correct token usage, form handling, and schema validation |
|
|
@@ -131,9 +153,15 @@ Add to your Claude config:
|
|
|
131
153
|
|
|
132
154
|
## Quick Start
|
|
133
155
|
|
|
134
|
-
### 1.
|
|
156
|
+
### 1. Start with get_started
|
|
157
|
+
|
|
158
|
+
Ask your AI assistant what you want to do:
|
|
135
159
|
|
|
136
|
-
|
|
160
|
+
> "I want to add some blog posts to my Fast Mode site"
|
|
161
|
+
|
|
162
|
+
The AI will call `get_started(intent: "add_content")` to see your projects and schema.
|
|
163
|
+
|
|
164
|
+
### 2. Convert a Website
|
|
137
165
|
|
|
138
166
|
> "Convert this website to Fast Mode format"
|
|
139
167
|
|
|
@@ -207,11 +235,26 @@ When you validate a template with a project ID, the tool will:
|
|
|
207
235
|
| `datetime` | Date + time picker |
|
|
208
236
|
| `image` | Image uploads (heroImage, thumbnail, photo) |
|
|
209
237
|
| `url` | Links (website, social profiles) |
|
|
210
|
-
| `videoEmbed` |
|
|
238
|
+
| `videoEmbed` | **Use for video content** - YouTube, Vimeo, Wistia, Loom. Template: `{{#videoEmbed field}}{{/videoEmbed}}` |
|
|
211
239
|
| `email` | Email addresses |
|
|
212
240
|
| `select` | Single dropdown choice (category, status) |
|
|
213
241
|
| `multiSelect` | Multiple dropdown choices (tags) |
|
|
214
|
-
| `relation` |
|
|
242
|
+
| `relation` | **Use for linked content** (author → authors). Template: `{{author.name}}`, `{{author.url}}` |
|
|
243
|
+
|
|
244
|
+
### Field Type Tips
|
|
245
|
+
|
|
246
|
+
The MCP server now provides intelligent field type suggestions when you use sync_schema:
|
|
247
|
+
|
|
248
|
+
**For Video Content:**
|
|
249
|
+
- Use `videoEmbed` type (NOT `url` or `text`)
|
|
250
|
+
- Outputs responsive iframes with correct YouTube settings
|
|
251
|
+
- Template: `{{#videoEmbed videoFieldName}}{{/videoEmbed}}`
|
|
252
|
+
|
|
253
|
+
**For Linked Content:**
|
|
254
|
+
- Use `relation` type (NOT `text`)
|
|
255
|
+
- Links to items in another collection
|
|
256
|
+
- Access with dot notation: `{{author.name}}`, `{{category.url}}`
|
|
257
|
+
- Requires `referenceCollection` parameter
|
|
215
258
|
|
|
216
259
|
### Example: Adding Fields to Existing Collection
|
|
217
260
|
|
|
@@ -420,6 +463,14 @@ Use the `get_conversion_guide` tool for detailed instructions.
|
|
|
420
463
|
|
|
421
464
|
## Changelog
|
|
422
465
|
|
|
466
|
+
### v1.7.0
|
|
467
|
+
- **get_started Tool** — New intelligent entry point that detects project state and returns exact workflows
|
|
468
|
+
- **Deploy Gate** — `deploy_package` now validates internally and BLOCKS deployment if there are errors
|
|
469
|
+
- **Stricter Validations** — Missing loops, wrong collections, assets outside public/ are now errors (not warnings)
|
|
470
|
+
- **Field Type Suggestions** — sync_schema provides tips when field names suggest better types (e.g., "video" → videoEmbed)
|
|
471
|
+
- **Smart Detection** — validate_template detects video content and relation patterns with guidance
|
|
472
|
+
- **Server Instructions** — Agents see guidance to call get_started first when connecting
|
|
473
|
+
|
|
423
474
|
### v1.6.0
|
|
424
475
|
- **CMS Item Management** — New tools: `create_cms_item`, `list_cms_items`, `get_cms_item`, `update_cms_item`, `delete_cms_item`
|
|
425
476
|
- **Delete Safety** — `delete_cms_item` requires explicit `confirmDelete: true` and prompts AI to ask user for permission
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,10 @@ const deploy_package_1 = require("./tools/deploy-package");
|
|
|
16
16
|
const get_field_types_1 = require("./tools/get-field-types");
|
|
17
17
|
const sync_schema_1 = require("./tools/sync-schema");
|
|
18
18
|
const generate_samples_1 = require("./tools/generate-samples");
|
|
19
|
+
const get_started_1 = require("./tools/get-started");
|
|
19
20
|
const cms_items_1 = require("./tools/cms-items");
|
|
21
|
+
// Server instructions - embedded in tool descriptions since MCP SDK doesn't have a dedicated field
|
|
22
|
+
// The get_started tool description acts as the primary entry point guidance for agents
|
|
20
23
|
const server = new index_js_1.Server({
|
|
21
24
|
name: 'multisite-cms',
|
|
22
25
|
version: '1.0.0',
|
|
@@ -27,6 +30,26 @@ const server = new index_js_1.Server({
|
|
|
27
30
|
});
|
|
28
31
|
// Define available tools
|
|
29
32
|
const TOOLS = [
|
|
33
|
+
// get_started is the FIRST tool - agents should call this first
|
|
34
|
+
{
|
|
35
|
+
name: 'get_started',
|
|
36
|
+
description: 'CALL THIS FIRST. Intelligent entry point that checks authentication, lists your projects, and returns the exact workflow for your task. Provides schema details, field types, and example tool calls with real values.',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
intent: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
enum: ['explore', 'add_content', 'update_schema', 'convert', 'deploy'],
|
|
43
|
+
description: 'What you want to do: explore (see projects), add_content (create/edit items), update_schema (add collections/fields), convert (new website), deploy (push changes)',
|
|
44
|
+
},
|
|
45
|
+
projectId: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
description: 'Project ID or name to get detailed schema and workflow for',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
required: [],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
30
53
|
{
|
|
31
54
|
name: 'validate_manifest',
|
|
32
55
|
description: 'Validate a manifest.json file for the CMS package. Returns errors if the structure is incorrect or required fields are missing.',
|
|
@@ -193,7 +216,7 @@ const TOOLS = [
|
|
|
193
216
|
},
|
|
194
217
|
{
|
|
195
218
|
name: 'deploy_package',
|
|
196
|
-
description: '
|
|
219
|
+
description: 'FIRST: Call get_started(intent: "deploy") to check project state. Validates and deploys a website package (.zip file). BLOCKS deployment if validation errors exist. Requires a projectId. Will check for GitHub sync and block if connected. Use force:true to override GitHub check.',
|
|
197
220
|
inputSchema: {
|
|
198
221
|
type: 'object',
|
|
199
222
|
properties: {
|
|
@@ -224,7 +247,7 @@ const TOOLS = [
|
|
|
224
247
|
},
|
|
225
248
|
{
|
|
226
249
|
name: 'sync_schema',
|
|
227
|
-
description: '
|
|
250
|
+
description: 'FIRST: Call get_started(intent: "update_schema") to see current schema. Creates collections and fields in Fast Mode. Uses two-phase creation for relation fields. Skips duplicates. Provides field type suggestions. Call this BEFORE deploying. Requires authentication.',
|
|
228
251
|
inputSchema: {
|
|
229
252
|
type: 'object',
|
|
230
253
|
properties: {
|
|
@@ -316,7 +339,7 @@ const TOOLS = [
|
|
|
316
339
|
},
|
|
317
340
|
{
|
|
318
341
|
name: 'create_cms_item',
|
|
319
|
-
description: '
|
|
342
|
+
description: 'FIRST: Call get_started(intent: "add_content") to see collections and field types. Creates a new CMS item. For relation fields, pass the related item ID (not name). For richText fields, use HTML content. Requires authentication.',
|
|
320
343
|
inputSchema: {
|
|
321
344
|
type: 'object',
|
|
322
345
|
properties: {
|
|
@@ -403,7 +426,7 @@ const TOOLS = [
|
|
|
403
426
|
},
|
|
404
427
|
{
|
|
405
428
|
name: 'update_cms_item',
|
|
406
|
-
description: '
|
|
429
|
+
description: 'FIRST: Call get_started(intent: "add_content") to see collections and field types. Updates an existing CMS item. Only provided fields are changed. Requires authentication.',
|
|
407
430
|
inputSchema: {
|
|
408
431
|
type: 'object',
|
|
409
432
|
properties: {
|
|
@@ -437,7 +460,7 @@ const TOOLS = [
|
|
|
437
460
|
},
|
|
438
461
|
{
|
|
439
462
|
name: 'delete_cms_item',
|
|
440
|
-
description: '
|
|
463
|
+
description: 'FIRST: Call get_started(intent: "add_content"). REQUIRES explicit user confirmation before calling. Ask the user to confirm deletion first. Never delete without user confirmation. This action cannot be undone.',
|
|
441
464
|
inputSchema: {
|
|
442
465
|
type: 'object',
|
|
443
466
|
properties: {
|
|
@@ -473,6 +496,12 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
473
496
|
try {
|
|
474
497
|
let result;
|
|
475
498
|
switch (name) {
|
|
499
|
+
case 'get_started':
|
|
500
|
+
result = await (0, get_started_1.getStarted)({
|
|
501
|
+
intent: params.intent,
|
|
502
|
+
projectId: params.projectId,
|
|
503
|
+
});
|
|
504
|
+
break;
|
|
476
505
|
case 'validate_manifest':
|
|
477
506
|
result = await (0, validate_manifest_1.validateManifest)(params.manifest);
|
|
478
507
|
break;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Fetcher
|
|
3
|
+
*
|
|
4
|
+
* Centralizes logic for fetching project state, collections, and content.
|
|
5
|
+
* Used by get_started and other tools that need project context.
|
|
6
|
+
*/
|
|
7
|
+
export interface ProjectSummary {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
subdomain: string;
|
|
11
|
+
customDomain: string | null;
|
|
12
|
+
status: 'pending' | 'building' | 'published' | 'failed';
|
|
13
|
+
hasCollections: boolean;
|
|
14
|
+
collectionCount: number;
|
|
15
|
+
hasContent: boolean;
|
|
16
|
+
totalItems: number;
|
|
17
|
+
hasPendingChanges: boolean;
|
|
18
|
+
lastDeployedAt: string | null;
|
|
19
|
+
githubConnected: boolean;
|
|
20
|
+
githubRepo: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface CollectionField {
|
|
23
|
+
slug: string;
|
|
24
|
+
name: string;
|
|
25
|
+
type: string;
|
|
26
|
+
isRequired: boolean;
|
|
27
|
+
referenceCollection?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface CollectionSummary {
|
|
30
|
+
slug: string;
|
|
31
|
+
name: string;
|
|
32
|
+
nameSingular: string;
|
|
33
|
+
fieldCount: number;
|
|
34
|
+
itemCount: number;
|
|
35
|
+
fields: CollectionField[];
|
|
36
|
+
}
|
|
37
|
+
export interface ProjectDetail {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
subdomain: string;
|
|
41
|
+
customDomain: string | null;
|
|
42
|
+
status: 'pending' | 'building' | 'published' | 'failed';
|
|
43
|
+
collections: CollectionSummary[];
|
|
44
|
+
hasPendingChanges: boolean;
|
|
45
|
+
lastDeployedAt: string | null;
|
|
46
|
+
githubConnected: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface ProjectContext {
|
|
49
|
+
isAuthenticated: boolean;
|
|
50
|
+
projects: ProjectSummary[];
|
|
51
|
+
selectedProject?: ProjectDetail;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Fetch complete project context
|
|
55
|
+
*
|
|
56
|
+
* @param projectId - Optional specific project to get detailed info for
|
|
57
|
+
*/
|
|
58
|
+
export declare function fetchProjectContext(projectId?: string): Promise<ProjectContext>;
|
|
59
|
+
/**
|
|
60
|
+
* Resolve a project identifier to a tenant ID
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveProjectId(projectIdentifier: string): Promise<{
|
|
63
|
+
tenantId: string;
|
|
64
|
+
name: string;
|
|
65
|
+
subdomain: string;
|
|
66
|
+
} | null>;
|
|
67
|
+
//# sourceMappingURL=context-fetcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-fetcher.d.ts","sourceRoot":"","sources":["../../src/lib/context-fetcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC;IACxD,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,OAAO,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC;IACxD,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,eAAe,CAAC,EAAE,aAAa,CAAC;CACjC;AAgHD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA6GrF;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAsCvI"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Context Fetcher
|
|
4
|
+
*
|
|
5
|
+
* Centralizes logic for fetching project state, collections, and content.
|
|
6
|
+
* Used by get_started and other tools that need project context.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.fetchProjectContext = fetchProjectContext;
|
|
10
|
+
exports.resolveProjectId = resolveProjectId;
|
|
11
|
+
const api_client_1 = require("./api-client");
|
|
12
|
+
// ============ Helper Functions ============
|
|
13
|
+
/**
|
|
14
|
+
* Fetch all projects for the authenticated user
|
|
15
|
+
*/
|
|
16
|
+
async function fetchProjects() {
|
|
17
|
+
const response = await (0, api_client_1.apiRequest)('/api/tenants');
|
|
18
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
return response.data;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Fetch collections for a specific project
|
|
25
|
+
*/
|
|
26
|
+
async function fetchCollections(tenantId) {
|
|
27
|
+
const response = await (0, api_client_1.apiRequest)('/api/collections', { tenantId });
|
|
28
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
return response.data;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Fetch GitHub connection status for a project
|
|
35
|
+
*/
|
|
36
|
+
async function fetchGitHubStatus(tenantId) {
|
|
37
|
+
const response = await (0, api_client_1.apiRequest)('/api/github/site-connection', { tenantId });
|
|
38
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return response.data;
|
|
42
|
+
}
|
|
43
|
+
// Note: fetchLatestDeploy could be used for detailed deploy state in the future
|
|
44
|
+
// async function fetchLatestDeploy(tenantId: string): Promise<DeployLog | null> {
|
|
45
|
+
// const response = await apiRequest<DeployLog[]>('/api/deploy/history?limit=1', { tenantId });
|
|
46
|
+
// if (isApiError(response) || !response.data || response.data.length === 0) {
|
|
47
|
+
// return null;
|
|
48
|
+
// }
|
|
49
|
+
// return response.data[0];
|
|
50
|
+
// }
|
|
51
|
+
/**
|
|
52
|
+
* Fetch item counts for collections
|
|
53
|
+
*/
|
|
54
|
+
async function fetchCollectionItemCounts(tenantId, collectionSlugs) {
|
|
55
|
+
const counts = new Map();
|
|
56
|
+
// Fetch counts in parallel (limit concurrency)
|
|
57
|
+
const promises = collectionSlugs.slice(0, 10).map(async (slug) => {
|
|
58
|
+
const response = await (0, api_client_1.apiRequest)(`/api/collections/${slug}/items?limit=0`, { tenantId });
|
|
59
|
+
if (!(0, api_client_1.isApiError)(response) && response.data) {
|
|
60
|
+
counts.set(slug, response.data.total || 0);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
await Promise.all(promises);
|
|
64
|
+
return counts;
|
|
65
|
+
}
|
|
66
|
+
// ============ Main Functions ============
|
|
67
|
+
/**
|
|
68
|
+
* Fetch complete project context
|
|
69
|
+
*
|
|
70
|
+
* @param projectId - Optional specific project to get detailed info for
|
|
71
|
+
*/
|
|
72
|
+
async function fetchProjectContext(projectId) {
|
|
73
|
+
// Check authentication
|
|
74
|
+
const isAuthenticated = !(await (0, api_client_1.needsAuthentication)());
|
|
75
|
+
if (!isAuthenticated) {
|
|
76
|
+
return {
|
|
77
|
+
isAuthenticated: false,
|
|
78
|
+
projects: [],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Fetch all projects
|
|
82
|
+
const tenants = await fetchProjects();
|
|
83
|
+
// Build project summaries
|
|
84
|
+
const projects = await Promise.all(tenants.map(async (tenant) => {
|
|
85
|
+
// Fetch collections for this project (for count)
|
|
86
|
+
const collections = await fetchCollections(tenant.id);
|
|
87
|
+
const collectionCount = collections.length;
|
|
88
|
+
// Calculate total items
|
|
89
|
+
let totalItems = 0;
|
|
90
|
+
for (const col of collections) {
|
|
91
|
+
if (col._count?.items) {
|
|
92
|
+
totalItems += col._count.items;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Fetch GitHub status
|
|
96
|
+
const github = await fetchGitHubStatus(tenant.id);
|
|
97
|
+
return {
|
|
98
|
+
id: tenant.id,
|
|
99
|
+
name: tenant.name,
|
|
100
|
+
subdomain: tenant.subdomain,
|
|
101
|
+
customDomain: tenant.customDomain || null,
|
|
102
|
+
status: (tenant.site?.status || 'pending'),
|
|
103
|
+
hasCollections: collectionCount > 0,
|
|
104
|
+
collectionCount,
|
|
105
|
+
hasContent: totalItems > 0,
|
|
106
|
+
totalItems,
|
|
107
|
+
hasPendingChanges: tenant.site?.hasPendingChanges || false,
|
|
108
|
+
lastDeployedAt: tenant.site?.updatedAt || null,
|
|
109
|
+
githubConnected: github?.connected || false,
|
|
110
|
+
githubRepo: github?.repo || null,
|
|
111
|
+
};
|
|
112
|
+
}));
|
|
113
|
+
// If a specific project is requested, get detailed info
|
|
114
|
+
let selectedProject;
|
|
115
|
+
if (projectId) {
|
|
116
|
+
// Find the project
|
|
117
|
+
const tenant = tenants.find(t => t.id === projectId ||
|
|
118
|
+
t.name.toLowerCase() === projectId.toLowerCase() ||
|
|
119
|
+
t.subdomain.toLowerCase() === projectId.toLowerCase());
|
|
120
|
+
if (tenant) {
|
|
121
|
+
// Fetch collections with full field details
|
|
122
|
+
const collections = await fetchCollections(tenant.id);
|
|
123
|
+
// Fetch item counts
|
|
124
|
+
const itemCounts = await fetchCollectionItemCounts(tenant.id, collections.map(c => c.slug));
|
|
125
|
+
// Fetch GitHub status
|
|
126
|
+
const github = await fetchGitHubStatus(tenant.id);
|
|
127
|
+
// Build collection summaries
|
|
128
|
+
const collectionSummaries = collections.map(col => ({
|
|
129
|
+
slug: col.slug,
|
|
130
|
+
name: col.name,
|
|
131
|
+
nameSingular: col.nameSingular,
|
|
132
|
+
fieldCount: col.fields.length,
|
|
133
|
+
itemCount: itemCounts.get(col.slug) || col._count?.items || 0,
|
|
134
|
+
fields: col.fields.map(f => ({
|
|
135
|
+
slug: f.slug,
|
|
136
|
+
name: f.name,
|
|
137
|
+
type: f.type,
|
|
138
|
+
isRequired: f.isRequired || false,
|
|
139
|
+
referenceCollection: f.referenceCollection,
|
|
140
|
+
})),
|
|
141
|
+
}));
|
|
142
|
+
selectedProject = {
|
|
143
|
+
id: tenant.id,
|
|
144
|
+
name: tenant.name,
|
|
145
|
+
subdomain: tenant.subdomain,
|
|
146
|
+
customDomain: tenant.customDomain || null,
|
|
147
|
+
status: (tenant.site?.status || 'pending'),
|
|
148
|
+
collections: collectionSummaries,
|
|
149
|
+
hasPendingChanges: tenant.site?.hasPendingChanges || false,
|
|
150
|
+
lastDeployedAt: tenant.site?.updatedAt || null,
|
|
151
|
+
githubConnected: github?.connected || false,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
isAuthenticated: true,
|
|
157
|
+
projects,
|
|
158
|
+
selectedProject,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Resolve a project identifier to a tenant ID
|
|
163
|
+
*/
|
|
164
|
+
async function resolveProjectId(projectIdentifier) {
|
|
165
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
166
|
+
const tenants = await fetchProjects();
|
|
167
|
+
// Try exact UUID match
|
|
168
|
+
if (uuidPattern.test(projectIdentifier)) {
|
|
169
|
+
const tenant = tenants.find(t => t.id === projectIdentifier);
|
|
170
|
+
if (tenant) {
|
|
171
|
+
return { tenantId: tenant.id, name: tenant.name, subdomain: tenant.subdomain };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Try exact name match
|
|
175
|
+
const exactMatch = tenants.find(t => t.name.toLowerCase() === projectIdentifier.toLowerCase());
|
|
176
|
+
if (exactMatch) {
|
|
177
|
+
return { tenantId: exactMatch.id, name: exactMatch.name, subdomain: exactMatch.subdomain };
|
|
178
|
+
}
|
|
179
|
+
// Try subdomain match
|
|
180
|
+
const subdomainMatch = tenants.find(t => t.subdomain.toLowerCase() === projectIdentifier.toLowerCase());
|
|
181
|
+
if (subdomainMatch) {
|
|
182
|
+
return { tenantId: subdomainMatch.id, name: subdomainMatch.name, subdomain: subdomainMatch.subdomain };
|
|
183
|
+
}
|
|
184
|
+
// Try partial match
|
|
185
|
+
const partialMatch = tenants.find(t => t.name.toLowerCase().includes(projectIdentifier.toLowerCase()));
|
|
186
|
+
if (partialMatch) {
|
|
187
|
+
return { tenantId: partialMatch.id, name: partialMatch.name, subdomain: partialMatch.subdomain };
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy-package.d.ts","sourceRoot":"","sources":["../../src/tools/deploy-package.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deploy-package.d.ts","sourceRoot":"","sources":["../../src/tools/deploy-package.ts"],"names":[],"mappings":"AA4QA;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,EAClB,KAAK,CAAC,EAAE,OAAO,GACd,OAAO,CAAC,MAAM,CAAC,CA2LjB"}
|
|
@@ -32,13 +32,18 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.deployPackage = deployPackage;
|
|
37
40
|
const fs = __importStar(require("fs"));
|
|
38
41
|
const path = __importStar(require("path"));
|
|
42
|
+
const adm_zip_1 = __importDefault(require("adm-zip"));
|
|
39
43
|
const api_client_1 = require("../lib/api-client");
|
|
40
44
|
const device_flow_1 = require("../lib/device-flow");
|
|
41
45
|
const credentials_1 = require("../lib/credentials");
|
|
46
|
+
const validate_package_1 = require("./validate-package");
|
|
42
47
|
/**
|
|
43
48
|
* List existing projects for the authenticated user
|
|
44
49
|
*/
|
|
@@ -280,6 +285,83 @@ Use \`list_projects\` to see available projects, or \`create_site\` to create a
|
|
|
280
285
|
return formatGitHubBlockMessage(githubStatus.repo, githubStatus.branch || 'main', subdomain);
|
|
281
286
|
}
|
|
282
287
|
}
|
|
288
|
+
// ============ PRE-DEPLOY VALIDATION GATE ============
|
|
289
|
+
// Validate the package BEFORE uploading to catch errors early
|
|
290
|
+
try {
|
|
291
|
+
const zip = new adm_zip_1.default(zipBuffer);
|
|
292
|
+
const entries = zip.getEntries();
|
|
293
|
+
// Build file list from zip
|
|
294
|
+
const fileList = entries.map(e => e.entryName);
|
|
295
|
+
// Find and read manifest.json
|
|
296
|
+
const manifestEntry = entries.find(e => e.entryName === 'manifest.json' ||
|
|
297
|
+
e.entryName.endsWith('/manifest.json'));
|
|
298
|
+
if (!manifestEntry) {
|
|
299
|
+
return `# DEPLOYMENT BLOCKED
|
|
300
|
+
|
|
301
|
+
**Reason:** Missing manifest.json
|
|
302
|
+
|
|
303
|
+
Every Fast Mode package requires a manifest.json at the root.
|
|
304
|
+
|
|
305
|
+
Run \`get_example("manifest_basic")\` to see the required format.
|
|
306
|
+
|
|
307
|
+
## Expected Package Structure
|
|
308
|
+
|
|
309
|
+
\`\`\`
|
|
310
|
+
your-site.zip
|
|
311
|
+
├── manifest.json <- REQUIRED
|
|
312
|
+
├── pages/
|
|
313
|
+
│ ├── index.html
|
|
314
|
+
│ └── ...
|
|
315
|
+
├── public/
|
|
316
|
+
│ └── css/, js/, images/
|
|
317
|
+
└── templates/ (optional, for CMS)
|
|
318
|
+
\`\`\`
|
|
319
|
+
`;
|
|
320
|
+
}
|
|
321
|
+
const manifestContent = manifestEntry.getData().toString('utf8');
|
|
322
|
+
// Collect template contents for validation
|
|
323
|
+
const templateContents = {};
|
|
324
|
+
for (const entry of entries) {
|
|
325
|
+
if (entry.entryName.endsWith('.html') && !entry.isDirectory) {
|
|
326
|
+
try {
|
|
327
|
+
templateContents[entry.entryName] = entry.getData().toString('utf8');
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
// Skip files that can't be read
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
// Run full package validation
|
|
335
|
+
const validationResult = await (0, validate_package_1.validatePackage)(fileList, manifestContent, templateContents);
|
|
336
|
+
// Only block on actual ERRORS, not warnings
|
|
337
|
+
if (validationResult.includes('HAS ERRORS') || validationResult.includes('INVALID')) {
|
|
338
|
+
return `# DEPLOYMENT BLOCKED
|
|
339
|
+
|
|
340
|
+
Your package has validation errors that must be fixed before deployment.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
${validationResult}
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Next Steps
|
|
349
|
+
|
|
350
|
+
1. Fix the errors listed above
|
|
351
|
+
2. Rebuild your .zip package
|
|
352
|
+
3. Run \`deploy_package\` again
|
|
353
|
+
|
|
354
|
+
Use \`validate_template\` and \`validate_manifest\` to check individual files.
|
|
355
|
+
`;
|
|
356
|
+
}
|
|
357
|
+
// Validation passed - include summary in success response later
|
|
358
|
+
console.error('Pre-deploy validation passed');
|
|
359
|
+
}
|
|
360
|
+
catch (validationError) {
|
|
361
|
+
// If validation itself fails (e.g., corrupt zip), log but continue
|
|
362
|
+
// The upload will fail with a more specific error if the package is truly broken
|
|
363
|
+
console.error('Pre-deploy validation error:', validationError);
|
|
364
|
+
}
|
|
283
365
|
// Upload the package
|
|
284
366
|
console.error(`Deploying to ${subdomain}.fastmode.ai...`);
|
|
285
367
|
const uploadResult = await uploadPackage(projectId, zipBuffer);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-conversion-guide.d.ts","sourceRoot":"","sources":["../../src/tools/get-conversion-guide.ts"],"names":[],"mappings":"AAAA,KAAK,OAAO,GAAG,MAAM,GAAG,aAAa,GAAG,UAAU,GAAG,WAAW,GAAG,KAAK,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"get-conversion-guide.d.ts","sourceRoot":"","sources":["../../src/tools/get-conversion-guide.ts"],"names":[],"mappings":"AAAA,KAAK,OAAO,GAAG,MAAM,GAAG,aAAa,GAAG,UAAU,GAAG,WAAW,GAAG,KAAK,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,iBAAiB,CAAC;AAotCtK;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CA+F1E"}
|