@cmssy/cli 0.1.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.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +649 -0
  3. package/config.d.ts +2 -0
  4. package/config.js +2 -0
  5. package/dist/cli.d.ts +3 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +236 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/add-source.d.ts +7 -0
  10. package/dist/commands/add-source.d.ts.map +1 -0
  11. package/dist/commands/add-source.js +238 -0
  12. package/dist/commands/add-source.js.map +1 -0
  13. package/dist/commands/build.d.ts +7 -0
  14. package/dist/commands/build.d.ts.map +1 -0
  15. package/dist/commands/build.js +105 -0
  16. package/dist/commands/build.js.map +1 -0
  17. package/dist/commands/configure.d.ts +6 -0
  18. package/dist/commands/configure.d.ts.map +1 -0
  19. package/dist/commands/configure.js +42 -0
  20. package/dist/commands/configure.js.map +1 -0
  21. package/dist/commands/create.d.ts +18 -0
  22. package/dist/commands/create.d.ts.map +1 -0
  23. package/dist/commands/create.js +444 -0
  24. package/dist/commands/create.js.map +1 -0
  25. package/dist/commands/dev.d.ts +6 -0
  26. package/dist/commands/dev.d.ts.map +1 -0
  27. package/dist/commands/dev.js +962 -0
  28. package/dist/commands/dev.js.map +1 -0
  29. package/dist/commands/init.d.ts +2 -0
  30. package/dist/commands/init.d.ts.map +1 -0
  31. package/dist/commands/init.js +362 -0
  32. package/dist/commands/init.js.map +1 -0
  33. package/dist/commands/migrate.d.ts +2 -0
  34. package/dist/commands/migrate.d.ts.map +1 -0
  35. package/dist/commands/migrate.js +227 -0
  36. package/dist/commands/migrate.js.map +1 -0
  37. package/dist/commands/package.d.ts +7 -0
  38. package/dist/commands/package.d.ts.map +1 -0
  39. package/dist/commands/package.js +136 -0
  40. package/dist/commands/package.js.map +1 -0
  41. package/dist/commands/publish.d.ts +13 -0
  42. package/dist/commands/publish.d.ts.map +1 -0
  43. package/dist/commands/publish.js +910 -0
  44. package/dist/commands/publish.js.map +1 -0
  45. package/dist/commands/sync.d.ts +6 -0
  46. package/dist/commands/sync.d.ts.map +1 -0
  47. package/dist/commands/sync.js +208 -0
  48. package/dist/commands/sync.js.map +1 -0
  49. package/dist/commands/upload.d.ts +7 -0
  50. package/dist/commands/upload.d.ts.map +1 -0
  51. package/dist/commands/upload.js +126 -0
  52. package/dist/commands/upload.js.map +1 -0
  53. package/dist/commands/workspaces.d.ts +2 -0
  54. package/dist/commands/workspaces.d.ts.map +1 -0
  55. package/dist/commands/workspaces.js +67 -0
  56. package/dist/commands/workspaces.js.map +1 -0
  57. package/dist/dev-ui/app.js +1284 -0
  58. package/dist/dev-ui/index.html +1511 -0
  59. package/dist/dev-ui-react/App.tsx +164 -0
  60. package/dist/dev-ui-react/__tests__/previewData.test.ts +193 -0
  61. package/dist/dev-ui-react/components/BlocksList.tsx +232 -0
  62. package/dist/dev-ui-react/components/Editor.tsx +469 -0
  63. package/dist/dev-ui-react/components/Preview.tsx +146 -0
  64. package/dist/dev-ui-react/hooks/useBlocks.ts +80 -0
  65. package/dist/dev-ui-react/index.html +13 -0
  66. package/dist/dev-ui-react/main.tsx +8 -0
  67. package/dist/dev-ui-react/styles.css +856 -0
  68. package/dist/dev-ui-react/types.ts +45 -0
  69. package/dist/types/block-config.d.ts +315 -0
  70. package/dist/types/block-config.d.ts.map +1 -0
  71. package/dist/types/block-config.js +8 -0
  72. package/dist/types/block-config.js.map +1 -0
  73. package/dist/utils/block-config.d.ts +10 -0
  74. package/dist/utils/block-config.d.ts.map +1 -0
  75. package/dist/utils/block-config.js +199 -0
  76. package/dist/utils/block-config.js.map +1 -0
  77. package/dist/utils/blocks-meta-cache.d.ts +28 -0
  78. package/dist/utils/blocks-meta-cache.d.ts.map +1 -0
  79. package/dist/utils/blocks-meta-cache.js +72 -0
  80. package/dist/utils/blocks-meta-cache.js.map +1 -0
  81. package/dist/utils/builder.d.ts +34 -0
  82. package/dist/utils/builder.d.ts.map +1 -0
  83. package/dist/utils/builder.js +140 -0
  84. package/dist/utils/builder.js.map +1 -0
  85. package/dist/utils/cmssy-config.d.ts +16 -0
  86. package/dist/utils/cmssy-config.d.ts.map +1 -0
  87. package/dist/utils/cmssy-config.js +19 -0
  88. package/dist/utils/cmssy-config.js.map +1 -0
  89. package/dist/utils/config.d.ts +9 -0
  90. package/dist/utils/config.d.ts.map +1 -0
  91. package/dist/utils/config.js +46 -0
  92. package/dist/utils/config.js.map +1 -0
  93. package/dist/utils/field-schema.d.ts +12 -0
  94. package/dist/utils/field-schema.d.ts.map +1 -0
  95. package/dist/utils/field-schema.js +202 -0
  96. package/dist/utils/field-schema.js.map +1 -0
  97. package/dist/utils/graphql.d.ts +8 -0
  98. package/dist/utils/graphql.d.ts.map +1 -0
  99. package/dist/utils/graphql.js +118 -0
  100. package/dist/utils/graphql.js.map +1 -0
  101. package/dist/utils/publish-helpers.d.ts +35 -0
  102. package/dist/utils/publish-helpers.d.ts.map +1 -0
  103. package/dist/utils/publish-helpers.js +141 -0
  104. package/dist/utils/publish-helpers.js.map +1 -0
  105. package/dist/utils/scanner.d.ts +36 -0
  106. package/dist/utils/scanner.d.ts.map +1 -0
  107. package/dist/utils/scanner.js +140 -0
  108. package/dist/utils/scanner.js.map +1 -0
  109. package/dist/utils/type-generator.d.ts +9 -0
  110. package/dist/utils/type-generator.d.ts.map +1 -0
  111. package/dist/utils/type-generator.js +85 -0
  112. package/dist/utils/type-generator.js.map +1 -0
  113. package/package.json +88 -0
@@ -0,0 +1,910 @@
1
+ import chalk from "chalk";
2
+ import fs from "fs-extra";
3
+ import { GraphQLClient } from "graphql-request";
4
+ import inquirer from "inquirer";
5
+ import ora from "ora";
6
+ import path from "path";
7
+ import semver from "semver";
8
+ import { hasConfig, loadConfig } from "../utils/config.js";
9
+ import { PUBLISH_PACKAGE_MUTATION, IMPORT_BLOCK_MUTATION, IMPORT_TEMPLATE_MUTATION, } from "../utils/graphql.js";
10
+ import { loadBlockConfig, } from "../utils/block-config.js";
11
+ export async function publishCommand(packageNames = [], options) {
12
+ console.log(chalk.blue.bold("\n📦 Cmssy - Publish\n"));
13
+ // Validate flags: must have either --marketplace or --workspace
14
+ if (!options.marketplace && !options.workspace) {
15
+ console.error(chalk.red("✖ Specify publish target:\n") +
16
+ chalk.white(" --marketplace Publish to public marketplace (requires review)\n") +
17
+ chalk.white(" --workspace <id> Publish to private workspace (no review)\n"));
18
+ process.exit(1);
19
+ }
20
+ if (options.marketplace && options.workspace) {
21
+ console.error(chalk.red("✖ Cannot specify both --marketplace and --workspace\n"));
22
+ process.exit(1);
23
+ }
24
+ // Check configuration
25
+ if (!hasConfig()) {
26
+ console.error(chalk.red("✖ Not configured. Run: cmssy configure\n"));
27
+ process.exit(1);
28
+ }
29
+ const config = loadConfig();
30
+ // Get workspace ID if --workspace without value
31
+ let workspaceId = options.workspace;
32
+ if (typeof options.workspace === "boolean" || options.workspace === "") {
33
+ // Flag provided without value, check .env
34
+ if (config.workspaceId) {
35
+ workspaceId = config.workspaceId;
36
+ console.log(chalk.gray(`Using workspace ID from .env: ${workspaceId}\n`));
37
+ }
38
+ else {
39
+ const answer = await inquirer.prompt([
40
+ {
41
+ type: "input",
42
+ name: "workspaceId",
43
+ message: "Enter Workspace ID:",
44
+ validate: (input) => {
45
+ if (!input) {
46
+ return "Workspace ID is required (or set CMSSY_WORKSPACE_ID in .env)";
47
+ }
48
+ return true;
49
+ },
50
+ },
51
+ ]);
52
+ workspaceId = answer.workspaceId;
53
+ }
54
+ }
55
+ // Find cmssy.config.js
56
+ const configPath = path.join(process.cwd(), "cmssy.config.js");
57
+ if (!fs.existsSync(configPath)) {
58
+ console.error(chalk.red("✖ Not a cmssy project (missing cmssy.config.js)\n"));
59
+ process.exit(1);
60
+ }
61
+ // Scan for packages to publish
62
+ let packages = await scanPackages(packageNames, options);
63
+ // Auto-detect and add template dependencies (blocks used in pages.json)
64
+ // Only for workspace publish, not marketplace
65
+ if (options.workspace) {
66
+ const templatesToProcess = packages.filter((p) => p.type === "template");
67
+ for (const template of templatesToProcess) {
68
+ const pagesJsonPath = path.join(template.path, "pages.json");
69
+ const requiredBlockTypes = extractBlockTypesFromPagesJson(pagesJsonPath);
70
+ if (requiredBlockTypes.length > 0) {
71
+ // Find which blocks exist in the project
72
+ const availableBlocks = findProjectBlocks(requiredBlockTypes);
73
+ // Check which blocks are not already in the packages list
74
+ const existingBlockNames = packages
75
+ .filter((p) => p.type === "block")
76
+ .map((p) => p.name);
77
+ const missingBlocks = availableBlocks.filter((b) => !existingBlockNames.includes(b));
78
+ if (missingBlocks.length > 0) {
79
+ console.log(chalk.cyan(`\n📦 Auto-detected dependencies for ${template.name}:\n`));
80
+ missingBlocks.forEach((b) => console.log(chalk.gray(` • ${b}`)));
81
+ console.log("");
82
+ // Scan and add missing blocks
83
+ const dependencyPackages = await scanPackages(missingBlocks, {
84
+ ...options,
85
+ all: false,
86
+ });
87
+ // Insert dependencies BEFORE the template
88
+ const templateIndex = packages.findIndex((p) => p.name === template.name);
89
+ packages = [
90
+ ...packages.slice(0, templateIndex),
91
+ ...dependencyPackages,
92
+ ...packages.slice(templateIndex),
93
+ ];
94
+ }
95
+ }
96
+ }
97
+ }
98
+ if (packages.length === 0) {
99
+ console.log(chalk.yellow("⚠ No packages found to publish\n"));
100
+ if (packageNames.length > 0) {
101
+ console.log(chalk.gray("Packages specified:"));
102
+ packageNames.forEach((name) => console.log(chalk.gray(` • ${name}`)));
103
+ }
104
+ return;
105
+ }
106
+ // Show current versions
107
+ console.log(chalk.cyan("Current versions:\n"));
108
+ packages.forEach((pkg) => {
109
+ console.log(chalk.white(` ${pkg.packageJson.name}: ${chalk.bold(pkg.packageJson.version)}`));
110
+ });
111
+ console.log("");
112
+ // Version bumping - interactive or from flags
113
+ let bumpType = null;
114
+ // --no-bump flag explicitly disables version bump
115
+ if (options.bump === false) {
116
+ bumpType = null;
117
+ console.log(chalk.gray("Version bump disabled (--no-bump)\n"));
118
+ }
119
+ else if (options.patch || options.minor || options.major) {
120
+ // Use flag-based bump
121
+ bumpType = options.patch ? "patch" : options.minor ? "minor" : "major";
122
+ }
123
+ else {
124
+ // Interactive prompt - show calculated versions for first package as example
125
+ const examplePkg = packages[0];
126
+ const currentVersion = examplePkg.packageJson.version;
127
+ const patchVersion = semver.inc(currentVersion, "patch");
128
+ const minorVersion = semver.inc(currentVersion, "minor");
129
+ const majorVersion = semver.inc(currentVersion, "major");
130
+ const answer = await inquirer.prompt([
131
+ {
132
+ type: "list",
133
+ name: "bumpType",
134
+ message: "Select version bump:",
135
+ choices: [
136
+ {
137
+ name: `Patch (${currentVersion} → ${patchVersion}) - Bug fixes`,
138
+ value: "patch",
139
+ },
140
+ {
141
+ name: `Minor (${currentVersion} → ${minorVersion}) - New features, backward compatible`,
142
+ value: "minor",
143
+ },
144
+ {
145
+ name: `Major (${currentVersion} → ${majorVersion}) - Breaking changes`,
146
+ value: "major",
147
+ },
148
+ {
149
+ name: "No version bump - publish current version",
150
+ value: null,
151
+ },
152
+ ],
153
+ },
154
+ ]);
155
+ bumpType = answer.bumpType;
156
+ }
157
+ // Apply version bump if selected
158
+ if (bumpType) {
159
+ console.log(chalk.cyan(`\nVersion bump: ${bumpType}\n`));
160
+ for (const pkg of packages) {
161
+ const oldVersion = pkg.packageJson.version;
162
+ const newVersion = semver.inc(oldVersion, bumpType);
163
+ if (!newVersion) {
164
+ console.error(chalk.red(`✖ Invalid version for ${pkg.name}: ${oldVersion}\n`));
165
+ continue;
166
+ }
167
+ pkg.packageJson.version = newVersion;
168
+ // Update package.json
169
+ const pkgPath = path.join(pkg.path, "package.json");
170
+ fs.writeJsonSync(pkgPath, pkg.packageJson, { spaces: 2 });
171
+ console.log(chalk.gray(` ${pkg.name}: ${oldVersion} → ${newVersion}`));
172
+ }
173
+ console.log("");
174
+ }
175
+ console.log(chalk.cyan(`Publishing ${packages.length} package(s):\n`));
176
+ packages.forEach((pkg) => {
177
+ console.log(chalk.white(` • ${pkg.packageJson.name} ${chalk.bold("v" + pkg.packageJson.version)}`));
178
+ });
179
+ console.log("");
180
+ if (options.dryRun) {
181
+ console.log(chalk.yellow("🔍 Dry run mode - nothing will be published\n"));
182
+ return;
183
+ }
184
+ // Show target info
185
+ if (options.marketplace) {
186
+ console.log(chalk.yellow("📋 Target: Marketplace (public)\n" +
187
+ " Status: Pending review\n" +
188
+ " You'll be notified when approved.\n"));
189
+ }
190
+ else {
191
+ console.log(chalk.cyan(`🏢 Target: Workspace (${workspaceId})\n` +
192
+ " Status: Published immediately (no review)\n"));
193
+ }
194
+ // Publish each package
195
+ let successCount = 0;
196
+ let errorCount = 0;
197
+ for (const pkg of packages) {
198
+ const target = options.marketplace ? "marketplace" : "workspace";
199
+ const spinner = ora(`Publishing ${pkg.packageJson.name} to ${target}...`).start();
200
+ try {
201
+ if (options.marketplace) {
202
+ await publishToMarketplace(pkg, config.apiToken, config.apiUrl);
203
+ spinner.succeed(chalk.green(`${pkg.packageJson.name} submitted for review (pending)`));
204
+ }
205
+ else {
206
+ await publishToWorkspace(pkg, workspaceId, config.apiToken, config.apiUrl);
207
+ spinner.succeed(chalk.green(`${pkg.packageJson.name} published to workspace`));
208
+ }
209
+ successCount++;
210
+ }
211
+ catch (error) {
212
+ spinner.fail(chalk.red(`✖ ${pkg.packageJson.name} failed`));
213
+ // Extract detailed error information from GraphQL errors
214
+ let errorMessage = error.message || "Unknown error";
215
+ let errorCode = null;
216
+ let isPlanLimitError = false;
217
+ // graphql-request wraps errors in response.errors array
218
+ if (error.response?.errors && error.response.errors.length > 0) {
219
+ const graphqlError = error.response.errors[0];
220
+ errorMessage = graphqlError.message;
221
+ errorCode = graphqlError.extensions?.code || null;
222
+ isPlanLimitError = errorCode === "PLAN_LIMIT_EXCEEDED" ||
223
+ errorMessage.toLowerCase().includes("limit reached");
224
+ // Show additional details for plan limit errors
225
+ if (graphqlError.extensions?.resource) {
226
+ console.error("");
227
+ console.error(chalk.yellow.bold(" ⚠ Plan Limit Reached"));
228
+ console.error(chalk.yellow(` Resource: ${graphqlError.extensions.resource}`));
229
+ if (graphqlError.extensions.current !== undefined) {
230
+ console.error(chalk.yellow(` Usage: ${graphqlError.extensions.current}/${graphqlError.extensions.limit}`));
231
+ }
232
+ if (graphqlError.extensions.plan) {
233
+ console.error(chalk.yellow(` Plan: ${graphqlError.extensions.plan}`));
234
+ }
235
+ console.error(chalk.gray(" Upgrade your plan at: https://cmssy.com/pricing"));
236
+ console.error("");
237
+ }
238
+ }
239
+ // Show error message prominently
240
+ if (isPlanLimitError) {
241
+ console.error(chalk.red.bold(` ${errorMessage}`));
242
+ if (errorCode) {
243
+ console.error(chalk.gray(` Error code: ${errorCode}`));
244
+ }
245
+ }
246
+ else {
247
+ console.error(chalk.red(` Error: ${errorMessage}`));
248
+ if (errorCode) {
249
+ console.error(chalk.gray(` Code: ${errorCode}`));
250
+ }
251
+ }
252
+ console.error("");
253
+ errorCount++;
254
+ }
255
+ }
256
+ console.log("");
257
+ if (errorCount === 0) {
258
+ console.log(chalk.green.bold(`✓ ${successCount} package(s) published successfully\n`));
259
+ }
260
+ else {
261
+ console.log(chalk.yellow(`⚠ ${successCount} succeeded, ${errorCount} failed\n`));
262
+ }
263
+ }
264
+ /**
265
+ * Extract block type names from pages.json (pages + layout positions)
266
+ * Supports all layout positions: header, footer, sidebar_left, sidebar_right, top, bottom
267
+ * @example "@cmssy-marketing/blocks.hero" -> "hero"
268
+ */
269
+ function extractBlockTypesFromPagesJson(pagesJsonPath) {
270
+ if (!fs.existsSync(pagesJsonPath)) {
271
+ return [];
272
+ }
273
+ const pagesData = fs.readJsonSync(pagesJsonPath);
274
+ const blockTypes = new Set();
275
+ // Extract from pages
276
+ for (const page of pagesData.pages || []) {
277
+ for (const block of page.blocks || []) {
278
+ if (block.type) {
279
+ // Convert full name to simple type: "@scope/blocks.hero" -> "hero"
280
+ let blockType = block.type;
281
+ if (blockType.includes("/")) {
282
+ blockType = blockType.split("/").pop();
283
+ }
284
+ if (blockType.startsWith("blocks.")) {
285
+ blockType = blockType.substring(7);
286
+ }
287
+ blockTypes.add(blockType);
288
+ }
289
+ }
290
+ }
291
+ // Extract from layoutSlots
292
+ for (const [_slot, data] of Object.entries(pagesData.layoutSlots || {})) {
293
+ if (data.type) {
294
+ let blockType = data.type;
295
+ if (blockType.includes("/")) {
296
+ blockType = blockType.split("/").pop();
297
+ }
298
+ if (blockType.startsWith("blocks.")) {
299
+ blockType = blockType.substring(7);
300
+ }
301
+ blockTypes.add(blockType);
302
+ }
303
+ }
304
+ return Array.from(blockTypes);
305
+ }
306
+ /**
307
+ * Find which blocks from a list exist in the project's blocks/ directory
308
+ */
309
+ function findProjectBlocks(blockTypes) {
310
+ const blocksDir = path.join(process.cwd(), "blocks");
311
+ if (!fs.existsSync(blocksDir)) {
312
+ return [];
313
+ }
314
+ const existingBlocks = fs
315
+ .readdirSync(blocksDir, { withFileTypes: true })
316
+ .filter((d) => d.isDirectory())
317
+ .map((d) => d.name);
318
+ return blockTypes.filter((bt) => existingBlocks.includes(bt));
319
+ }
320
+ async function scanPackages(packageNames, options) {
321
+ const packages = [];
322
+ // Scan blocks
323
+ const blocksDir = path.join(process.cwd(), "blocks");
324
+ if (fs.existsSync(blocksDir)) {
325
+ const blockDirs = fs
326
+ .readdirSync(blocksDir, { withFileTypes: true })
327
+ .filter((dirent) => dirent.isDirectory())
328
+ .map((dirent) => dirent.name);
329
+ for (const blockName of blockDirs) {
330
+ // Filter: --all OR packageNames includes this block
331
+ if (!options.all && !packageNames.includes(blockName)) {
332
+ continue;
333
+ }
334
+ const blockPath = path.join(blocksDir, blockName);
335
+ const pkgPath = path.join(blockPath, "package.json");
336
+ if (!fs.existsSync(pkgPath)) {
337
+ console.warn(chalk.yellow(`Warning: ${blockName} has no package.json, skipping`));
338
+ continue;
339
+ }
340
+ const packageJson = fs.readJsonSync(pkgPath);
341
+ // Try loading block.config.ts
342
+ const blockConfig = await loadBlockConfig(blockPath);
343
+ if (!blockConfig && !packageJson.cmssy) {
344
+ console.warn(chalk.yellow(`Warning: ${blockName} has no block.config.ts or package.json cmssy section, skipping`));
345
+ continue;
346
+ }
347
+ packages.push({
348
+ type: "block",
349
+ name: blockName,
350
+ path: blockPath,
351
+ packageJson,
352
+ blockConfig,
353
+ });
354
+ }
355
+ }
356
+ // Scan templates
357
+ const templatesDir = path.join(process.cwd(), "templates");
358
+ if (fs.existsSync(templatesDir)) {
359
+ const templateDirs = fs
360
+ .readdirSync(templatesDir, { withFileTypes: true })
361
+ .filter((dirent) => dirent.isDirectory())
362
+ .map((dirent) => dirent.name);
363
+ for (const templateName of templateDirs) {
364
+ // Filter: --all OR packageNames includes this template
365
+ if (!options.all && !packageNames.includes(templateName)) {
366
+ continue;
367
+ }
368
+ const templatePath = path.join(templatesDir, templateName);
369
+ const pkgPath = path.join(templatePath, "package.json");
370
+ if (!fs.existsSync(pkgPath)) {
371
+ console.warn(chalk.yellow(`Warning: ${templateName} has no package.json, skipping`));
372
+ continue;
373
+ }
374
+ const packageJson = fs.readJsonSync(pkgPath);
375
+ // Try loading block.config.ts
376
+ const blockConfig = await loadBlockConfig(templatePath);
377
+ if (!blockConfig && !packageJson.cmssy) {
378
+ console.warn(chalk.yellow(`Warning: ${templateName} has no block.config.ts or package.json cmssy section, skipping`));
379
+ continue;
380
+ }
381
+ packages.push({
382
+ type: "template",
383
+ name: templateName,
384
+ path: templatePath,
385
+ packageJson,
386
+ blockConfig,
387
+ });
388
+ }
389
+ }
390
+ return packages;
391
+ }
392
+ /**
393
+ * Read original source code for AI Block Builder (editable in Sandpack).
394
+ * Combines the main component file with type definitions into a single file.
395
+ */
396
+ async function readOriginalSourceCode(packagePath) {
397
+ const srcDir = path.join(packagePath, "src");
398
+ // Find main component file (not index.tsx, but the actual component)
399
+ const files = fs.readdirSync(srcDir);
400
+ let mainComponentFile;
401
+ let sourceCode;
402
+ // Look for component files (excluding index.tsx and .d.ts files)
403
+ for (const file of files) {
404
+ if ((file.endsWith(".tsx") || file.endsWith(".ts")) &&
405
+ !file.startsWith("index") &&
406
+ !file.endsWith(".d.ts")) {
407
+ mainComponentFile = path.join(srcDir, file);
408
+ break;
409
+ }
410
+ }
411
+ // If no main component found, try index.tsx
412
+ if (!mainComponentFile) {
413
+ const indexPath = path.join(srcDir, "index.tsx");
414
+ if (fs.existsSync(indexPath)) {
415
+ mainComponentFile = indexPath;
416
+ }
417
+ }
418
+ if (mainComponentFile && fs.existsSync(mainComponentFile)) {
419
+ // Read the main component
420
+ let content = fs.readFileSync(mainComponentFile, "utf-8");
421
+ // Read block.d.ts if exists and inline the types
422
+ const blockDtsPath = path.join(srcDir, "block.d.ts");
423
+ if (fs.existsSync(blockDtsPath)) {
424
+ const blockDts = fs.readFileSync(blockDtsPath, "utf-8");
425
+ // Extract interface/type definitions from block.d.ts
426
+ const typeMatch = blockDts.match(/(?:export\s+)?(?:interface|type)\s+BlockContent[\s\S]*?(?=(?:export\s+)?(?:interface|type)|$)/);
427
+ if (typeMatch) {
428
+ // Remove the import from block.d.ts and add inline type
429
+ content = content.replace(/import\s*{\s*BlockContent\s*}\s*from\s*["']\.\/block(?:\.d)?["'];?\n?/, "");
430
+ // Add inline interface at the top
431
+ const inlineInterface = `interface BlockContent {
432
+ [key: string]: any;
433
+ }\n\n`;
434
+ // Insert after imports
435
+ const lastImportMatch = content.match(/^(import[\s\S]*?from\s*['"][^'"]+['"];?\n)/m);
436
+ if (lastImportMatch) {
437
+ const insertPos = content.lastIndexOf(lastImportMatch[0]) + lastImportMatch[0].length;
438
+ content =
439
+ content.slice(0, insertPos) +
440
+ "\n" +
441
+ inlineInterface +
442
+ content.slice(insertPos);
443
+ }
444
+ else {
445
+ content = inlineInterface + content;
446
+ }
447
+ }
448
+ }
449
+ sourceCode = content;
450
+ }
451
+ // Read CSS
452
+ const cssPath = path.join(srcDir, "index.css");
453
+ const sourceCss = fs.existsSync(cssPath)
454
+ ? fs.readFileSync(cssPath, "utf-8")
455
+ : undefined;
456
+ return { sourceCode, sourceCss };
457
+ }
458
+ // Bundle source code with esbuild (combines all local imports into single file)
459
+ // Bundle source code with esbuild (combines all local imports into single file)
460
+ // UPDATED: Use CommonJS format to avoid ES module export statements
461
+ async function bundleSourceCode(packagePath) {
462
+ const { build } = await import("esbuild");
463
+ const srcDir = path.join(packagePath, "src");
464
+ const tsxPath = path.join(srcDir, "index.tsx");
465
+ const tsPath = path.join(srcDir, "index.ts");
466
+ let entryPoint;
467
+ if (fs.existsSync(tsxPath)) {
468
+ entryPoint = tsxPath;
469
+ }
470
+ else if (fs.existsSync(tsPath)) {
471
+ entryPoint = tsPath;
472
+ }
473
+ else {
474
+ throw new Error(`Source code not found. Expected ${tsxPath} or ${tsPath}`);
475
+ }
476
+ const result = await build({
477
+ entryPoints: [entryPoint],
478
+ bundle: true,
479
+ write: false,
480
+ format: "cjs", // CommonJS format (module.exports) - compatible with SSR VM
481
+ platform: "browser", // Browser platform to avoid Node.js globals like 'process'
482
+ jsx: "transform", // Transform JSX to React.createElement
483
+ loader: { ".tsx": "tsx", ".ts": "ts", ".css": "empty" },
484
+ external: ["react", "react-dom", "react/jsx-runtime"], // React provided by SSR sandbox / BlockRenderer scope
485
+ minify: true, // Minify for smaller bundle size
486
+ define: {
487
+ // Replace process.env references with static values
488
+ 'process.env.NODE_ENV': '"production"',
489
+ },
490
+ });
491
+ let bundledCode = result.outputFiles[0].text;
492
+ // Post-process: Add __component for SSR if code has mount/update pattern
493
+ // This makes blocks work in both dev environment (mount/update) and SSR (__component)
494
+ bundledCode = addComponentForSSR(bundledCode);
495
+ return bundledCode;
496
+ }
497
+ // Add __component to mount/update pattern for SSR compatibility
498
+ function addComponentForSSR(code) {
499
+ // Check if code exports mount/update pattern
500
+ const hasPattern = /exports\.default\s*=\s*\{[^}]*mount\s*\([^)]*\)/s.test(code) ||
501
+ /module\.exports\s*=\s*\{[^}]*mount\s*\([^)]*\)/s.test(code);
502
+ if (!hasPattern) {
503
+ // No mount/update pattern - return as-is
504
+ return code;
505
+ }
506
+ // Find the component that's being used in mount()
507
+ // Pattern: export default { mount() { ... render(<Component ... /> or createElement(Component ...) } }
508
+ const componentMatch = code.match(/(?:render|createElement)\s*\(\s*(?:<\s*)?(\w+)/);
509
+ const componentName = componentMatch?.[1];
510
+ if (!componentName) {
511
+ console.warn('[CLI] Warning: Found mount/update pattern but could not extract component name for __component');
512
+ return code;
513
+ }
514
+ // Add __component to the exports object
515
+ // Replace: module.exports = { mount, update, unmount };
516
+ // With: module.exports = { mount, update, unmount, __component: ComponentName };
517
+ const updatedCode = code.replace(/((?:exports\.default|module\.exports)\s*=\s*\{[^}]*)(}\s*;)/s, `$1,\n // Auto-added by CLI for SSR compatibility\n __component: ${componentName}\n$2`);
518
+ if (updatedCode === code) {
519
+ console.warn('[CLI] Warning: Could not add __component to exports');
520
+ }
521
+ return updatedCode;
522
+ }
523
+ // Wrap bundled code with mount/update pattern for interactive blocks
524
+ function wrapWithInteractivePattern(bundledCode) {
525
+ return `
526
+ // Original component code
527
+ ${bundledCode}
528
+
529
+ // Auto-generated interactive wrapper by CLI
530
+ import { createRoot } from 'react-dom/client';
531
+
532
+ const OriginalComponent = exports.default || module.exports.default;
533
+
534
+ if (!OriginalComponent) {
535
+ throw new Error('Block must export a default component');
536
+ }
537
+
538
+ // Export both mount/update pattern AND original component for SSR
539
+ module.exports = {
540
+ // Mount/update pattern for browser
541
+ mount(element, props) {
542
+ const root = createRoot(element);
543
+ root.render(React.createElement(OriginalComponent, { content: props }));
544
+ return { root };
545
+ },
546
+
547
+ update(_element, props, ctx) {
548
+ ctx.root.render(React.createElement(OriginalComponent, { content: props }));
549
+ },
550
+
551
+ unmount(_element, ctx) {
552
+ ctx.root.unmount();
553
+ },
554
+
555
+ // Original component for SSR (server-side rendering)
556
+ __component: OriginalComponent
557
+ };
558
+ `.trim();
559
+ }
560
+ // Compile CSS with optional Tailwind support
561
+ async function compileCss(packagePath, bundledSourceCode) {
562
+ const srcDir = path.join(packagePath, "src");
563
+ const cssPath = path.join(srcDir, "index.css");
564
+ if (!fs.existsSync(cssPath)) {
565
+ return undefined;
566
+ }
567
+ let cssContent = fs.readFileSync(cssPath, "utf-8");
568
+ // If no Tailwind/PostCSS imports, return raw CSS
569
+ if (!cssContent.includes("@import") && !cssContent.includes("@tailwind")) {
570
+ return cssContent;
571
+ }
572
+ // Load PostCSS from project
573
+ const { default: postcss } = await import("postcss");
574
+ // Check for Tailwind v4 vs v3
575
+ const projectRoot = process.cwd();
576
+ // Load postcss-import from project's node_modules (ESM requires full path to index.js)
577
+ const postcssImportPath = path.join(projectRoot, "node_modules", "postcss-import", "index.js");
578
+ const { default: postcssImport } = await import(postcssImportPath);
579
+ const projectPackageJson = fs.readJsonSync(path.join(projectRoot, "package.json"));
580
+ const hasTailwindV4 = !!(projectPackageJson.devDependencies?.["@tailwindcss/postcss"] ||
581
+ projectPackageJson.dependencies?.["@tailwindcss/postcss"]);
582
+ // Configure postcss-import to resolve from project's styles/ folder
583
+ const importPlugin = postcssImport({
584
+ path: [path.join(projectRoot, "styles")],
585
+ });
586
+ let tailwindPlugin;
587
+ if (hasTailwindV4) {
588
+ // Tailwind v4 with @tailwindcss/postcss
589
+ const tailwindV4Path = path.join(projectRoot, "node_modules", "@tailwindcss/postcss", "dist", "index.mjs");
590
+ const tailwindV4Module = await import(tailwindV4Path);
591
+ tailwindPlugin = tailwindV4Module.default || tailwindV4Module;
592
+ }
593
+ else {
594
+ // Tailwind v3 - convert @import to @tailwind directives
595
+ cssContent = cssContent.replace(/@import\s+["']tailwindcss["'];?/g, "@tailwind base;\n@tailwind components;\n@tailwind utilities;");
596
+ const tailwindcssPath = path.join(projectRoot, "node_modules", "tailwindcss", "lib", "index.js");
597
+ const tailwindcssModule = await import(tailwindcssPath);
598
+ const tailwindcss = tailwindcssModule.default || tailwindcssModule;
599
+ tailwindPlugin = tailwindcss({
600
+ content: [{ raw: bundledSourceCode, extension: "tsx" }],
601
+ });
602
+ }
603
+ // Process CSS with postcss-import FIRST, then Tailwind
604
+ const result = await postcss([importPlugin, tailwindPlugin]).process(cssContent, {
605
+ from: cssPath,
606
+ });
607
+ return result.css;
608
+ }
609
+ /**
610
+ * Convert full block type name to simple type.
611
+ * "@cmssy-marketing/blocks.hero" -> "hero"
612
+ * "@vendor/blocks.pricing-table" -> "pricing-table"
613
+ * "hero" -> "hero" (already simple)
614
+ */
615
+ function convertBlockTypeToSimple(blockType) {
616
+ let simple = blockType;
617
+ // Remove @scope/ prefix
618
+ if (simple.includes("/")) {
619
+ simple = simple.split("/").pop();
620
+ }
621
+ // Remove blocks. or templates. prefix
622
+ if (simple.startsWith("blocks.")) {
623
+ simple = simple.substring(7);
624
+ }
625
+ else if (simple.startsWith("templates.")) {
626
+ simple = simple.substring(10);
627
+ }
628
+ return simple;
629
+ }
630
+ async function publishToMarketplace(pkg, apiToken, apiUrl) {
631
+ const { packageJson, path: packagePath, blockConfig } = pkg;
632
+ // Use blockConfig if available, fallback to package.json cmssy
633
+ const metadata = blockConfig || packageJson.cmssy || {};
634
+ // Validate vendor info
635
+ if (!metadata.vendorName && !packageJson.author) {
636
+ throw new Error("Vendor name required. Add 'vendorName' to block.config.ts or 'author' to package.json");
637
+ }
638
+ const vendorName = metadata.vendorName ||
639
+ (typeof packageJson.author === "string"
640
+ ? packageJson.author
641
+ : packageJson.author?.name);
642
+ // Read source code from src/index.tsx or src/index.ts
643
+ const srcDir = path.join(packagePath, "src");
644
+ let sourceCode;
645
+ const tsxPath = path.join(srcDir, "index.tsx");
646
+ const tsPath = path.join(srcDir, "index.ts");
647
+ if (fs.existsSync(tsxPath)) {
648
+ sourceCode = fs.readFileSync(tsxPath, "utf-8");
649
+ }
650
+ else if (fs.existsSync(tsPath)) {
651
+ sourceCode = fs.readFileSync(tsPath, "utf-8");
652
+ }
653
+ else {
654
+ throw new Error(`Source code not found. Expected ${tsxPath} or ${tsPath}`);
655
+ }
656
+ // Read CSS if exists
657
+ const cssPath = path.join(srcDir, "index.css");
658
+ let cssCode;
659
+ if (fs.existsSync(cssPath)) {
660
+ cssCode = fs.readFileSync(cssPath, "utf-8");
661
+ }
662
+ // Convert block.config.ts schema to schemaFields if using blockConfig
663
+ let schemaFields = metadata.schemaFields || [];
664
+ if (blockConfig && blockConfig.schema) {
665
+ schemaFields = convertSchemaToFields(blockConfig.schema);
666
+ }
667
+ // Build input
668
+ const input = {
669
+ name: packageJson.name,
670
+ version: packageJson.version,
671
+ displayName: metadata.displayName || metadata.name || packageJson.name,
672
+ description: packageJson.description || metadata.description || "",
673
+ longDescription: metadata.longDescription || null,
674
+ packageType: pkg.type,
675
+ category: metadata.category || "other",
676
+ tags: metadata.tags || [],
677
+ sourceCode,
678
+ cssUrl: null,
679
+ packageJsonUrl: "",
680
+ schemaFields,
681
+ defaultContent: extractDefaultContent(blockConfig?.schema || {}),
682
+ vendorName,
683
+ vendorEmail: packageJson.author?.email || null,
684
+ vendorUrl: packageJson.homepage || packageJson.repository?.url || null,
685
+ licenseType: metadata.pricing?.licenseType || metadata.licenseType || "free",
686
+ priceCents: metadata.pricing?.priceCents || metadata.priceCents || 0,
687
+ };
688
+ // Create client
689
+ const client = new GraphQLClient(apiUrl, {
690
+ headers: {
691
+ "Content-Type": "application/json",
692
+ },
693
+ });
694
+ // Send mutation
695
+ const result = await client.request(PUBLISH_PACKAGE_MUTATION, {
696
+ token: apiToken,
697
+ input,
698
+ });
699
+ if (!result.publishPackage?.success) {
700
+ throw new Error(result.publishPackage?.message || "Failed to publish package");
701
+ }
702
+ }
703
+ async function publishToWorkspace(pkg, workspaceId, apiToken, apiUrl) {
704
+ const { packageJson, path: packagePath, blockConfig, type: packageType } = pkg;
705
+ // Use blockConfig if available, fallback to package.json cmssy
706
+ const metadata = blockConfig || packageJson.cmssy || {};
707
+ // Generate block_type from package name
708
+ // @cmssy/blocks.hero -> hero
709
+ const blockType = packageJson.name
710
+ .replace(/@[^/]+\//, "")
711
+ .replace(/^blocks\./, "")
712
+ .replace(/^templates\./, "");
713
+ // Bundle source code (combines all local imports)
714
+ // Post-processing automatically adds __component for SSR if mount/update pattern detected
715
+ const bundledSourceCode = await bundleSourceCode(packagePath);
716
+ // Compile CSS (with Tailwind if needed)
717
+ const compiledCss = await compileCss(packagePath, bundledSourceCode);
718
+ // Read original source code for AI Block Builder editing
719
+ const { sourceCode: rawSourceCode, sourceCss: rawSourceCss } = await readOriginalSourceCode(packagePath);
720
+ // Read dependencies from package.json for AI Block Builder
721
+ const dependencies = packageJson.dependencies || {};
722
+ // Convert block.config.ts schema to schemaFields if using blockConfig
723
+ let schemaFields = metadata.schemaFields || [];
724
+ if (blockConfig && blockConfig.schema) {
725
+ schemaFields = convertSchemaToFields(blockConfig.schema);
726
+ }
727
+ // Build input with inline sourceCode and cssCode
728
+ // Backend will handle uploading to Blob Storage
729
+ const input = {
730
+ blockType,
731
+ name: metadata.displayName || metadata.name || packageJson.name,
732
+ description: packageJson.description || metadata.description || "",
733
+ icon: metadata.icon || "Blocks",
734
+ category: metadata.category || "Custom",
735
+ sourceCode: bundledSourceCode,
736
+ cssCode: compiledCss,
737
+ interactive: metadata.interactive || false,
738
+ schemaFields,
739
+ defaultContent: extractDefaultContent(blockConfig?.schema || {}),
740
+ sourceRegistry: "local",
741
+ sourceItem: packageJson.name,
742
+ version: packageJson.version || "1.0.0",
743
+ packageType, // "block" or "template"
744
+ // AI Block Builder source files (for editable blocks in Sandpack)
745
+ rawSourceCode,
746
+ rawSourceCss,
747
+ dependencies: Object.keys(dependencies).length > 0 ? dependencies : undefined,
748
+ };
749
+ // Add layoutPosition if defined (for layout blocks)
750
+ if (blockConfig?.layoutPosition) {
751
+ input.layoutPosition = blockConfig.layoutPosition;
752
+ }
753
+ // Add requires if defined
754
+ if (blockConfig?.requires) {
755
+ input.requires = blockConfig.requires;
756
+ }
757
+ // Check if this is a template with pages.json
758
+ const isTemplate = packageType === "template";
759
+ const pagesJsonPath = path.join(packagePath, "pages.json");
760
+ const hasPagesJson = isTemplate && fs.existsSync(pagesJsonPath);
761
+ // Create client with workspace header
762
+ const client = new GraphQLClient(apiUrl, {
763
+ headers: {
764
+ "Content-Type": "application/json",
765
+ Authorization: `Bearer ${apiToken}`,
766
+ "X-Workspace-ID": workspaceId,
767
+ },
768
+ });
769
+ // Send mutation with timeout using Promise.race
770
+ const TIMEOUT_MS = 180000; // 3 minutes
771
+ let timeoutId;
772
+ const timeoutPromise = new Promise((_, reject) => {
773
+ timeoutId = setTimeout(() => {
774
+ reject(new Error("Block upload timed out after 3 minutes. This may be due to:\n" +
775
+ " - Large file size (try reducing bundle size)\n" +
776
+ " - Slow network connection\n" +
777
+ " - Backend processing issues\n" +
778
+ "Check backend logs for more details."));
779
+ }, TIMEOUT_MS);
780
+ });
781
+ // Use different mutation for templates with pages
782
+ if (hasPagesJson) {
783
+ // Load pages.json for template
784
+ const pagesData = fs.readJsonSync(pagesJsonPath);
785
+ // Convert pages.json format to mutation input format
786
+ // IMPORTANT: Convert full block names to simple types
787
+ // "@cmssy-marketing/blocks.hero" -> "hero"
788
+ const pages = (pagesData.pages || []).map((page) => ({
789
+ name: page.name,
790
+ slug: page.slug,
791
+ blocks: (page.blocks || []).map((block) => ({
792
+ type: convertBlockTypeToSimple(block.type),
793
+ content: block.content || {},
794
+ })),
795
+ }));
796
+ // Convert layoutSlots to array format
797
+ // IMPORTANT: Convert full block names to simple types
798
+ const layoutSlots = [];
799
+ if (pagesData.layoutSlots) {
800
+ for (const [slot, data] of Object.entries(pagesData.layoutSlots)) {
801
+ layoutSlots.push({
802
+ slot,
803
+ type: convertBlockTypeToSimple(data.type),
804
+ content: data.content || {},
805
+ });
806
+ }
807
+ }
808
+ // Add pages and layout slots to input
809
+ input.pages = pages;
810
+ input.layoutSlots = layoutSlots;
811
+ // Remove fields not supported by ImportTemplateInput
812
+ // (these are only for blocks, not templates)
813
+ delete input.packageType;
814
+ delete input.rawSourceCode;
815
+ delete input.rawSourceCss;
816
+ delete input.dependencies;
817
+ const requestPromise = client.request(IMPORT_TEMPLATE_MUTATION, { input });
818
+ try {
819
+ const result = await Promise.race([requestPromise, timeoutPromise]);
820
+ clearTimeout(timeoutId);
821
+ if (!result.importTemplate?.success) {
822
+ throw new Error(result.importTemplate?.message || "Failed to import template to workspace");
823
+ }
824
+ // Log template import summary
825
+ const { pagesCreated, pagesUpdated, layoutSlotsCreated, layoutSlotsUpdated } = result.importTemplate;
826
+ console.log(chalk.gray(` └─ ${pagesCreated} pages created, ${pagesUpdated} updated | ` +
827
+ `${layoutSlotsCreated} layout slots created, ${layoutSlotsUpdated} updated`));
828
+ }
829
+ catch (error) {
830
+ clearTimeout(timeoutId);
831
+ throw error;
832
+ }
833
+ }
834
+ else {
835
+ // Standard block import
836
+ const requestPromise = client.request(IMPORT_BLOCK_MUTATION, { input });
837
+ try {
838
+ const result = await Promise.race([requestPromise, timeoutPromise]);
839
+ clearTimeout(timeoutId);
840
+ if (!result.importBlock) {
841
+ throw new Error("Failed to import block to workspace");
842
+ }
843
+ }
844
+ catch (error) {
845
+ clearTimeout(timeoutId);
846
+ throw error;
847
+ }
848
+ }
849
+ }
850
+ // Helper: Convert block.config.ts schema to schemaFields array
851
+ function convertSchemaToFields(schema) {
852
+ const fields = [];
853
+ Object.entries(schema).forEach(([key, field]) => {
854
+ const baseField = {
855
+ key,
856
+ type: field.type,
857
+ label: field.label,
858
+ required: field.required || false,
859
+ };
860
+ // Add defaultValue if present
861
+ if (field.defaultValue !== undefined) {
862
+ baseField.defaultValue = field.defaultValue;
863
+ }
864
+ // Add placeholder if present
865
+ if (field.placeholder) {
866
+ baseField.placeholder = field.placeholder;
867
+ }
868
+ // Add helpText if present
869
+ if (field.helpText) {
870
+ baseField.helperText = field.helpText;
871
+ }
872
+ // Add group if present
873
+ if (field.group) {
874
+ baseField.group = field.group;
875
+ }
876
+ // Add showWhen conditional visibility
877
+ if (field.showWhen) {
878
+ baseField.showWhen = field.showWhen;
879
+ }
880
+ // Add validation rules
881
+ if (field.validation) {
882
+ baseField.validation = field.validation;
883
+ }
884
+ if (field.type === "select" && field.options) {
885
+ baseField.options = field.options;
886
+ }
887
+ if (field.type === "repeater" && field.schema) {
888
+ baseField.minItems = field.minItems;
889
+ baseField.maxItems = field.maxItems;
890
+ // Backend expects itemSchema to be a flat array of field definitions
891
+ baseField.itemSchema = convertSchemaToFields(field.schema);
892
+ }
893
+ fields.push(baseField);
894
+ });
895
+ return fields;
896
+ }
897
+ // Helper: Extract default content from schema
898
+ function extractDefaultContent(schema) {
899
+ const content = {};
900
+ Object.entries(schema).forEach(([key, field]) => {
901
+ if (field.defaultValue !== undefined) {
902
+ content[key] = field.defaultValue;
903
+ }
904
+ else if (field.type === "repeater") {
905
+ content[key] = [];
906
+ }
907
+ });
908
+ return content;
909
+ }
910
+ //# sourceMappingURL=publish.js.map