webstudio 0.163.0 → 0.167.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 CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/cli.ts
2
- import { exit, argv } from "node:process";
2
+ import { exit as exit4, argv } from "node:process";
3
3
  import { hideBin } from "yargs/helpers";
4
4
 
5
5
  // src/config.ts
@@ -94,10 +94,41 @@ var loadJSONFile = async (filePath) => {
94
94
  };
95
95
 
96
96
  // src/commands/link.ts
97
- import { stdin, stdout, cwd } from "node:process";
97
+ import { cwd, exit } from "node:process";
98
98
  import { join as join2 } from "node:path";
99
- import * as readline from "node:readline/promises";
100
99
  import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
100
+ import { cancel, isCancel, log, text } from "@clack/prompts";
101
+ var parseShareLink = (value) => {
102
+ const url = new URL(value);
103
+ const origin = url.origin;
104
+ const token = url.searchParams.get("authToken");
105
+ const segments = url.pathname.split("/").slice(1);
106
+ if (segments.length !== 2 || segments[0] !== "builder") {
107
+ throw Error("Segments not matching");
108
+ }
109
+ const [_builder, projectId] = segments;
110
+ if (token == null) {
111
+ throw Error("Token is missing");
112
+ }
113
+ return {
114
+ origin,
115
+ projectId,
116
+ token
117
+ };
118
+ };
119
+ var validateShareLink = (value) => {
120
+ if (value.length === 0) {
121
+ return "Share link is required";
122
+ }
123
+ if (URL.canParse(value) === false) {
124
+ return "Share link is invalid";
125
+ }
126
+ try {
127
+ parseShareLink(value);
128
+ } catch {
129
+ return "Share link is invalid";
130
+ }
131
+ };
101
132
  var linkOptions = (yargs) => yargs.option("link", {
102
133
  alias: "l",
103
134
  type: "string",
@@ -108,62 +139,48 @@ var link = async (options) => {
108
139
  if (options.link) {
109
140
  shareLink = options.link;
110
141
  } else {
111
- const rl = readline.createInterface({ input: stdin, output: stdout });
112
- shareLink = await rl.question(
113
- `Please paste a link from the Share Dialog in the builder: `
114
- );
115
- rl.close();
116
- }
117
- const shareLinkUrl = new URL(shareLink);
118
- const origin = shareLinkUrl.origin;
119
- const token = shareLinkUrl.searchParams.get("authToken");
120
- const paths = shareLinkUrl.pathname.split("/").slice(1);
121
- if (paths[0] !== "builder" || paths.length !== 2) {
122
- throw new Error("Invalid share link.");
123
- }
124
- const projectId = paths[1];
125
- if (token == null) {
126
- throw new Error("Invalid share link.");
127
- }
128
- try {
129
- const currentConfig = await readFile2(GLOBAL_CONFIG_FILE, "utf-8");
130
- const currentConfigJson = jsonToGlobalConfig(JSON.parse(currentConfig));
131
- const newConfig = {
132
- ...currentConfigJson,
133
- [projectId]: {
134
- origin,
135
- token
136
- }
137
- };
138
- await writeFile2(GLOBAL_CONFIG_FILE, JSON.stringify(newConfig, null, 2));
139
- console.info(`Saved credentials for project ${projectId}.
140
- You can find your config at ${GLOBAL_CONFIG_FILE}
141
- `);
142
- const localConfig = {
143
- projectId
144
- };
145
- await createFileIfNotExists(
146
- join2(cwd(), LOCAL_CONFIG_FILE),
147
- JSON.stringify(localConfig, null, 2)
148
- );
149
- } catch (error) {
150
- if (error instanceof Error && "code" in error && error.code === "ENONET") {
151
- throw new Error(`Global config file is not found`);
142
+ shareLink = await text({
143
+ message: "Please paste a link from the Share Dialog in the builder",
144
+ validate: validateShareLink
145
+ });
146
+ if (isCancel(shareLink)) {
147
+ cancel("Project linking is cancelled");
148
+ exit(1);
152
149
  }
153
- throw error;
154
150
  }
151
+ const { origin, projectId, token } = parseShareLink(shareLink);
152
+ const currentConfig = await readFile2(GLOBAL_CONFIG_FILE, "utf-8");
153
+ const currentConfigJson = jsonToGlobalConfig(JSON.parse(currentConfig));
154
+ const newConfig = {
155
+ ...currentConfigJson,
156
+ [projectId]: {
157
+ origin,
158
+ token
159
+ }
160
+ };
161
+ await writeFile2(GLOBAL_CONFIG_FILE, JSON.stringify(newConfig, null, 2));
162
+ log.info(`Saved credentials for project ${projectId}.
163
+ You can find your config at ${GLOBAL_CONFIG_FILE}`);
164
+ const localConfig = {
165
+ projectId
166
+ };
167
+ await createFileIfNotExists(
168
+ join2(cwd(), LOCAL_CONFIG_FILE),
169
+ JSON.stringify(localConfig, null, 2)
170
+ );
171
+ log.step("The project is linked successfully");
155
172
  };
156
173
 
157
174
  // src/commands/sync.ts
158
175
  import { readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
159
176
  import { cwd as cwd2 } from "node:process";
160
177
  import { join as join3 } from "node:path";
161
- import ora from "ora";
178
+ import pc from "picocolors";
179
+ import { spinner } from "@clack/prompts";
162
180
  import {
163
181
  loadProjectDataByBuildId,
164
182
  loadProjectDataById
165
183
  } from "@webstudio-is/http-client";
166
- import pc from "picocolors";
167
184
  var syncOptions = (yargs) => yargs.option("buildId", {
168
185
  type: "string",
169
186
  describe: "[Experimental] Project build id to sync"
@@ -175,15 +192,15 @@ var syncOptions = (yargs) => yargs.option("buildId", {
175
192
  describe: "[Experimental] Service token"
176
193
  });
177
194
  var sync = async (options) => {
178
- const spinner = ora("Syncing project data").start();
179
- spinner.text = "Loading project data from config file";
195
+ const syncing = spinner();
196
+ syncing.start("Synchronizing project data");
180
197
  const definedOptionValues = [
181
198
  options.buildId,
182
199
  options.origin,
183
200
  options.authToken
184
201
  ].filter(Boolean);
185
202
  if (definedOptionValues.length > 0 && definedOptionValues.length < 3) {
186
- spinner.fail(`Please provide buildId, origin and authToken`);
203
+ syncing.stop(`Please provide buildId, origin and authToken`, 2);
187
204
  return;
188
205
  }
189
206
  let project;
@@ -194,17 +211,12 @@ var sync = async (options) => {
194
211
  origin: options.origin
195
212
  });
196
213
  } else {
197
- if (await isFileExists(GLOBAL_CONFIG_FILE) === false) {
198
- spinner.fail(
199
- `Global config file at path ${GLOBAL_CONFIG_FILE} is not found. Please link your project using webstudio link command`
200
- );
201
- return;
202
- }
203
214
  const globalConfigText = await readFile3(GLOBAL_CONFIG_FILE, "utf-8");
204
215
  const globalConfig = jsonToGlobalConfig(JSON.parse(globalConfigText));
205
216
  if (await isFileExists(LOCAL_CONFIG_FILE) === false) {
206
- spinner.fail(
207
- `Local config file is not found. Please make sure current directory is a webstudio project`
217
+ syncing.stop(
218
+ `Local config file is not found. Please make sure current directory is a webstudio project`,
219
+ 2
208
220
  );
209
221
  return;
210
222
  }
@@ -215,29 +227,35 @@ var sync = async (options) => {
215
227
  const localConfig = jsonToLocalConfig(JSON.parse(localConfigText));
216
228
  const projectConfig = globalConfig[localConfig.projectId];
217
229
  if (projectConfig === void 0) {
218
- spinner.fail(
219
- `Project config is not found, please run ${pc.dim("webstudio link")}`
230
+ syncing.stop(
231
+ `Project config is not found, please run ${pc.dim("webstudio link")}`,
232
+ 2
220
233
  );
221
234
  return;
222
235
  }
223
236
  const { origin, token } = projectConfig;
224
- spinner.text = "Loading project data from webstudio\n";
225
- project = await loadProjectDataById({
226
- projectId: localConfig.projectId,
227
- authToken: token,
228
- origin
229
- });
237
+ try {
238
+ project = await loadProjectDataById({
239
+ projectId: localConfig.projectId,
240
+ authToken: token,
241
+ origin
242
+ });
243
+ } catch (error) {
244
+ syncing.stop(error.message, 2);
245
+ return;
246
+ }
230
247
  }
231
248
  project;
232
- spinner.text = "Saving project data to config file";
233
249
  const localBuildFilePath = join3(cwd2(), LOCAL_DATA_FILE);
234
250
  await createFileIfNotExists(localBuildFilePath);
235
251
  await writeFile3(localBuildFilePath, JSON.stringify(project, null, 2), "utf8");
236
- spinner.succeed("Project data synced successfully");
252
+ syncing.stop("Project data synchronized successfully");
237
253
  };
238
254
 
239
255
  // src/commands/build.ts
240
256
  import { access as access3 } from "node:fs/promises";
257
+ import { exit as exit3 } from "node:process";
258
+ import { log as log3 } from "@clack/prompts";
241
259
 
242
260
  // src/prebuild.ts
243
261
  import { basename, dirname as dirname2, join as join4, normalize } from "node:path";
@@ -252,10 +270,10 @@ import {
252
270
  readdir
253
271
  } from "node:fs/promises";
254
272
  import { pipeline } from "node:stream/promises";
255
- import { cwd as cwd3 } from "node:process";
273
+ import { cwd as cwd3, exit as exit2 } from "node:process";
256
274
  import { fileURLToPath, pathToFileURL } from "node:url";
257
275
  import pLimit from "p-limit";
258
- import ora2 from "ora";
276
+ import { log as log2, spinner as spinner2 } from "@clack/prompts";
259
277
  import merge from "deepmerge";
260
278
  import {
261
279
  generateCss,
@@ -281,6 +299,122 @@ import { createImageLoader } from "@webstudio-is/image";
281
299
  import * as baseComponentMetas from "@webstudio-is/sdk-components-react/metas";
282
300
  import * as remixComponentMetas from "@webstudio-is/sdk-components-react-remix/metas";
283
301
  import * as radixComponentMetas from "@webstudio-is/sdk-components-react-radix/metas";
302
+
303
+ // src/html-to-jsx.ts
304
+ import {
305
+ parseFragment,
306
+ defaultTreeAdapter
307
+ } from "parse5";
308
+ import { camelCase } from "change-case";
309
+ var BOOLEAN_ATTRIBUTES = /* @__PURE__ */ new Set([
310
+ "async",
311
+ "autofocus",
312
+ "autoplay",
313
+ "checked",
314
+ "contenteditable",
315
+ "controls",
316
+ "default",
317
+ "defer",
318
+ "disabled",
319
+ "formnovalidate",
320
+ "hidden",
321
+ "ismap",
322
+ "itemscope",
323
+ "loop",
324
+ "multiple",
325
+ "muted",
326
+ "nomodule",
327
+ "novalidate",
328
+ "open",
329
+ "playsinline",
330
+ "readonly",
331
+ "required",
332
+ "reversed",
333
+ "scoped",
334
+ "selected",
335
+ "truespeed"
336
+ ]);
337
+ var isBooleanAttr = (name) => BOOLEAN_ATTRIBUTES.has(name.toLowerCase());
338
+ function* walkChildNodes(node) {
339
+ if (defaultTreeAdapter.isCommentNode(node) || defaultTreeAdapter.isTextNode(node) || defaultTreeAdapter.isDocumentTypeNode(node)) {
340
+ throw new Error("Unsupported node type");
341
+ }
342
+ for (const childNode of node.childNodes) {
343
+ if (defaultTreeAdapter.isCommentNode(childNode)) {
344
+ continue;
345
+ }
346
+ if (defaultTreeAdapter.isTextNode(childNode)) {
347
+ yield { type: "text", value: childNode.value };
348
+ continue;
349
+ }
350
+ if (false === defaultTreeAdapter.isElementNode(childNode)) {
351
+ continue;
352
+ }
353
+ const attributes = childNode.attrs.map((attr) => [
354
+ attr.name,
355
+ attr.value
356
+ ]);
357
+ yield { type: "element-start", tagName: childNode.tagName, attributes };
358
+ yield* walkChildNodes(childNode);
359
+ yield { type: "element-end", tagName: childNode.tagName };
360
+ }
361
+ }
362
+ var convertStyleString = (style) => {
363
+ const styles = style.split(";").map((style2) => style2.trim()).map((style2) => style2.split(":").map((part) => part.trim()));
364
+ const res = {};
365
+ for (const [name, value] of styles) {
366
+ res[camelCase(name)] = value;
367
+ }
368
+ return JSON.stringify(res);
369
+ };
370
+ var toAttrString = (name, value) => {
371
+ const attName = name.toLowerCase();
372
+ const jsxName = attName === "class" ? "className" : attName;
373
+ if (value === "" && isBooleanAttr(attName)) {
374
+ return `${jsxName}`;
375
+ }
376
+ if (attName === "style") {
377
+ return `${jsxName}={${convertStyleString(value)}}`;
378
+ }
379
+ return `${jsxName}="${value}"`;
380
+ };
381
+ var attributesToString = (attributes) => attributes.map(([attName, value]) => ` ${toAttrString(attName, value)}`).join("");
382
+ var convertTagName = (tagName) => {
383
+ const tag = tagName.toLowerCase();
384
+ if (tag === "script") {
385
+ return "Script";
386
+ }
387
+ if (tag === "style") {
388
+ return "Style";
389
+ }
390
+ return tag;
391
+ };
392
+ var escapeValue = (value) => value.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$").replace(/\r/g, "\\r").replace(/\n/g, "\\n");
393
+ var htmlToJsx = (html) => {
394
+ const parsedHtml = parseFragment(html, { scriptingEnabled: false });
395
+ let result = "";
396
+ for (const walkNode of walkChildNodes(parsedHtml)) {
397
+ switch (walkNode.type) {
398
+ case "text": {
399
+ const escapedValue = escapeValue(walkNode.value);
400
+ result += escapedValue ? "{`" + escapedValue + "`}" : "";
401
+ break;
402
+ }
403
+ case "element-start": {
404
+ const tag = convertTagName(walkNode.tagName);
405
+ result += `<${tag}${attributesToString(walkNode.attributes)}>`;
406
+ break;
407
+ }
408
+ case "element-end": {
409
+ result += `</${convertTagName(walkNode.tagName)}>`;
410
+ break;
411
+ }
412
+ }
413
+ }
414
+ return result;
415
+ };
416
+
417
+ // src/prebuild.ts
284
418
  var limit = pLimit(10);
285
419
  var downloadAsset = async (url, name, assetBaseUrl) => {
286
420
  const assetPath = join4("public", assetBaseUrl, name);
@@ -357,12 +491,12 @@ var copyTemplates = async (template = "defaults") => {
357
491
  }
358
492
  };
359
493
  var prebuild = async (options) => {
360
- if (options.template === void 0) {
361
- throw new Error(
362
- `
363
- Template is not provided
364
- Please check webstudio --help for more details`
494
+ if (options.template.length === 0) {
495
+ log2.error(
496
+ `Template is not provided
497
+ Please check webstudio --help for more details`
365
498
  );
499
+ exit2(1);
366
500
  }
367
501
  for (const template of options.template) {
368
502
  if (template === "vanilla") {
@@ -372,16 +506,14 @@ var prebuild = async (options) => {
372
506
  continue;
373
507
  }
374
508
  if (await isCliTemplate(template) === false) {
375
- throw Error(
376
- `
377
- Template ${options.template} is not available
378
- Please check webstudio --help for more details`
509
+ log2.error(
510
+ `Template ${options.template} is not available
511
+ Please check webstudio --help for more details`
379
512
  );
513
+ exit2(1);
380
514
  }
381
515
  }
382
- const spinner = ora2("Scaffolding the project files");
383
- spinner.start();
384
- spinner.text = "Generating files";
516
+ log2.step("Scaffolding the project files");
385
517
  const appRoot = "app";
386
518
  const generatedDir = join4(appRoot, "__generated__");
387
519
  await rm(generatedDir, { recursive: true, force: true });
@@ -544,7 +676,6 @@ var prebuild = async (options) => {
544
676
  }
545
677
  }
546
678
  const assets = new Map(siteData.assets.map((asset) => [asset.id, asset]));
547
- spinner.text = "Generating css file";
548
679
  const { cssText, classesMap } = generateCss({
549
680
  instances: new Map(siteData.build.instances),
550
681
  props: new Map(siteData.build.props),
@@ -558,7 +689,6 @@ var prebuild = async (options) => {
558
689
  atomic: siteData.build.pages.compiler?.atomicStyles ?? true
559
690
  });
560
691
  await createFileIfNotExists(join4(generatedDir, "index.css"), cssText);
561
- spinner.text = "Generating routes and pages";
562
692
  const routeTemplatesDir = join4(cwd3(), "app/route-templates");
563
693
  const routeTemplatePath = normalize(join4(routeTemplatesDir, "html.tsx"));
564
694
  const routeXmlTemplatePath = normalize(join4(routeTemplatesDir, "xml.tsx"));
@@ -669,6 +799,9 @@ var prebuild = async (options) => {
669
799
  const pageMeta = pageData.page.meta;
670
800
  const favIconAsset = assets.get(projectMeta?.faviconAssetId ?? "");
671
801
  const socialImageAsset = assets.get(pageMeta.socialImageAssetId ?? "");
802
+ const pagePath = getPagePath(pageData.page.id, siteData.build.pages);
803
+ const remixRoute = generateRemixRoute(pagePath);
804
+ const fileName = `${remixRoute}.tsx`;
672
805
  const pageExports = `/* eslint-disable */
673
806
  /* This is a auto generated file for building the project */
674
807
 
@@ -693,6 +826,29 @@ var prebuild = async (options) => {
693
826
  export const pageBackgroundImageAssets: ImageAsset[] =
694
827
  ${JSON.stringify(pageBackgroundImageAssets)}
695
828
 
829
+ ${remixRoute === "_index" ? `
830
+ ${projectMeta?.code ? `
831
+ const Script = ({children, ...props}: Record<string, string | boolean>) => {
832
+ if (children == null) {
833
+ return <script {...props} />;
834
+ }
835
+
836
+ return <script {...props} dangerouslySetInnerHTML={{__html: children}} />;
837
+ };
838
+ const Style = ({children, ...props}: Record<string, string | boolean>) => {
839
+ if (children == null) {
840
+ return <style {...props} />;
841
+ }
842
+
843
+ return <style {...props} dangerouslySetInnerHTML={{__html: children}} />;
844
+ };
845
+ ` : ""}
846
+
847
+ export const CustomCode = () => {
848
+ return (<>${projectMeta?.code ? htmlToJsx(projectMeta.code) : ""}</>);
849
+ }
850
+ ` : ""}
851
+
696
852
  ${xmlPresentationComponents}
697
853
 
698
854
  ${pageComponent}
@@ -724,14 +880,7 @@ var prebuild = async (options) => {
724
880
  export const projectId = "${siteData.build.projectId}";
725
881
 
726
882
  export const contactEmail = ${JSON.stringify(contactEmail)};
727
-
728
- export const customCode = ${JSON.stringify(
729
- projectMeta?.code?.trim() ?? ""
730
- )};
731
883
  `;
732
- const pagePath = getPagePath(pageData.page.id, siteData.build.pages);
733
- const remixRoute = generateRemixRoute(pagePath);
734
- const fileName = `${remixRoute}.tsx`;
735
884
  const routeFileContent = (documentType === "html" ? routeFileTemplate : routeXmlFileTemplate).replace(
736
885
  /".*\/__generated__\/_index"/,
737
886
  `"../__generated__/${remixRoute}"`
@@ -762,7 +911,6 @@ var prebuild = async (options) => {
762
911
  );
763
912
  const redirects = siteData.build.pages?.redirects;
764
913
  if (redirects !== void 0 && redirects.length > 0) {
765
- spinner.text = "Generating redirects";
766
914
  for (const redirect of redirects) {
767
915
  const redirectPagePath = generateRemixRoute(redirect.old);
768
916
  const redirectFileName = `${redirectPagePath}.ts`;
@@ -775,9 +923,13 @@ var prebuild = async (options) => {
775
923
  await createFileIfNotExists(join4(routesDir, redirectFileName), content);
776
924
  }
777
925
  }
778
- spinner.text = "Downloading fonts and images";
779
- await Promise.all(assetsToDownload);
780
- spinner.succeed("Build finished");
926
+ if (assetsToDownload.length > 0) {
927
+ const downloading = spinner2();
928
+ downloading.start("Downloading fonts and images");
929
+ await Promise.all(assetsToDownload);
930
+ downloading.stop("Downloaded fonts and images");
931
+ }
932
+ log2.step("Build finished");
781
933
  };
782
934
 
783
935
  // src/commands/build.ts
@@ -792,6 +944,7 @@ var buildOptions = (yargs) => yargs.option("assets", {
792
944
  }).option("template", {
793
945
  type: "array",
794
946
  string: true,
947
+ default: [],
795
948
  describe: `Template to use for the build [choices: ${PROJECT_TEMPALTES.join(
796
949
  ", "
797
950
  )}]`
@@ -801,9 +954,10 @@ var build = async (options) => {
801
954
  await access3(LOCAL_DATA_FILE);
802
955
  } catch (error) {
803
956
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
804
- throw new Error(
957
+ log3.error(
805
958
  `You need to link a webstudio project before building it. Run \`webstudio link\` to link a project.`
806
959
  );
960
+ exit3(1);
807
961
  }
808
962
  throw error;
809
963
  }
@@ -813,110 +967,99 @@ var build = async (options) => {
813
967
  // src/commands/init-flow.ts
814
968
  import { chdir, cwd as cwd4 } from "node:process";
815
969
  import { join as join5 } from "node:path";
816
- import ora3 from "ora";
817
-
818
- // src/prompts.ts
819
- import prompts from "prompts";
820
- var prompt = (prompt2) => {
821
- return prompts([
822
- {
823
- ...prompt2,
824
- onState: (state) => {
825
- if (state.aborted) {
826
- process.nextTick(() => {
827
- process.exit(0);
828
- });
829
- }
830
- }
831
- }
832
- ]);
833
- };
834
-
835
- // src/commands/init-flow.ts
836
970
  import pc2 from "picocolors";
971
+ import {
972
+ cancel as cancel2,
973
+ confirm,
974
+ isCancel as isCancel2,
975
+ log as log4,
976
+ select,
977
+ spinner as spinner3,
978
+ text as text2
979
+ } from "@clack/prompts";
837
980
  import { $ } from "execa";
838
981
  import { titleCase } from "title-case";
982
+ var exitIfCancelled = (value) => {
983
+ if (isCancel2(value)) {
984
+ cancel2("Project initialization is cancelled");
985
+ process.exit(0);
986
+ }
987
+ return value;
988
+ };
839
989
  var initFlow = async (options) => {
840
990
  const isProjectConfigured = await isFileExists(".webstudio/config.json");
841
991
  let shouldInstallDeps = false;
842
992
  let folderName;
843
993
  let projectTemplate = void 0;
844
994
  if (isProjectConfigured === false) {
845
- const { shouldCreateFolder } = await prompt({
846
- type: "confirm",
847
- name: "shouldCreateFolder",
848
- message: "Would you like to create a project folder? (no to use current folder)",
849
- initial: true
850
- });
995
+ const shouldCreateFolder = exitIfCancelled(
996
+ await confirm({
997
+ message: "Would you like to create a project folder? (no to use current folder)",
998
+ initialValue: true
999
+ })
1000
+ );
851
1001
  if (shouldCreateFolder === true) {
852
- folderName = (await prompt({
853
- type: "text",
854
- name: "folderName",
855
- message: "Please enter a project name"
856
- })).folderName;
857
- if (folderName === void 0) {
858
- throw new Error("Folder name is required");
859
- }
1002
+ folderName = exitIfCancelled(
1003
+ await text2({
1004
+ message: "Please enter a project name",
1005
+ validate(value) {
1006
+ if (value.length === 0) {
1007
+ return "Folder name is required";
1008
+ }
1009
+ }
1010
+ })
1011
+ );
860
1012
  await createFolderIfNotExists(join5(cwd4(), folderName));
861
1013
  chdir(join5(cwd4(), folderName));
862
1014
  }
863
- const { projectLink } = await prompt({
864
- type: "text",
865
- name: "projectLink",
866
- message: "Please paste a link from the Share dialog in the builder"
867
- });
868
- if (projectLink === void 0) {
869
- throw new Error(`Project Link is required`);
870
- }
871
- await link({ link: projectLink });
872
- const { deployTarget } = await prompt({
873
- type: "select",
874
- name: "deployTarget",
875
- message: "Where would you like to deploy your project?",
876
- choices: PROJECT_TEMPALTES.map((template) => {
877
- return {
878
- title: titleCase(template),
879
- value: template
880
- };
1015
+ const shareLink = exitIfCancelled(
1016
+ await text2({
1017
+ message: "Please paste a link from the Share Dialog in the builder",
1018
+ validate: validateShareLink
881
1019
  })
882
- });
883
- projectTemplate = deployTarget;
884
- const { installDeps } = await prompt({
885
- type: "confirm",
886
- name: "installDeps",
887
- message: "Would you like to install dependencies? (recommended)",
888
- initial: true
889
- });
890
- shouldInstallDeps = installDeps;
1020
+ );
1021
+ await link({ link: shareLink });
1022
+ projectTemplate = exitIfCancelled(
1023
+ await select({
1024
+ message: "Where would you like to deploy your project?",
1025
+ options: PROJECT_TEMPALTES.map((template) => ({
1026
+ value: template,
1027
+ label: titleCase(template)
1028
+ }))
1029
+ })
1030
+ );
1031
+ shouldInstallDeps = exitIfCancelled(
1032
+ await confirm({
1033
+ message: "Would you like to install dependencies? (recommended)",
1034
+ initialValue: true
1035
+ })
1036
+ );
891
1037
  }
892
- await sync({ buildId: void 0, origin: void 0, authToken: void 0 });
893
1038
  if (projectTemplate === void 0) {
894
- const { deployTarget } = await prompt({
895
- type: "select",
896
- name: "deployTarget",
897
- message: "Where would you like to deploy your project?",
898
- choices: PROJECT_TEMPALTES.map((template) => {
899
- return {
900
- title: titleCase(template),
901
- value: template
902
- };
1039
+ projectTemplate = exitIfCancelled(
1040
+ await select({
1041
+ message: "Where would you like to deploy your project?",
1042
+ options: PROJECT_TEMPALTES.map((template) => ({
1043
+ value: template,
1044
+ label: titleCase(template)
1045
+ }))
903
1046
  })
904
- });
905
- projectTemplate = deployTarget;
1047
+ );
906
1048
  }
1049
+ await sync({ buildId: void 0, origin: void 0, authToken: void 0 });
907
1050
  await build({
908
1051
  ...options,
909
1052
  ...projectTemplate && { template: [projectTemplate] }
910
1053
  });
911
1054
  if (shouldInstallDeps === true) {
912
- const spinner = ora3().start();
913
- spinner.text = "Installing dependencies";
1055
+ const install = spinner3();
1056
+ install.start("Installing dependencies");
914
1057
  await $`npm install`;
915
- spinner.succeed("Installed dependencies");
1058
+ install.stop("Installed dependencies");
916
1059
  }
917
- console.info(pc2.bold(pc2.green(`
918
- Your project was successfully synced \u{1F389}`)));
919
- console.info(
1060
+ log4.message();
1061
+ log4.message(pc2.green(pc2.bold(`Your project was successfully synced \u{1F389}`)));
1062
+ log4.message(
920
1063
  [
921
1064
  "Now you can:",
922
1065
  folderName && `Go to your project: ${pc2.dim(`cd ${folderName}`)}`,
@@ -949,7 +1092,7 @@ import makeCLI from "yargs";
949
1092
  // package.json
950
1093
  var package_default = {
951
1094
  name: "webstudio",
952
- version: "0.163.0",
1095
+ version: "0.167.0",
953
1096
  description: "Webstudio CLI",
954
1097
  author: "Webstudio <github@webstudio.is>",
955
1098
  homepage: "https://webstudio.is",
@@ -969,10 +1112,12 @@ var package_default = {
969
1112
  checks: "pnpm typecheck",
970
1113
  build: "rm -rf lib && esbuild src/cli.ts --outdir=lib --bundle --format=esm --packages=external",
971
1114
  "local-run": "tsx --no-warnings ./src/bin.ts",
972
- dev: "esbuild src/cli.ts --watch --bundle --format=esm --packages=external --outdir=./lib"
1115
+ dev: "esbuild src/cli.ts --watch --bundle --format=esm --packages=external --outdir=./lib",
1116
+ test: "NODE_OPTIONS=--experimental-vm-modules jest"
973
1117
  },
974
1118
  license: "AGPL-3.0-or-later",
975
1119
  dependencies: {
1120
+ "@clack/prompts": "^0.7.0",
976
1121
  "@webstudio-is/http-client": "workspace:*",
977
1122
  "@webstudio-is/image": "workspace:*",
978
1123
  "@webstudio-is/react-sdk": "workspace:*",
@@ -980,39 +1125,39 @@ var package_default = {
980
1125
  "@webstudio-is/sdk-components-react": "workspace:*",
981
1126
  "@webstudio-is/sdk-components-react-radix": "workspace:*",
982
1127
  "@webstudio-is/sdk-components-react-remix": "workspace:*",
1128
+ "change-case": "^5.0.2",
983
1129
  deepmerge: "^4.3.1",
984
1130
  "env-paths": "^3.0.0",
985
1131
  execa: "^7.2.0",
986
- ora: "^7.0.1",
1132
+ parse5: "7.1.2",
987
1133
  "p-limit": "^4.0.0",
988
- picocolors: "^1.0.0",
989
- prompts: "^2.4.2",
1134
+ picocolors: "^1.0.1",
990
1135
  "strip-indent": "^4.0.0",
991
1136
  "title-case": "^4.1.0",
992
1137
  yargs: "^17.7.2",
993
1138
  zod: "^3.22.4"
994
1139
  },
995
1140
  devDependencies: {
996
- "@netlify/remix-adapter": "^2.3.1",
997
- "@netlify/remix-edge-adapter": "3.2.2",
998
- "@remix-run/cloudflare": "^2.9.1",
999
- "@remix-run/cloudflare-pages": "^2.9.1",
1000
- "@remix-run/dev": "^2.9.1",
1001
- "@remix-run/node": "^2.9.1",
1002
- "@remix-run/react": "^2.9.1",
1003
- "@remix-run/server-runtime": "^2.9.1",
1141
+ "@jest/globals": "^29.7.0",
1142
+ "@netlify/remix-adapter": "^2.4.0",
1143
+ "@netlify/remix-edge-adapter": "3.3.0",
1144
+ "@remix-run/cloudflare": "^2.9.2",
1145
+ "@remix-run/cloudflare-pages": "^2.9.2",
1146
+ "@remix-run/dev": "^2.9.2",
1147
+ "@remix-run/node": "^2.9.2",
1148
+ "@remix-run/react": "^2.9.2",
1149
+ "@remix-run/server-runtime": "^2.9.2",
1004
1150
  "@types/node": "^20.12.7",
1005
- "@types/prompts": "^2.4.5",
1006
1151
  "@types/react": "^18.2.70",
1007
1152
  "@types/react-dom": "^18.2.25",
1008
1153
  "@types/yargs": "^17.0.32",
1009
1154
  "@webstudio-is/form-handlers": "workspace:*",
1155
+ "@webstudio-is/jest-config": "workspace:*",
1010
1156
  "@webstudio-is/tsconfig": "workspace:*",
1011
1157
  react: "18.3.0-canary-14898b6a9-20240318",
1012
1158
  "react-dom": "18.3.0-canary-14898b6a9-20240318",
1013
- tsx: "^4.7.2",
1014
1159
  typescript: "5.4.5",
1015
- vite: "^5.2.11",
1160
+ vite: "^5.2.12",
1016
1161
  wrangler: "^3.48.0"
1017
1162
  }
1018
1163
  };
@@ -1056,7 +1201,7 @@ var main = async () => {
1056
1201
  await cmd.parse();
1057
1202
  } catch (error) {
1058
1203
  console.error(error);
1059
- exit(1);
1204
+ exit4(1);
1060
1205
  }
1061
1206
  };
1062
1207
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webstudio",
3
- "version": "0.163.0",
3
+ "version": "0.167.0",
4
4
  "description": "Webstudio CLI",
5
5
  "author": "Webstudio <github@webstudio.is>",
6
6
  "homepage": "https://webstudio.is",
@@ -17,53 +17,55 @@
17
17
  ],
18
18
  "license": "AGPL-3.0-or-later",
19
19
  "dependencies": {
20
+ "@clack/prompts": "^0.7.0",
21
+ "change-case": "^5.0.2",
20
22
  "deepmerge": "^4.3.1",
21
23
  "env-paths": "^3.0.0",
22
24
  "execa": "^7.2.0",
23
- "ora": "^7.0.1",
25
+ "parse5": "7.1.2",
24
26
  "p-limit": "^4.0.0",
25
- "picocolors": "^1.0.0",
26
- "prompts": "^2.4.2",
27
+ "picocolors": "^1.0.1",
27
28
  "strip-indent": "^4.0.0",
28
29
  "title-case": "^4.1.0",
29
30
  "yargs": "^17.7.2",
30
31
  "zod": "^3.22.4",
31
- "@webstudio-is/http-client": "0.163.0",
32
- "@webstudio-is/react-sdk": "0.163.0",
33
- "@webstudio-is/image": "0.163.0",
34
- "@webstudio-is/sdk": "0.163.0",
35
- "@webstudio-is/sdk-components-react-radix": "0.163.0",
36
- "@webstudio-is/sdk-components-react": "0.163.0",
37
- "@webstudio-is/sdk-components-react-remix": "0.163.0"
32
+ "@webstudio-is/http-client": "0.167.0",
33
+ "@webstudio-is/image": "0.167.0",
34
+ "@webstudio-is/react-sdk": "0.167.0",
35
+ "@webstudio-is/sdk": "0.167.0",
36
+ "@webstudio-is/sdk-components-react-radix": "0.167.0",
37
+ "@webstudio-is/sdk-components-react": "0.167.0",
38
+ "@webstudio-is/sdk-components-react-remix": "0.167.0"
38
39
  },
39
40
  "devDependencies": {
40
- "@netlify/remix-adapter": "^2.3.1",
41
- "@netlify/remix-edge-adapter": "3.2.2",
42
- "@remix-run/cloudflare": "^2.9.1",
43
- "@remix-run/cloudflare-pages": "^2.9.1",
44
- "@remix-run/dev": "^2.9.1",
45
- "@remix-run/node": "^2.9.1",
46
- "@remix-run/react": "^2.9.1",
47
- "@remix-run/server-runtime": "^2.9.1",
41
+ "@jest/globals": "^29.7.0",
42
+ "@netlify/remix-adapter": "^2.4.0",
43
+ "@netlify/remix-edge-adapter": "3.3.0",
44
+ "@remix-run/cloudflare": "^2.9.2",
45
+ "@remix-run/cloudflare-pages": "^2.9.2",
46
+ "@remix-run/dev": "^2.9.2",
47
+ "@remix-run/node": "^2.9.2",
48
+ "@remix-run/react": "^2.9.2",
49
+ "@remix-run/server-runtime": "^2.9.2",
48
50
  "@types/node": "^20.12.7",
49
- "@types/prompts": "^2.4.5",
50
51
  "@types/react": "^18.2.70",
51
52
  "@types/react-dom": "^18.2.25",
52
53
  "@types/yargs": "^17.0.32",
53
54
  "react": "18.3.0-canary-14898b6a9-20240318",
54
55
  "react-dom": "18.3.0-canary-14898b6a9-20240318",
55
- "tsx": "^4.7.2",
56
56
  "typescript": "5.4.5",
57
- "vite": "^5.2.11",
57
+ "vite": "^5.2.12",
58
58
  "wrangler": "^3.48.0",
59
- "@webstudio-is/form-handlers": "0.163.0",
60
- "@webstudio-is/tsconfig": "1.0.7"
59
+ "@webstudio-is/form-handlers": "0.167.0",
60
+ "@webstudio-is/tsconfig": "1.0.7",
61
+ "@webstudio-is/jest-config": "1.0.7"
61
62
  },
62
63
  "scripts": {
63
64
  "typecheck": "tsc",
64
65
  "checks": "pnpm typecheck",
65
66
  "build": "rm -rf lib && esbuild src/cli.ts --outdir=lib --bundle --format=esm --packages=external",
66
67
  "local-run": "tsx --no-warnings ./src/bin.ts",
67
- "dev": "esbuild src/cli.ts --watch --bundle --format=esm --packages=external --outdir=./lib"
68
+ "dev": "esbuild src/cli.ts --watch --bundle --format=esm --packages=external --outdir=./lib",
69
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest"
68
70
  }
69
71
  }
@@ -12,9 +12,9 @@
12
12
  "build-cf-types": "wrangler types"
13
13
  },
14
14
  "dependencies": {
15
- "@remix-run/cloudflare": "2.9.1",
16
- "@remix-run/cloudflare-pages": "2.9.1",
17
- "isbot": "^4.1.0"
15
+ "@remix-run/cloudflare": "2.9.2",
16
+ "@remix-run/cloudflare-pages": "2.9.2",
17
+ "isbot": "^5.1.8"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@cloudflare/workers-types": "^4.20240405.0",
@@ -8,7 +8,7 @@
8
8
  "**/.client/**/*.tsx"
9
9
  ],
10
10
  "compilerOptions": {
11
- "lib": ["DOM", "DOM.Iterable", "ES2022"],
11
+ "lib": ["DOM", "DOM.Iterable", "ES2023"],
12
12
  "types": [
13
13
  "@remix-run/cloudflare",
14
14
  "vite/client",
@@ -1,13 +1,31 @@
1
- import { Links, Meta, Outlet } from "@remix-run/react";
1
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
2
+
3
+ import { Links, Meta, Outlet, useMatches } from "@remix-run/react";
4
+ // @todo think about how to make __generated__ typeable
5
+ // @ts-ignore
6
+ import { CustomCode } from "./__generated__/_index";
2
7
 
3
8
  const Root = () => {
9
+ // Get language from matches
10
+ const matches = useMatches();
11
+
12
+ const lastMatchWithLanguage = matches.findLast((match) => {
13
+ // @ts-ignore
14
+ const language = match?.data?.pageMeta?.language;
15
+ return language != null;
16
+ });
17
+
18
+ // @ts-ignore
19
+ const lang = lastMatchWithLanguage?.data?.pageMeta?.language ?? "en";
20
+
4
21
  return (
5
- <html lang="en">
22
+ <html lang={lang}>
6
23
  <head>
7
24
  <meta charSet="utf-8" />
8
25
  <meta name="viewport" content="width=device-width,initial-scale=1" />
9
26
  <Meta />
10
27
  <Links />
28
+ <CustomCode />
11
29
  </head>
12
30
  <Outlet />
13
31
  </html>
@@ -81,7 +81,6 @@ export const loader = async (arg: LoaderFunctionArgs) => {
81
81
  status: pageMeta.status,
82
82
  headers: {
83
83
  "Cache-Control": "public, max-age=600",
84
- "x-ws-language": pageMeta.language ?? "en",
85
84
  },
86
85
  }
87
86
  );
@@ -90,7 +89,6 @@ export const loader = async (arg: LoaderFunctionArgs) => {
90
89
  export const headers: HeadersFunction = ({ loaderHeaders }) => {
91
90
  return {
92
91
  "Cache-Control": "public, max-age=0, must-revalidate",
93
- "x-ws-language": loaderHeaders.get("x-ws-language") ?? "",
94
92
  };
95
93
  };
96
94
 
@@ -8,26 +8,26 @@
8
8
  "typecheck": "tsc"
9
9
  },
10
10
  "dependencies": {
11
- "@remix-run/node": "2.9.1",
12
- "@remix-run/react": "2.9.1",
13
- "@remix-run/server-runtime": "2.9.1",
14
- "@webstudio-is/react-sdk": "0.163.0",
15
- "@webstudio-is/sdk-components-react-radix": "0.163.0",
16
- "@webstudio-is/sdk-components-react-remix": "0.163.0",
17
- "@webstudio-is/sdk-components-react": "0.163.0",
18
- "@webstudio-is/form-handlers": "0.163.0",
19
- "@webstudio-is/image": "0.163.0",
20
- "@webstudio-is/sdk": "0.163.0",
21
- "isbot": "^3.6.8",
11
+ "@remix-run/node": "2.9.2",
12
+ "@remix-run/react": "2.9.2",
13
+ "@remix-run/server-runtime": "2.9.2",
14
+ "@webstudio-is/react-sdk": "0.167.0",
15
+ "@webstudio-is/sdk-components-react-radix": "0.167.0",
16
+ "@webstudio-is/sdk-components-react-remix": "0.167.0",
17
+ "@webstudio-is/sdk-components-react": "0.167.0",
18
+ "@webstudio-is/form-handlers": "0.167.0",
19
+ "@webstudio-is/image": "0.167.0",
20
+ "@webstudio-is/sdk": "0.167.0",
21
+ "isbot": "^5.1.8",
22
22
  "react": "18.3.0-canary-14898b6a9-20240318",
23
23
  "react-dom": "18.3.0-canary-14898b6a9-20240318"
24
24
  },
25
25
  "devDependencies": {
26
- "@remix-run/dev": "2.9.1",
26
+ "@remix-run/dev": "2.9.2",
27
27
  "@types/react": "^18.2.70",
28
28
  "@types/react-dom": "^18.2.25",
29
29
  "typescript": "5.4.5",
30
- "vite": "^5.2.11"
30
+ "vite": "^5.2.12"
31
31
  },
32
32
  "engines": {
33
33
  "node": ">=20.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "include": ["**/*.ts", "**/*.tsx", "**/*.mjs"],
3
3
  "compilerOptions": {
4
- "lib": ["DOM", "DOM.Iterable", "ES2022"],
4
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
5
5
  "types": ["@remix-run/node", "vite/client"],
6
6
  "isolatedModules": true,
7
7
  "esModuleInterop": true,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "include": ["**/*.ts", "**/*.tsx", "**/*.mjs"],
3
3
  "compilerOptions": {
4
- "lib": ["DOM", "DOM.Iterable", "ES2022"],
4
+ "lib": ["DOM", "DOM.Iterable", "ES2023"],
5
5
  "types": ["@remix-run/node", "vite/client"],
6
6
  "isolatedModules": true,
7
7
  "esModuleInterop": true,
@@ -3,7 +3,7 @@
3
3
  "start": "netlify serve"
4
4
  },
5
5
  "dependencies": {
6
- "@netlify/edge-functions": "^2.6.0",
7
- "@netlify/remix-edge-adapter": "^3.2.2"
6
+ "@netlify/edge-functions": "^2.8.1",
7
+ "@netlify/remix-edge-adapter": "^3.3.0"
8
8
  }
9
9
  }
@@ -3,7 +3,7 @@
3
3
  "start": "netlify serve"
4
4
  },
5
5
  "dependencies": {
6
- "@netlify/functions": "^2.6.0",
7
- "@netlify/remix-adapter": "^2.3.1"
6
+ "@netlify/functions": "^2.7.0",
7
+ "@netlify/remix-adapter": "^2.4.0"
8
8
  }
9
9
  }
@@ -8,7 +8,7 @@
8
8
  "**/.client/**/*.tsx"
9
9
  ],
10
10
  "compilerOptions": {
11
- "lib": ["DOM", "DOM.Iterable", "ES2022"],
11
+ "lib": ["DOM", "DOM.Iterable", "ES2023"],
12
12
  "types": [
13
13
  "@remix-run/cloudflare",
14
14
  "vite/client",