@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.
- package/LICENSE +21 -0
- package/README.md +649 -0
- package/config.d.ts +2 -0
- package/config.js +2 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +236 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add-source.d.ts +7 -0
- package/dist/commands/add-source.d.ts.map +1 -0
- package/dist/commands/add-source.js +238 -0
- package/dist/commands/add-source.js.map +1 -0
- package/dist/commands/build.d.ts +7 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +105 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/configure.d.ts +6 -0
- package/dist/commands/configure.d.ts.map +1 -0
- package/dist/commands/configure.js +42 -0
- package/dist/commands/configure.js.map +1 -0
- package/dist/commands/create.d.ts +18 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +444 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dev.d.ts +6 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +962 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +362 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/migrate.d.ts +2 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +227 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/package.d.ts +7 -0
- package/dist/commands/package.d.ts.map +1 -0
- package/dist/commands/package.js +136 -0
- package/dist/commands/package.js.map +1 -0
- package/dist/commands/publish.d.ts +13 -0
- package/dist/commands/publish.d.ts.map +1 -0
- package/dist/commands/publish.js +910 -0
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/sync.d.ts +6 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +208 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/upload.d.ts +7 -0
- package/dist/commands/upload.d.ts.map +1 -0
- package/dist/commands/upload.js +126 -0
- package/dist/commands/upload.js.map +1 -0
- package/dist/commands/workspaces.d.ts +2 -0
- package/dist/commands/workspaces.d.ts.map +1 -0
- package/dist/commands/workspaces.js +67 -0
- package/dist/commands/workspaces.js.map +1 -0
- package/dist/dev-ui/app.js +1284 -0
- package/dist/dev-ui/index.html +1511 -0
- package/dist/dev-ui-react/App.tsx +164 -0
- package/dist/dev-ui-react/__tests__/previewData.test.ts +193 -0
- package/dist/dev-ui-react/components/BlocksList.tsx +232 -0
- package/dist/dev-ui-react/components/Editor.tsx +469 -0
- package/dist/dev-ui-react/components/Preview.tsx +146 -0
- package/dist/dev-ui-react/hooks/useBlocks.ts +80 -0
- package/dist/dev-ui-react/index.html +13 -0
- package/dist/dev-ui-react/main.tsx +8 -0
- package/dist/dev-ui-react/styles.css +856 -0
- package/dist/dev-ui-react/types.ts +45 -0
- package/dist/types/block-config.d.ts +315 -0
- package/dist/types/block-config.d.ts.map +1 -0
- package/dist/types/block-config.js +8 -0
- package/dist/types/block-config.js.map +1 -0
- package/dist/utils/block-config.d.ts +10 -0
- package/dist/utils/block-config.d.ts.map +1 -0
- package/dist/utils/block-config.js +199 -0
- package/dist/utils/block-config.js.map +1 -0
- package/dist/utils/blocks-meta-cache.d.ts +28 -0
- package/dist/utils/blocks-meta-cache.d.ts.map +1 -0
- package/dist/utils/blocks-meta-cache.js +72 -0
- package/dist/utils/blocks-meta-cache.js.map +1 -0
- package/dist/utils/builder.d.ts +34 -0
- package/dist/utils/builder.d.ts.map +1 -0
- package/dist/utils/builder.js +140 -0
- package/dist/utils/builder.js.map +1 -0
- package/dist/utils/cmssy-config.d.ts +16 -0
- package/dist/utils/cmssy-config.d.ts.map +1 -0
- package/dist/utils/cmssy-config.js +19 -0
- package/dist/utils/cmssy-config.js.map +1 -0
- package/dist/utils/config.d.ts +9 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +46 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/field-schema.d.ts +12 -0
- package/dist/utils/field-schema.d.ts.map +1 -0
- package/dist/utils/field-schema.js +202 -0
- package/dist/utils/field-schema.js.map +1 -0
- package/dist/utils/graphql.d.ts +8 -0
- package/dist/utils/graphql.d.ts.map +1 -0
- package/dist/utils/graphql.js +118 -0
- package/dist/utils/graphql.js.map +1 -0
- package/dist/utils/publish-helpers.d.ts +35 -0
- package/dist/utils/publish-helpers.d.ts.map +1 -0
- package/dist/utils/publish-helpers.js +141 -0
- package/dist/utils/publish-helpers.js.map +1 -0
- package/dist/utils/scanner.d.ts +36 -0
- package/dist/utils/scanner.d.ts.map +1 -0
- package/dist/utils/scanner.js +140 -0
- package/dist/utils/scanner.js.map +1 -0
- package/dist/utils/type-generator.d.ts +9 -0
- package/dist/utils/type-generator.d.ts.map +1 -0
- package/dist/utils/type-generator.js +85 -0
- package/dist/utils/type-generator.js.map +1 -0
- 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
|