webstudio 0.105.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/lib/cli.js ADDED
@@ -0,0 +1,875 @@
1
+ // src/cli.ts
2
+ import { exit, argv } from "node:process";
3
+ import { hideBin } from "yargs/helpers";
4
+
5
+ // src/config.ts
6
+ import { join } from "node:path";
7
+ import envPaths from "env-paths";
8
+ import { z } from "zod";
9
+ var GLOBAL_CONFIG_FOLDER = envPaths("webstudio").config;
10
+ var GLOBAL_CONFIG_FILE_NAME = "webstudio-config.json";
11
+ var GLOBAL_CONFIG_FILE = join(
12
+ GLOBAL_CONFIG_FOLDER,
13
+ GLOBAL_CONFIG_FILE_NAME
14
+ );
15
+ var LOCAL_CONFIG_FILE = ".webstudio/config.json";
16
+ var LOCAL_DATA_FILE = ".webstudio/data.json";
17
+ var zLocalConfig = z.object({
18
+ projectId: z.string()
19
+ });
20
+ var jsonToLocalConfig = (json) => {
21
+ return zLocalConfig.parse(json);
22
+ };
23
+ var zGlobalConfig = z.record(
24
+ z.union([
25
+ z.object({
26
+ // origin mistakenly called host in the past
27
+ host: z.string(),
28
+ token: z.string()
29
+ }),
30
+ z.object({
31
+ origin: z.string(),
32
+ token: z.string()
33
+ })
34
+ ]).transform((value) => {
35
+ if ("host" in value) {
36
+ return {
37
+ origin: value.host,
38
+ token: value.token
39
+ };
40
+ }
41
+ return value;
42
+ })
43
+ );
44
+ var jsonToGlobalConfig = (json) => {
45
+ return zGlobalConfig.parse(json);
46
+ };
47
+ var PROJECT_TEMPALTES = [
48
+ "vercel",
49
+ "netlify-functions",
50
+ "netlify-edge-functions"
51
+ ];
52
+
53
+ // src/fs-utils.ts
54
+ import { dirname } from "node:path";
55
+ import {
56
+ access,
57
+ mkdir,
58
+ writeFile,
59
+ constants,
60
+ readFile
61
+ } from "node:fs/promises";
62
+ var isFileExists = async (filePath) => {
63
+ try {
64
+ await access(filePath, constants.F_OK);
65
+ return true;
66
+ } catch {
67
+ return false;
68
+ }
69
+ };
70
+ var ensureFileInPath = async (filePath, content) => {
71
+ const dir = dirname(filePath);
72
+ await ensureFolderExists(dir);
73
+ try {
74
+ await access(filePath, constants.F_OK);
75
+ } catch {
76
+ await writeFile(filePath, content || "", "utf8");
77
+ }
78
+ };
79
+ var ensureFolderExists = async (folderPath) => {
80
+ try {
81
+ await access(folderPath, constants.F_OK);
82
+ } catch {
83
+ await mkdir(folderPath, { recursive: true });
84
+ }
85
+ };
86
+ var loadJSONFile = async (filePath) => {
87
+ try {
88
+ const content = await readFile(filePath, "utf8");
89
+ return JSON.parse(content);
90
+ } catch (error) {
91
+ return null;
92
+ }
93
+ };
94
+
95
+ // src/commands/link.ts
96
+ import { stdin, stdout, cwd } from "node:process";
97
+ import { join as join2 } from "node:path";
98
+ import * as readline from "node:readline/promises";
99
+ import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
100
+ var linkOptions = (yargs) => yargs.option("link", {
101
+ alias: "l",
102
+ type: "string",
103
+ describe: "Link to a webstudio project"
104
+ });
105
+ var link = async (options) => {
106
+ let shareLink;
107
+ if (options.link) {
108
+ shareLink = options.link;
109
+ } else {
110
+ const rl = readline.createInterface({ input: stdin, output: stdout });
111
+ shareLink = await rl.question(
112
+ `Please paste a link from the Share Dialog in the builder: `
113
+ );
114
+ rl.close();
115
+ }
116
+ const shareLinkUrl = new URL(shareLink);
117
+ const origin = shareLinkUrl.origin;
118
+ const token = shareLinkUrl.searchParams.get("authToken");
119
+ const paths = shareLinkUrl.pathname.split("/").slice(1);
120
+ if (paths[0] !== "builder" || paths.length !== 2) {
121
+ throw new Error("Invalid share link.");
122
+ }
123
+ const projectId = paths[1];
124
+ if (token == null) {
125
+ throw new Error("Invalid share link.");
126
+ }
127
+ try {
128
+ const currentConfig = await readFile2(GLOBAL_CONFIG_FILE, "utf-8");
129
+ const currentConfigJson = jsonToGlobalConfig(JSON.parse(currentConfig));
130
+ const newConfig = {
131
+ ...currentConfigJson,
132
+ [projectId]: {
133
+ origin,
134
+ token
135
+ }
136
+ };
137
+ await writeFile2(GLOBAL_CONFIG_FILE, JSON.stringify(newConfig, null, 2));
138
+ console.info(`Saved credentials for project ${projectId}.
139
+ You can find your config at ${GLOBAL_CONFIG_FILE}
140
+ `);
141
+ const localConfig = {
142
+ projectId
143
+ };
144
+ await ensureFileInPath(
145
+ join2(cwd(), LOCAL_CONFIG_FILE),
146
+ JSON.stringify(localConfig, null, 2)
147
+ );
148
+ } catch (error) {
149
+ if (error instanceof Error && "code" in error && error.code === "ENONET") {
150
+ throw new Error(`Global config file is not found`);
151
+ }
152
+ throw error;
153
+ }
154
+ };
155
+
156
+ // src/commands/sync.ts
157
+ import { readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
158
+ import { cwd as cwd2 } from "node:process";
159
+ import { join as join3 } from "node:path";
160
+ import ora from "ora";
161
+ import {
162
+ loadProjectDataByBuildId,
163
+ loadProjectDataById
164
+ } from "@webstudio-is/http-client";
165
+ import pc from "picocolors";
166
+ var syncOptions = (yargs) => yargs.option("buildId", {
167
+ type: "string",
168
+ describe: "[Experimental] Project build id to sync"
169
+ }).option("origin", {
170
+ type: "string",
171
+ describe: "[Experimental] Remote origin to sync with"
172
+ }).option("authToken", {
173
+ type: "string",
174
+ describe: "[Experimental] Service token"
175
+ });
176
+ var sync = async (options) => {
177
+ const spinner = ora("Syncing project data").start();
178
+ spinner.text = "Loading project data from config file";
179
+ const definedOptionValues = [
180
+ options.buildId,
181
+ options.origin,
182
+ options.authToken
183
+ ].filter(Boolean);
184
+ if (definedOptionValues.length > 0 && definedOptionValues.length < 3) {
185
+ spinner.fail(`Please provide buildId, origin and authToken`);
186
+ return;
187
+ }
188
+ let project;
189
+ if (options.buildId !== void 0 && options.origin !== void 0 && options.authToken !== void 0) {
190
+ project = await loadProjectDataByBuildId({
191
+ buildId: options.buildId,
192
+ authToken: options.authToken,
193
+ origin: options.origin
194
+ });
195
+ } else {
196
+ if (await isFileExists(GLOBAL_CONFIG_FILE) === false) {
197
+ spinner.fail(
198
+ `Global config file at path ${GLOBAL_CONFIG_FILE} is not found. Please link your project using webstudio link command`
199
+ );
200
+ return;
201
+ }
202
+ const globalConfigText = await readFile3(GLOBAL_CONFIG_FILE, "utf-8");
203
+ const globalConfig = jsonToGlobalConfig(JSON.parse(globalConfigText));
204
+ if (await isFileExists(LOCAL_CONFIG_FILE) === false) {
205
+ spinner.fail(
206
+ `Local config file is not found. Please make sure current directory is a webstudio project`
207
+ );
208
+ return;
209
+ }
210
+ const localConfigText = await readFile3(
211
+ join3(cwd2(), LOCAL_CONFIG_FILE),
212
+ "utf-8"
213
+ );
214
+ const localConfig = jsonToLocalConfig(JSON.parse(localConfigText));
215
+ const projectConfig = globalConfig[localConfig.projectId];
216
+ if (projectConfig === void 0) {
217
+ spinner.fail(
218
+ `Project config is not found, please run ${pc.dim("webstudio link")}`
219
+ );
220
+ return;
221
+ }
222
+ const { origin, token } = projectConfig;
223
+ spinner.text = "Loading project data from webstudio\n";
224
+ project = await loadProjectDataById({
225
+ projectId: localConfig.projectId,
226
+ authToken: token,
227
+ origin
228
+ });
229
+ }
230
+ project;
231
+ spinner.text = "Saving project data to config file";
232
+ const localBuildFilePath = join3(cwd2(), LOCAL_DATA_FILE);
233
+ await ensureFileInPath(localBuildFilePath);
234
+ await writeFile3(localBuildFilePath, JSON.stringify(project, null, 2), "utf8");
235
+ spinner.succeed("Project data synced successfully");
236
+ };
237
+
238
+ // src/commands/build.ts
239
+ import { access as access3 } from "node:fs/promises";
240
+
241
+ // src/prebuild.ts
242
+ import { basename, dirname as dirname2, join as join4, normalize } from "node:path";
243
+ import { createWriteStream } from "node:fs";
244
+ import {
245
+ rm,
246
+ access as access2,
247
+ mkdtemp,
248
+ rename,
249
+ cp,
250
+ readFile as readFile4,
251
+ writeFile as writeFile4,
252
+ readdir
253
+ } from "node:fs/promises";
254
+ import { pipeline } from "node:stream/promises";
255
+ import { tmpdir } from "node:os";
256
+ import { cwd as cwd3 } from "node:process";
257
+ import { fileURLToPath, pathToFileURL } from "node:url";
258
+ import pLimit from "p-limit";
259
+ import ora2 from "ora";
260
+ import merge from "deepmerge";
261
+ import {
262
+ generateCssText,
263
+ generateUtilsExport,
264
+ generatePageComponent,
265
+ getIndexesWithinAncestors,
266
+ namespaceMeta,
267
+ normalizeProps
268
+ } from "@webstudio-is/react-sdk";
269
+ import {
270
+ createScope,
271
+ findTreeInstanceIds,
272
+ parseComponentName
273
+ } from "@webstudio-is/sdk";
274
+ import { createImageLoader } from "@webstudio-is/image";
275
+ import * as baseComponentMetas from "@webstudio-is/sdk-components-react/metas";
276
+ import * as remixComponentMetas from "@webstudio-is/sdk-components-react-remix/metas";
277
+ import * as radixComponentMetas from "@webstudio-is/sdk-components-react-radix/metas";
278
+ var limit = pLimit(10);
279
+ var downloadAsset = async (url, name, assetBaseUrl, temporaryDir) => {
280
+ const assetPath = join4("public", assetBaseUrl, name);
281
+ const tempAssetPath = join4(temporaryDir, name);
282
+ try {
283
+ await access2(assetPath);
284
+ } catch {
285
+ try {
286
+ const response = await fetch(url);
287
+ if (!response.ok) {
288
+ throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
289
+ }
290
+ const writableStream = createWriteStream(tempAssetPath);
291
+ await pipeline(
292
+ response.body,
293
+ writableStream
294
+ );
295
+ await ensureFolderExists(dirname2(assetPath));
296
+ await rename(tempAssetPath, assetPath);
297
+ } catch (error) {
298
+ console.error(`Error in downloading file ${name}
299
+ ${error}`);
300
+ }
301
+ }
302
+ };
303
+ var mergeJsonFiles = async (sourcePath, destinationPath) => {
304
+ const sourceJson = await readFile4(sourcePath, "utf8");
305
+ const destinationJson = await readFile4(destinationPath, "utf8").catch(
306
+ (error) => {
307
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
308
+ return "{}";
309
+ }
310
+ throw new Error(error);
311
+ }
312
+ );
313
+ const content = JSON.stringify(
314
+ merge(JSON.parse(sourceJson), JSON.parse(destinationJson)),
315
+ null,
316
+ " "
317
+ );
318
+ await writeFile4(destinationPath, content, "utf8");
319
+ };
320
+ var isCliTemplate = async (template) => {
321
+ const currentPath = fileURLToPath(new URL(import.meta.url));
322
+ const templatesPath = normalize(
323
+ join4(dirname2(currentPath), "..", "templates")
324
+ );
325
+ const dirents = await readdir(templatesPath, { withFileTypes: true });
326
+ for (const dirent of dirents) {
327
+ if (dirent.isDirectory() && dirent.name === template) {
328
+ return true;
329
+ }
330
+ }
331
+ return false;
332
+ };
333
+ var copyTemplates = async (template = "defaults") => {
334
+ const currentPath = fileURLToPath(new URL(import.meta.url));
335
+ const templatesPath = await isCliTemplate(template) ? normalize(join4(dirname2(currentPath), "..", "templates", template)) : template;
336
+ await cp(templatesPath, cwd3(), {
337
+ recursive: true,
338
+ filter: (source) => {
339
+ return basename(source) !== "package.json";
340
+ }
341
+ });
342
+ if (await isFileExists(join4(templatesPath, "package.json")) === true) {
343
+ await mergeJsonFiles(
344
+ join4(templatesPath, "package.json"),
345
+ join4(cwd3(), "package.json")
346
+ );
347
+ }
348
+ };
349
+ var prebuild = async (options) => {
350
+ if (options.template !== void 0 && await isCliTemplate(options.template) === false && options.template.startsWith(".") === false) {
351
+ throw Error(
352
+ `
353
+ Template ${options.template} is not available
354
+ Please check webstudio --help for more details`
355
+ );
356
+ }
357
+ const spinner = ora2("Scaffolding the project files");
358
+ spinner.start();
359
+ spinner.text = "Generating files";
360
+ await copyTemplates();
361
+ if (options.template !== void 0) {
362
+ await copyTemplates(options.template);
363
+ }
364
+ const constants2 = await import(pathToFileURL(join4(cwd3(), "app/constants.mjs")).href);
365
+ const { assetBaseUrl } = constants2;
366
+ const siteData = await loadJSONFile(LOCAL_DATA_FILE);
367
+ if (siteData === null) {
368
+ throw new Error(
369
+ `Project data is missing, please make sure you the project is synced.`
370
+ );
371
+ }
372
+ const domain = siteData.build.deployment?.projectDomain;
373
+ if (domain === void 0) {
374
+ throw new Error(`Project domain is missing from the site data`);
375
+ }
376
+ const remixRoutes = {
377
+ routes: []
378
+ };
379
+ const radixComponentNamespacedMetas = Object.entries(
380
+ radixComponentMetas
381
+ ).reduce(
382
+ (r, [name, meta]) => {
383
+ const namespace = "@webstudio-is/sdk-components-react-radix";
384
+ r[`${namespace}:${name}`] = namespaceMeta(
385
+ meta,
386
+ namespace,
387
+ new Set(Object.keys(radixComponentMetas))
388
+ );
389
+ return r;
390
+ },
391
+ {}
392
+ );
393
+ const metas = new Map(
394
+ Object.entries({
395
+ ...baseComponentMetas,
396
+ ...radixComponentNamespacedMetas,
397
+ ...remixComponentMetas
398
+ })
399
+ );
400
+ const projectMetas = /* @__PURE__ */ new Map();
401
+ const componentsByPage = {};
402
+ const siteDataByPage = {};
403
+ for (const page of Object.values(siteData.pages)) {
404
+ const originPath = page.path;
405
+ const path = originPath === "" ? "index" : originPath.replace("/", "");
406
+ if (path !== "index") {
407
+ remixRoutes.routes.push({
408
+ path: originPath === "" ? "/" : originPath,
409
+ file: `routes/${path}.tsx`
410
+ });
411
+ }
412
+ const instanceMap = new Map(siteData.build.instances);
413
+ const pageInstanceSet = findTreeInstanceIds(
414
+ instanceMap,
415
+ page.rootInstanceId
416
+ );
417
+ const instances = siteData.build.instances.filter(([id]) => pageInstanceSet.has(id));
418
+ const dataSources = [];
419
+ const normalizedProps = normalizeProps({
420
+ props: siteData.build.props.map(([_id, prop]) => prop),
421
+ assetBaseUrl,
422
+ assets: new Map(siteData.assets.map((asset) => [asset.id, asset])),
423
+ pages: new Map(siteData.pages.map((page2) => [page2.id, page2]))
424
+ });
425
+ const props = [];
426
+ for (const prop of normalizedProps) {
427
+ if (pageInstanceSet.has(prop.instanceId)) {
428
+ props.push([prop.id, prop]);
429
+ }
430
+ }
431
+ for (const [dataSourceId, dataSource] of siteData.build.dataSources) {
432
+ if (dataSource.scopeInstanceId === void 0 || pageInstanceSet.has(dataSource.scopeInstanceId)) {
433
+ dataSources.push([dataSourceId, dataSource]);
434
+ }
435
+ }
436
+ siteDataByPage[path] = {
437
+ build: {
438
+ props,
439
+ instances,
440
+ dataSources
441
+ },
442
+ pages: siteData.pages,
443
+ page,
444
+ assets: siteData.assets
445
+ };
446
+ componentsByPage[path] = /* @__PURE__ */ new Set();
447
+ for (const [_instanceId, instance] of instances) {
448
+ if (instance.component) {
449
+ componentsByPage[path].add(instance.component);
450
+ const meta = metas.get(instance.component);
451
+ if (meta) {
452
+ projectMetas.set(instance.component, meta);
453
+ }
454
+ }
455
+ }
456
+ }
457
+ const assetsToDownload = [];
458
+ const fontAssets = [];
459
+ const appDomain = options.preview ? "wstd.work" : "wstd.io";
460
+ const assetBuildUrl = `https://${domain}.${appDomain}/cgi/asset/`;
461
+ const temporaryDir = await mkdtemp(join4(tmpdir(), "webstudio-"));
462
+ const imageLoader = createImageLoader({
463
+ imageBaseUrl: assetBuildUrl
464
+ });
465
+ if (options.assets === true) {
466
+ for (const asset of siteData.assets) {
467
+ if (asset.type === "image") {
468
+ const imageSrc = imageLoader({
469
+ width: 16,
470
+ quality: 100,
471
+ src: asset.name,
472
+ format: "raw"
473
+ });
474
+ assetsToDownload.push(
475
+ limit(
476
+ () => downloadAsset(imageSrc, asset.name, assetBaseUrl, temporaryDir)
477
+ )
478
+ );
479
+ }
480
+ if (asset.type === "font") {
481
+ assetsToDownload.push(
482
+ limit(
483
+ () => downloadAsset(
484
+ `${assetBuildUrl}${asset.name}`,
485
+ asset.name,
486
+ assetBaseUrl,
487
+ temporaryDir
488
+ )
489
+ )
490
+ );
491
+ fontAssets.push(asset);
492
+ }
493
+ }
494
+ }
495
+ spinner.text = "Generating routes and pages";
496
+ const appRoot = "app";
497
+ const generatedDir = join4(appRoot, "__generated__");
498
+ await rm(generatedDir, { recursive: true, force: true });
499
+ const routesDir = join4(appRoot, "routes");
500
+ await rm(routesDir, { recursive: true, force: true });
501
+ const routeFileTemplate = await readFile4(
502
+ normalize(
503
+ join4(
504
+ dirname2(fileURLToPath(new URL(import.meta.url))),
505
+ "..",
506
+ "templates",
507
+ "route-template.tsx"
508
+ )
509
+ ),
510
+ "utf8"
511
+ );
512
+ for (const [pathName, pageComponents] of Object.entries(componentsByPage)) {
513
+ const scope = createScope([
514
+ // manually maintained list of occupied identifiers
515
+ "useState",
516
+ "ReactNode",
517
+ "PageData",
518
+ "Asset",
519
+ "fontAssets",
520
+ "pageData",
521
+ "user",
522
+ "projectId",
523
+ "formsProperties",
524
+ "Page",
525
+ "props"
526
+ ]);
527
+ const namespaces = /* @__PURE__ */ new Map();
528
+ const BASE_NAMESPACE = "@webstudio-is/sdk-components-react";
529
+ const REMIX_NAMESPACE = "@webstudio-is/sdk-components-react-remix";
530
+ for (const component of pageComponents) {
531
+ const parsed = parseComponentName(component);
532
+ let [namespace] = parsed;
533
+ const [_namespace, shortName] = parsed;
534
+ if (namespace === void 0) {
535
+ if (shortName in remixComponentMetas) {
536
+ namespace = REMIX_NAMESPACE;
537
+ } else {
538
+ namespace = BASE_NAMESPACE;
539
+ }
540
+ }
541
+ if (namespaces.has(namespace) === false) {
542
+ namespaces.set(
543
+ namespace,
544
+ /* @__PURE__ */ new Set()
545
+ );
546
+ }
547
+ namespaces.get(namespace)?.add([shortName, component]);
548
+ }
549
+ let componentImports = "";
550
+ for (const [namespace, componentsSet] of namespaces.entries()) {
551
+ const specifiers = Array.from(componentsSet).map(
552
+ ([shortName, component]) => `${shortName} as ${scope.getName(component, shortName)}`
553
+ ).join(", ");
554
+ componentImports += `import { ${specifiers} } from "${namespace}";
555
+ `;
556
+ }
557
+ const pageData = siteDataByPage[pathName];
558
+ const renderedPageData = {
559
+ page: pageData.page
560
+ };
561
+ const rootInstanceId = pageData.page.rootInstanceId;
562
+ const instances = new Map(pageData.build.instances);
563
+ const props = new Map(pageData.build.props);
564
+ const dataSources = new Map(pageData.build.dataSources);
565
+ const utilsExport = generateUtilsExport({
566
+ pages: siteData.build.pages,
567
+ props
568
+ });
569
+ const pageComponent = generatePageComponent({
570
+ scope,
571
+ rootInstanceId,
572
+ instances,
573
+ props,
574
+ dataSources,
575
+ indexesWithinAncestors: getIndexesWithinAncestors(
576
+ projectMetas,
577
+ instances,
578
+ [rootInstanceId]
579
+ )
580
+ });
581
+ const pageExports = `/* eslint-disable */
582
+ /* This is a auto generated file for building the project */
583
+
584
+ import { type ReactNode, useState } from "react";
585
+ import type { PageData } from "~/routes/_index";
586
+ import type { Asset } from "@webstudio-is/sdk";
587
+ ${componentImports}
588
+ export const fontAssets: Asset[] = ${JSON.stringify(fontAssets)}
589
+ export const pageData: PageData = ${JSON.stringify(renderedPageData)};
590
+ export const user: { email: string | null } | undefined = ${JSON.stringify(
591
+ siteData.user
592
+ )};
593
+ export const projectId = "${siteData.build.projectId}";
594
+
595
+ ${pageComponent}
596
+
597
+ export { Page }
598
+
599
+ ${utilsExport}
600
+ `;
601
+ const fileName = pathName === "main" || pathName === "index" ? "_index.tsx" : `${pathName.split("/").map((route) => `[${route}]`).join(".")}._index.tsx`;
602
+ const routeFileContent = routeFileTemplate.replace(
603
+ "../__generated__/index",
604
+ `../__generated__/${fileName}`
605
+ );
606
+ await ensureFileInPath(join4(routesDir, fileName), routeFileContent);
607
+ await ensureFileInPath(join4(generatedDir, fileName), pageExports);
608
+ }
609
+ spinner.text = "Generating css file";
610
+ const cssText = generateCssText(
611
+ {
612
+ assets: siteData.assets,
613
+ breakpoints: siteData.build?.breakpoints,
614
+ styles: siteData.build?.styles,
615
+ styleSourceSelections: siteData.build?.styleSourceSelections,
616
+ // pass only used metas to not generate unused preset styles
617
+ componentMetas: projectMetas
618
+ },
619
+ {
620
+ assetBaseUrl
621
+ }
622
+ );
623
+ await ensureFileInPath(join4(generatedDir, "index.css"), cssText);
624
+ spinner.text = "Downloading fonts and images";
625
+ await Promise.all(assetsToDownload);
626
+ spinner.succeed("Build finished");
627
+ };
628
+
629
+ // src/commands/build.ts
630
+ var buildOptions = (yargs) => yargs.option("assets", {
631
+ type: "boolean",
632
+ default: true,
633
+ describe: "[Experimental] Download assets"
634
+ }).option("preview", {
635
+ type: "boolean",
636
+ default: false,
637
+ describe: "[Experimental] Use preview version of the project"
638
+ }).option("template", {
639
+ type: "string",
640
+ default: "vercel",
641
+ describe: `[Experimental] Template to use for the build [choices: ${PROJECT_TEMPALTES.toString()}]`
642
+ });
643
+ var build = async (options) => {
644
+ try {
645
+ await access3(LOCAL_DATA_FILE);
646
+ } catch (error) {
647
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
648
+ throw new Error(
649
+ `You need to link a webstudio project before building it. Run \`webstudio link\` to link a project.`
650
+ );
651
+ }
652
+ throw error;
653
+ }
654
+ await prebuild(options);
655
+ };
656
+
657
+ // src/commands/init-flow.ts
658
+ import { chdir, cwd as cwd4 } from "node:process";
659
+ import { join as join5 } from "node:path";
660
+ import ora3 from "ora";
661
+
662
+ // src/prompts.ts
663
+ import prompts from "prompts";
664
+ var prompt = (prompt2) => {
665
+ return prompts([
666
+ {
667
+ ...prompt2,
668
+ onState: (state) => {
669
+ if (state.aborted) {
670
+ process.nextTick(() => {
671
+ process.exit(0);
672
+ });
673
+ }
674
+ }
675
+ }
676
+ ]);
677
+ };
678
+
679
+ // src/commands/init-flow.ts
680
+ import pc2 from "picocolors";
681
+ import { $ } from "execa";
682
+ import { titleCase } from "title-case";
683
+ var initFlow = async (options) => {
684
+ const isProjectConfigured = await isFileExists(".webstudio/config.json");
685
+ let shouldInstallDeps = false;
686
+ let folderName;
687
+ let projectTemplate = void 0;
688
+ if (isProjectConfigured === false) {
689
+ const { shouldCreateFolder } = await prompt({
690
+ type: "confirm",
691
+ name: "shouldCreateFolder",
692
+ message: "Would you like to create a project folder? (no to use current folder)",
693
+ initial: true
694
+ });
695
+ if (shouldCreateFolder === true) {
696
+ folderName = (await prompt({
697
+ type: "text",
698
+ name: "folderName",
699
+ message: "Please enter a project name"
700
+ })).folderName;
701
+ if (folderName === void 0) {
702
+ throw new Error("Folder name is required");
703
+ }
704
+ await ensureFolderExists(join5(cwd4(), folderName));
705
+ chdir(join5(cwd4(), folderName));
706
+ }
707
+ const { projectLink } = await prompt({
708
+ type: "text",
709
+ name: "projectLink",
710
+ message: "Please paste a link from the Share dialog in the builder"
711
+ });
712
+ if (projectLink === void 0) {
713
+ throw new Error(`Project Link is required`);
714
+ }
715
+ await link({ link: projectLink });
716
+ const { shouldSetupDeployTarget } = await prompt({
717
+ type: "confirm",
718
+ name: "shouldSetupDeployTarget",
719
+ message: "Would you like to setup a deploy target?",
720
+ initial: false
721
+ });
722
+ if (shouldSetupDeployTarget === true) {
723
+ const { deployTarget } = await prompt({
724
+ type: "select",
725
+ name: "deployTarget",
726
+ message: "Where would you like to deploy your project?",
727
+ choices: PROJECT_TEMPALTES.map((template) => {
728
+ return {
729
+ title: titleCase(template),
730
+ value: template
731
+ };
732
+ })
733
+ });
734
+ projectTemplate = deployTarget;
735
+ }
736
+ const { installDeps } = await prompt({
737
+ type: "confirm",
738
+ name: "installDeps",
739
+ message: "Would you like to install dependencies? (recommended)",
740
+ initial: true
741
+ });
742
+ shouldInstallDeps = installDeps;
743
+ }
744
+ await sync({ buildId: void 0, origin: void 0, authToken: void 0 });
745
+ await build({
746
+ ...options,
747
+ ...projectTemplate && { template: projectTemplate }
748
+ });
749
+ if (shouldInstallDeps === true) {
750
+ const spinner = ora3().start();
751
+ spinner.text = "Installing dependencies";
752
+ await $`npm install`;
753
+ spinner.succeed("Installed dependencies");
754
+ }
755
+ console.info(pc2.bold(pc2.green(`
756
+ Your project was successfully synced \u{1F389}`)));
757
+ console.info(
758
+ [
759
+ "Now you can:",
760
+ folderName && `Go to your project: ${pc2.dim(`cd ${folderName}`)}`,
761
+ `Run ${pc2.dim("npm run dev")} to preview your site on a local server.`,
762
+ projectTemplate && getDeploymentInstructions(projectTemplate)
763
+ ].filter(Boolean).join("\n")
764
+ );
765
+ };
766
+ var getDeploymentInstructions = (deployTarget) => {
767
+ switch (deployTarget) {
768
+ case "vercel":
769
+ return `Run ${pc2.dim("npx vercel")} to publish on Vercel.`;
770
+ case "netlify-functions":
771
+ case "netlify-edge-functions":
772
+ return [
773
+ `To deploy to Netlify, run the following commands: `,
774
+ `Run ${pc2.dim("npx netlify-cli login")} to login to Netlify.`,
775
+ `Run ${pc2.dim("npx netlify-cli sites:create")} to create a new site.`,
776
+ `Run ${pc2.dim("npx netlify-cli build")} to build the site`,
777
+ `Run ${pc2.dim("npx netlify-cli deploy")} to deploy on Netlify.`
778
+ ].join("\n");
779
+ }
780
+ };
781
+
782
+ // src/cli.ts
783
+ import makeCLI from "yargs";
784
+
785
+ // package.json
786
+ var package_default = {
787
+ name: "webstudio",
788
+ version: "0.105.0",
789
+ description: "Webstudio CLI",
790
+ author: "Webstudio <github@webstudio.is>",
791
+ homepage: "https://webstudio.is",
792
+ type: "module",
793
+ bin: {
794
+ "webstudio-cli": "./bin.js",
795
+ webstudio: "./bin.js"
796
+ },
797
+ files: [
798
+ "lib/*",
799
+ "templates/*",
800
+ "bin.js",
801
+ "!*.{test,stories}.*"
802
+ ],
803
+ scripts: {
804
+ typecheck: "tsc",
805
+ checks: "pnpm typecheck",
806
+ build: "rm -rf lib && esbuild src/cli.ts --outdir=lib --bundle --format=esm --packages=external",
807
+ "local-run": "tsx --no-warnings ./src/bin.ts",
808
+ dev: "esbuild src/cli.ts --watch --bundle --format=esm --packages=external --outdir=./lib"
809
+ },
810
+ license: "AGPL-3.0-or-later",
811
+ dependencies: {
812
+ "@webstudio-is/http-client": "workspace:*",
813
+ "@webstudio-is/image": "workspace:*",
814
+ "@webstudio-is/react-sdk": "workspace:*",
815
+ "@webstudio-is/sdk": "workspace:*",
816
+ "@webstudio-is/sdk-components-react": "workspace:*",
817
+ "@webstudio-is/sdk-components-react-radix": "workspace:*",
818
+ "@webstudio-is/sdk-components-react-remix": "workspace:*",
819
+ deepmerge: "^4.3.1",
820
+ "env-paths": "^3.0.0",
821
+ execa: "^7.2.0",
822
+ ora: "^7.0.1",
823
+ "p-limit": "^4.0.0",
824
+ picocolors: "^1.0.0",
825
+ prompts: "^2.4.2",
826
+ "strip-indent": "^4.0.0",
827
+ "title-case": "^4.1.0",
828
+ yargs: "^17.7.2",
829
+ zod: "^3.21.4"
830
+ },
831
+ devDependencies: {
832
+ "@types/node": "^18.17.1",
833
+ "@types/prompts": "^2.4.5",
834
+ "@webstudio-is/form-handlers": "workspace:*",
835
+ "@webstudio-is/tsconfig": "workspace:*",
836
+ tsx: "^3.12.8",
837
+ typescript: "5.2.2"
838
+ }
839
+ };
840
+
841
+ // src/cli.ts
842
+ var main = async () => {
843
+ try {
844
+ await ensureFileInPath(GLOBAL_CONFIG_FILE, "{}");
845
+ const cmd = makeCLI(hideBin(argv)).strict().fail(function(msg, err, yargs) {
846
+ if (err) {
847
+ throw err;
848
+ }
849
+ console.error(msg);
850
+ console.error(yargs.help());
851
+ process.exit(1);
852
+ }).wrap(null).option("v", {
853
+ describe: "Show version number",
854
+ type: "boolean"
855
+ }).option("h", {
856
+ describe: "Show all commands",
857
+ alias: "help",
858
+ type: "boolean"
859
+ }).scriptName("webstudio").usage(
860
+ `Webstudio CLI (${package_default.version}) allows you to setup, sync, build and preview your project.`
861
+ );
862
+ cmd.version(package_default.version).alias("v", "version");
863
+ cmd.command(["build"], "Build the project", buildOptions, build);
864
+ cmd.command(["link"], "Link the project with the cloud", linkOptions, link);
865
+ cmd.command(["sync"], "Sync your project", syncOptions, sync);
866
+ cmd.command(["$0", "init"], "Setup the project", buildOptions, initFlow);
867
+ await cmd.parse();
868
+ } catch (error) {
869
+ console.error(error);
870
+ exit(1);
871
+ }
872
+ };
873
+ export {
874
+ main
875
+ };