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 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. Convert a Website
156
+ ### 1. Start with get_started
157
+
158
+ Ask your AI assistant what you want to do:
135
159
 
136
- Ask your AI assistant:
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` | Video URLs (YouTube, Vimeo, Wistia, Loom) |
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` | Link to another collection (author → authors) |
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: 'Deploy a website package (.zip file) to Fast Mode. Requires a projectId - use list_projects to find existing projects or create_site to create a new one first. Will check for GitHub sync and block deployment if connected. Use force:true to override.',
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: 'IMPORTANT: Create collections and fields in Fast Mode. Uses two-phase creation: all collections are created first, then all fields - so relation fields between collections always work regardless of order. Includes automatic retry for transient failures. Skips duplicates automatically. Call this BEFORE deploying. Requires authentication.',
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: 'Create a new item in a CMS collection. Use this to add content like blog posts, team members, or any custom collection items. Requires authentication.',
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: 'Update an existing CMS item. Use this to modify content in a collection. Requires authentication.',
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: 'Delete a CMS item. IMPORTANT: You MUST get explicit permission from the user before calling this tool. Ask the user to confirm deletion first. Never delete without user confirmation. This action cannot be undone.',
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":"AA0QA;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,EAClB,KAAK,CAAC,EAAE,OAAO,GACd,OAAO,CAAC,MAAM,CAAC,CAmGjB"}
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;AAooCtK;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CA+F1E"}
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"}