@gallop.software/studio 2.3.135 → 2.3.137

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.
@@ -11,7 +11,7 @@
11
11
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
12
12
  }
13
13
  </style>
14
- <script type="module" crossorigin src="/assets/index-CyMBtvIS.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-3VggyaFt.js"></script>
15
15
  <link rel="stylesheet" crossorigin href="/assets/index-DfPQBmNf.css">
16
16
  </head>
17
17
  <body>
@@ -3967,8 +3967,8 @@ import sharp4 from "sharp";
3967
3967
  function parseImageUrl(url) {
3968
3968
  const parsed = new URL(url);
3969
3969
  const base = `${parsed.protocol}//${parsed.host}`;
3970
- const path11 = parsed.pathname;
3971
- return { base, path: path11 };
3970
+ const path12 = parsed.pathname;
3971
+ return { base, path: path12 };
3972
3972
  }
3973
3973
  async function processRemoteImage(url) {
3974
3974
  const response = await fetch(url);
@@ -4029,10 +4029,10 @@ async function handleImportUrls(request) {
4029
4029
  const url = urls[i].trim();
4030
4030
  if (!url) continue;
4031
4031
  try {
4032
- const { base, path: path11 } = parseImageUrl(url);
4033
- const existingEntry = getMetaEntry(meta, path11);
4032
+ const { base, path: path12 } = parseImageUrl(url);
4033
+ const existingEntry = getMetaEntry(meta, path12);
4034
4034
  if (existingEntry) {
4035
- skipped.push(path11);
4035
+ skipped.push(path12);
4036
4036
  sendEvent({
4037
4037
  type: "progress",
4038
4038
  current: i + 1,
@@ -4045,13 +4045,13 @@ async function handleImportUrls(request) {
4045
4045
  }
4046
4046
  const cdnIndex = getOrAddCdnIndex(meta, base);
4047
4047
  const imageData = await processRemoteImage(url);
4048
- setMetaEntry(meta, path11, {
4048
+ setMetaEntry(meta, path12, {
4049
4049
  o: imageData.o,
4050
4050
  b: imageData.b,
4051
4051
  c: cdnIndex
4052
4052
  });
4053
4053
  await saveMeta(meta);
4054
- added.push(path11);
4054
+ added.push(path12);
4055
4055
  sendEvent({
4056
4056
  type: "progress",
4057
4057
  current: i + 1,
@@ -4572,6 +4572,189 @@ async function handleEditImage(request) {
4572
4572
  }
4573
4573
  }
4574
4574
 
4575
+ // src/handlers/fonts.ts
4576
+ import { promises as fs12 } from "fs";
4577
+ import path11 from "path";
4578
+ async function handleFontsList(request) {
4579
+ const searchParams = new URL(request.url).searchParams;
4580
+ const requestedPath = searchParams.get("path") || "_fonts";
4581
+ try {
4582
+ const items = [];
4583
+ let fsPath;
4584
+ let allowedPaths = ["_fonts", "src/fonts", "src"];
4585
+ const isAllowed = allowedPaths.some(
4586
+ (allowed) => requestedPath === allowed || requestedPath.startsWith(allowed + "/")
4587
+ );
4588
+ if (!isAllowed) {
4589
+ return jsonResponse({ items: [], error: "Path not allowed" }, { status: 400 });
4590
+ }
4591
+ if (requestedPath === "src") {
4592
+ const fontsFolderPath = getWorkspacePath("src", "fonts");
4593
+ try {
4594
+ const stat = await fs12.stat(fontsFolderPath);
4595
+ if (stat.isDirectory()) {
4596
+ items.push({
4597
+ name: "fonts",
4598
+ path: "src/fonts",
4599
+ type: "folder"
4600
+ });
4601
+ }
4602
+ } catch {
4603
+ }
4604
+ return jsonResponse({ items, canCreate: true });
4605
+ }
4606
+ fsPath = getWorkspacePath(requestedPath);
4607
+ try {
4608
+ const stat = await fs12.stat(fsPath);
4609
+ if (!stat.isDirectory()) {
4610
+ return jsonResponse({ items: [] });
4611
+ }
4612
+ } catch {
4613
+ return jsonResponse({ items: [], canCreate: true });
4614
+ }
4615
+ const entries = await fs12.readdir(fsPath, { withFileTypes: true });
4616
+ for (const entry of entries) {
4617
+ const itemPath = `${requestedPath}/${entry.name}`;
4618
+ if (entry.isDirectory()) {
4619
+ let fileCount = 0;
4620
+ try {
4621
+ const subEntries = await fs12.readdir(path11.join(fsPath, entry.name));
4622
+ fileCount = subEntries.filter(
4623
+ (f) => f.match(/\.(ttf|woff2?|otf|ts|tsx|js)$/i)
4624
+ ).length;
4625
+ } catch {
4626
+ }
4627
+ items.push({
4628
+ name: entry.name,
4629
+ path: itemPath,
4630
+ type: "folder",
4631
+ fileCount
4632
+ });
4633
+ } else {
4634
+ const ext = path11.extname(entry.name).toLowerCase();
4635
+ const allowedExts = [".ttf", ".woff", ".woff2", ".otf", ".ts", ".tsx", ".js"];
4636
+ if (allowedExts.includes(ext)) {
4637
+ let size = 0;
4638
+ try {
4639
+ const fileStat = await fs12.stat(path11.join(fsPath, entry.name));
4640
+ size = fileStat.size;
4641
+ } catch {
4642
+ }
4643
+ items.push({
4644
+ name: entry.name,
4645
+ path: itemPath,
4646
+ type: "file",
4647
+ size
4648
+ });
4649
+ }
4650
+ }
4651
+ }
4652
+ items.sort((a, b) => {
4653
+ if (a.type === "folder" && b.type !== "folder") return -1;
4654
+ if (a.type !== "folder" && b.type === "folder") return 1;
4655
+ return a.name.localeCompare(b.name);
4656
+ });
4657
+ return jsonResponse({ items });
4658
+ } catch (error) {
4659
+ console.error("Error listing fonts:", error);
4660
+ return jsonResponse({ error: "Failed to list fonts" }, { status: 500 });
4661
+ }
4662
+ }
4663
+ async function handleFontsUpload(request) {
4664
+ try {
4665
+ const formData = await request.formData();
4666
+ const file = formData.get("file");
4667
+ const targetPath = formData.get("path") || "_fonts";
4668
+ if (!file) {
4669
+ return jsonResponse({ error: "No file provided" }, { status: 400 });
4670
+ }
4671
+ if (!file.name.toLowerCase().endsWith(".ttf")) {
4672
+ return jsonResponse({ error: "Only TTF files are supported" }, { status: 400 });
4673
+ }
4674
+ if (!targetPath.startsWith("_fonts")) {
4675
+ return jsonResponse({ error: "Can only upload to _fonts/" }, { status: 400 });
4676
+ }
4677
+ const bytes = await file.arrayBuffer();
4678
+ const buffer = Buffer.from(bytes);
4679
+ const uploadDir = getWorkspacePath(targetPath);
4680
+ await fs12.mkdir(uploadDir, { recursive: true });
4681
+ const filePath = path11.join(uploadDir, file.name.toLowerCase());
4682
+ await fs12.writeFile(filePath, buffer);
4683
+ return jsonResponse({
4684
+ success: true,
4685
+ path: `${targetPath}/${file.name.toLowerCase()}`
4686
+ });
4687
+ } catch (error) {
4688
+ console.error("Error uploading font:", error);
4689
+ return jsonResponse({ error: "Failed to upload font" }, { status: 500 });
4690
+ }
4691
+ }
4692
+ async function handleFontsCreateFolder(request) {
4693
+ try {
4694
+ const { path: targetPath, name } = await request.json();
4695
+ if (!targetPath || !name) {
4696
+ return jsonResponse({ error: "Path and name are required" }, { status: 400 });
4697
+ }
4698
+ const allowedPaths = ["_fonts", "src/fonts", "src"];
4699
+ const isAllowed = allowedPaths.some(
4700
+ (allowed) => targetPath === allowed || targetPath.startsWith(allowed + "/")
4701
+ );
4702
+ if (!isAllowed) {
4703
+ return jsonResponse({ error: "Path not allowed" }, { status: 400 });
4704
+ }
4705
+ const folderPath = getWorkspacePath(targetPath, name.toLowerCase());
4706
+ await fs12.mkdir(folderPath, { recursive: true });
4707
+ return jsonResponse({
4708
+ success: true,
4709
+ path: `${targetPath}/${name.toLowerCase()}`
4710
+ });
4711
+ } catch (error) {
4712
+ console.error("Error creating folder:", error);
4713
+ return jsonResponse({ error: "Failed to create folder" }, { status: 500 });
4714
+ }
4715
+ }
4716
+ async function handleFontsDelete(request) {
4717
+ try {
4718
+ const { paths } = await request.json();
4719
+ if (!paths || !Array.isArray(paths) || paths.length === 0) {
4720
+ return jsonResponse({ error: "Paths are required" }, { status: 400 });
4721
+ }
4722
+ const allowedPaths = ["_fonts", "src/fonts"];
4723
+ for (const p of paths) {
4724
+ const isAllowed = allowedPaths.some(
4725
+ (allowed) => p.startsWith(allowed + "/")
4726
+ );
4727
+ if (!isAllowed) {
4728
+ return jsonResponse({ error: `Path not allowed: ${p}` }, { status: 400 });
4729
+ }
4730
+ }
4731
+ const deleted = [];
4732
+ const errors = [];
4733
+ for (const p of paths) {
4734
+ try {
4735
+ const fullPath = getWorkspacePath(p);
4736
+ const stat = await fs12.stat(fullPath);
4737
+ if (stat.isDirectory()) {
4738
+ await fs12.rm(fullPath, { recursive: true });
4739
+ } else {
4740
+ await fs12.unlink(fullPath);
4741
+ }
4742
+ deleted.push(p);
4743
+ } catch (err) {
4744
+ errors.push(`Failed to delete ${p}: ${err instanceof Error ? err.message : "Unknown error"}`);
4745
+ }
4746
+ }
4747
+ return jsonResponse({
4748
+ success: true,
4749
+ deleted,
4750
+ errors: errors.length > 0 ? errors : void 0
4751
+ });
4752
+ } catch (error) {
4753
+ console.error("Error deleting:", error);
4754
+ return jsonResponse({ error: "Failed to delete" }, { status: 500 });
4755
+ }
4756
+ }
4757
+
4575
4758
  // src/server/index.ts
4576
4759
  var __filename = fileURLToPath(import.meta.url);
4577
4760
  var __dirname = dirname(__filename);
@@ -4617,15 +4800,16 @@ async function startServer(options) {
4617
4800
  if (!process.env.STUDIO_DEV_SITE_URL && process.env.NEXT_PUBLIC_PRODUCTION_URL) {
4618
4801
  process.env.STUDIO_DEV_SITE_URL = process.env.NEXT_PUBLIC_PRODUCTION_URL;
4619
4802
  }
4803
+ const rawBodyPaths = ["/api/studio/upload", "/api/studio/fonts/upload"];
4620
4804
  app.use((req, res, next) => {
4621
- if (req.path === "/api/studio/upload") {
4805
+ if (rawBodyPaths.includes(req.path)) {
4622
4806
  next();
4623
4807
  } else {
4624
4808
  express.json({ limit: "50mb" })(req, res, next);
4625
4809
  }
4626
4810
  });
4627
4811
  app.use((req, res, next) => {
4628
- if (req.path === "/api/studio/upload") {
4812
+ if (rawBodyPaths.includes(req.path)) {
4629
4813
  next();
4630
4814
  } else {
4631
4815
  express.urlencoded({ extended: true, limit: "50mb" })(req, res, next);
@@ -4645,6 +4829,10 @@ async function startServer(options) {
4645
4829
  "/api/studio/featured-image-options",
4646
4830
  wrapHandler(handleGetFeaturedImageOptions)
4647
4831
  );
4832
+ app.get("/api/studio/fonts/list", wrapHandler(handleFontsList));
4833
+ app.post("/api/studio/fonts/upload", wrapRawHandler(handleFontsUpload));
4834
+ app.post("/api/studio/fonts/create-folder", wrapHandler(handleFontsCreateFolder));
4835
+ app.post("/api/studio/fonts/delete", wrapHandler(handleFontsDelete));
4648
4836
  app.post("/api/studio/upload", wrapRawHandler(handleUpload));
4649
4837
  app.post("/api/studio/create-folder", wrapHandler(handleCreateFolder));
4650
4838
  app.post("/api/studio/rename", wrapHandler(handleRename));