@gallop.software/studio 2.3.151 → 2.3.152

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-DjWOd1ah.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-DWAQ5-8C.js"></script>
15
15
  <link rel="stylesheet" crossorigin href="/assets/index-DfPQBmNf.css">
16
16
  </head>
17
17
  <body>
@@ -5,7 +5,7 @@ import express from "express";
5
5
  import { resolve, join } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { dirname } from "path";
8
- import { existsSync, readFileSync } from "fs";
8
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
9
9
  import { config as loadEnv } from "dotenv";
10
10
  import { createServer } from "net";
11
11
 
@@ -4574,7 +4574,55 @@ async function handleEditImage(request) {
4574
4574
 
4575
4575
  // src/handlers/fonts.ts
4576
4576
  import { promises as fs12 } from "fs";
4577
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
4577
4578
  import path11 from "path";
4579
+ var weightMap = {
4580
+ thin: "100",
4581
+ extralight: "200",
4582
+ light: "300",
4583
+ regular: "400",
4584
+ medium: "500",
4585
+ semibold: "600",
4586
+ bold: "700",
4587
+ extrabold: "800",
4588
+ black: "900"
4589
+ };
4590
+ function parseFontMetadata(filename) {
4591
+ const name = filename.toLowerCase();
4592
+ let weight = "400";
4593
+ let style = "normal";
4594
+ const isVariable = name.includes("variable");
4595
+ if (isVariable) {
4596
+ weight = "100 900";
4597
+ } else {
4598
+ if (name.includes("extralight")) weight = weightMap.extralight;
4599
+ else if (name.includes("extrabold")) weight = weightMap.extrabold;
4600
+ else if (name.includes("semibold")) weight = weightMap.semibold;
4601
+ else if (name.includes("thin")) weight = weightMap.thin;
4602
+ else if (name.includes("light")) weight = weightMap.light;
4603
+ else if (name.includes("black")) weight = weightMap.black;
4604
+ else if (name.includes("bold")) weight = weightMap.bold;
4605
+ else if (name.includes("medium")) weight = weightMap.medium;
4606
+ else if (name.includes("regular")) weight = weightMap.regular;
4607
+ }
4608
+ if (name.includes("italic")) style = "italic";
4609
+ return { weight, style, isVariable };
4610
+ }
4611
+ function getWeightName(weight) {
4612
+ if (weight === "100 900") return "Variable";
4613
+ const names = {
4614
+ "100": "Thin",
4615
+ "200": "ExtraLight",
4616
+ "300": "Light",
4617
+ "400": "Regular",
4618
+ "500": "Medium",
4619
+ "600": "SemiBold",
4620
+ "700": "Bold",
4621
+ "800": "ExtraBold",
4622
+ "900": "Black"
4623
+ };
4624
+ return names[weight] || weight;
4625
+ }
4578
4626
  async function handleFontsList(request) {
4579
4627
  const searchParams = new URL(request.url).searchParams;
4580
4628
  const requestedPath = searchParams.get("path") || "_fonts";
@@ -4787,12 +4835,242 @@ async function handleFontsRename(request) {
4787
4835
  return jsonResponse({ error: "Failed to rename" }, { status: 500 });
4788
4836
  }
4789
4837
  }
4838
+ async function handleFontsScan(request) {
4839
+ try {
4840
+ const { folder } = await request.json();
4841
+ if (!folder || !folder.startsWith("_fonts/")) {
4842
+ return jsonResponse({ error: "Invalid folder path" }, { status: 400 });
4843
+ }
4844
+ const folderPath = getWorkspacePath(folder);
4845
+ const folderName = path11.basename(folder);
4846
+ try {
4847
+ const stat = await fs12.stat(folderPath);
4848
+ if (!stat.isDirectory()) {
4849
+ return jsonResponse({ error: "Path is not a folder" }, { status: 400 });
4850
+ }
4851
+ } catch {
4852
+ return jsonResponse({ error: "Folder not found" }, { status: 404 });
4853
+ }
4854
+ const entries = await fs12.readdir(folderPath);
4855
+ const ttfFiles = [];
4856
+ const woff2Files = [];
4857
+ const detectedFonts = [];
4858
+ for (const entry of entries) {
4859
+ const ext = path11.extname(entry).toLowerCase();
4860
+ if (ext === ".ttf") {
4861
+ ttfFiles.push(entry);
4862
+ } else if (ext === ".woff2") {
4863
+ woff2Files.push(entry);
4864
+ const baseName = path11.basename(entry, ".woff2");
4865
+ const { weight, style } = parseFontMetadata(baseName);
4866
+ detectedFonts.push({
4867
+ file: entry,
4868
+ weight,
4869
+ weightName: getWeightName(weight),
4870
+ style
4871
+ });
4872
+ }
4873
+ }
4874
+ const needsGeneration = ttfFiles.length > 0 && woff2Files.length === 0;
4875
+ const srcFontsPath = getWorkspacePath("src/fonts");
4876
+ const assignments = [];
4877
+ try {
4878
+ const srcEntries = await fs12.readdir(srcFontsPath);
4879
+ for (const entry of srcEntries) {
4880
+ if (entry.endsWith(".ts")) {
4881
+ const filePath = path11.join(srcFontsPath, entry);
4882
+ const content = await fs12.readFile(filePath, "utf8");
4883
+ if (content.includes(`/_fonts/${folderName}/`)) {
4884
+ assignments.push(path11.basename(entry, ".ts"));
4885
+ }
4886
+ }
4887
+ }
4888
+ } catch {
4889
+ }
4890
+ return jsonResponse({
4891
+ folder,
4892
+ folderName,
4893
+ ttfFiles,
4894
+ woff2Files,
4895
+ detectedFonts,
4896
+ needsGeneration,
4897
+ assignments
4898
+ });
4899
+ } catch (error) {
4900
+ console.error("Error scanning fonts:", error);
4901
+ return jsonResponse({ error: "Failed to scan fonts" }, { status: 500 });
4902
+ }
4903
+ }
4904
+ async function handleFontsListAssignments() {
4905
+ try {
4906
+ const srcFontsPath = getWorkspacePath("src/fonts");
4907
+ const assignments = [];
4908
+ try {
4909
+ const entries = await fs12.readdir(srcFontsPath);
4910
+ for (const entry of entries) {
4911
+ if (entry.endsWith(".ts")) {
4912
+ const name = path11.basename(entry, ".ts");
4913
+ const filePath = path11.join(srcFontsPath, entry);
4914
+ const content = await fs12.readFile(filePath, "utf8");
4915
+ const match = content.match(/\/_fonts\/([^/]+)\//);
4916
+ const folder = match ? match[1] : "unknown";
4917
+ assignments.push({ name, folder });
4918
+ }
4919
+ }
4920
+ } catch {
4921
+ }
4922
+ return jsonResponse({ assignments });
4923
+ } catch (error) {
4924
+ console.error("Error listing assignments:", error);
4925
+ return jsonResponse({ error: "Failed to list assignments" }, { status: 500 });
4926
+ }
4927
+ }
4928
+ async function handleFontsDeleteAssignment(request) {
4929
+ try {
4930
+ const { name } = await request.json();
4931
+ if (!name || typeof name !== "string") {
4932
+ return jsonResponse({ error: "Assignment name is required" }, { status: 400 });
4933
+ }
4934
+ if (name.includes("/") || name.includes("\\") || name.includes("..")) {
4935
+ return jsonResponse({ error: "Invalid assignment name" }, { status: 400 });
4936
+ }
4937
+ const filePath = getWorkspacePath("src/fonts", `${name}.ts`);
4938
+ try {
4939
+ await fs12.unlink(filePath);
4940
+ return jsonResponse({ success: true });
4941
+ } catch {
4942
+ return jsonResponse({ error: "Assignment not found" }, { status: 404 });
4943
+ }
4944
+ } catch (error) {
4945
+ console.error("Error deleting assignment:", error);
4946
+ return jsonResponse({ error: "Failed to delete assignment" }, { status: 500 });
4947
+ }
4948
+ }
4949
+ async function handleFontsAssign(request) {
4950
+ try {
4951
+ const { folder, assignments } = await request.json();
4952
+ if (!folder || !folder.startsWith("_fonts/")) {
4953
+ return jsonResponse({ error: "Invalid folder path" }, { status: 400 });
4954
+ }
4955
+ if (!assignments || !Array.isArray(assignments) || assignments.length === 0) {
4956
+ return jsonResponse({ error: "At least one assignment is required" }, { status: 400 });
4957
+ }
4958
+ for (const name of assignments) {
4959
+ if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(name)) {
4960
+ return jsonResponse({ error: `Invalid assignment name: ${name}` }, { status: 400 });
4961
+ }
4962
+ }
4963
+ const folderPath = getWorkspacePath(folder);
4964
+ const folderName = path11.basename(folder);
4965
+ const encoder = new TextEncoder();
4966
+ const stream = new ReadableStream({
4967
+ async start(controller) {
4968
+ const send = (data) => {
4969
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
4970
+
4971
+ `));
4972
+ };
4973
+ try {
4974
+ const entries = await fs12.readdir(folderPath);
4975
+ const ttfFiles = entries.filter((f) => f.toLowerCase().endsWith(".ttf"));
4976
+ let woff2Files = entries.filter((f) => f.toLowerCase().endsWith(".woff2"));
4977
+ if (ttfFiles.length === 0 && woff2Files.length === 0) {
4978
+ send({ status: "error", message: "No font files found in folder" });
4979
+ controller.close();
4980
+ return;
4981
+ }
4982
+ if (woff2Files.length === 0 && ttfFiles.length > 0) {
4983
+ send({ status: "progress", message: "Generating woff2 files...", current: 0, total: ttfFiles.length });
4984
+ const ttf2woff2Module = await import("ttf2woff2");
4985
+ const ttf2woff2 = ttf2woff2Module.default;
4986
+ for (let i = 0; i < ttfFiles.length; i++) {
4987
+ const ttfFile = ttfFiles[i];
4988
+ const baseName = path11.basename(ttfFile, ".ttf");
4989
+ const woff2Name = baseName + ".woff2";
4990
+ send({ status: "progress", message: `Compressing ${ttfFile}...`, current: i + 1, total: ttfFiles.length, currentFile: ttfFile });
4991
+ try {
4992
+ const ttfPath = path11.join(folderPath, ttfFile);
4993
+ const input = readFileSync(ttfPath);
4994
+ const woff2Data = ttf2woff2(input);
4995
+ writeFileSync(path11.join(folderPath, woff2Name), woff2Data);
4996
+ woff2Files.push(woff2Name);
4997
+ } catch (err) {
4998
+ send({ status: "progress", message: `Failed to compress ${ttfFile}`, error: String(err) });
4999
+ }
5000
+ }
5001
+ }
5002
+ if (woff2Files.length === 0) {
5003
+ send({ status: "error", message: "No woff2 files available" });
5004
+ controller.close();
5005
+ return;
5006
+ }
5007
+ const fontMap = woff2Files.map((file) => {
5008
+ const baseName = path11.basename(file, ".woff2");
5009
+ const { weight, style } = parseFontMetadata(baseName);
5010
+ return {
5011
+ path: `${folderName}/${file}`,
5012
+ weight,
5013
+ style
5014
+ };
5015
+ });
5016
+ const srcFontsPath = getWorkspacePath("src/fonts");
5017
+ if (!existsSync(srcFontsPath)) {
5018
+ mkdirSync(srcFontsPath, { recursive: true });
5019
+ }
5020
+ const created = [];
5021
+ const errors = [];
5022
+ for (let i = 0; i < assignments.length; i++) {
5023
+ const assignmentName = assignments[i];
5024
+ send({ status: "progress", message: `Writing ${assignmentName}.ts...`, current: i + 1, total: assignments.length });
5025
+ try {
5026
+ const fileName = `${assignmentName}.ts`;
5027
+ const filePath = path11.join(srcFontsPath, fileName);
5028
+ const variableName = `${assignmentName}Font`;
5029
+ const srcArray = fontMap.map((font) => ` { path: '../../_fonts/${font.path}', weight: '${font.weight}', style: '${font.style}' },`).join("\n");
5030
+ const template = `import localFont from 'next/font/local'
5031
+
5032
+ export const ${variableName} = localFont({
5033
+ src: [
5034
+ ${srcArray}
5035
+ ],
5036
+ })
5037
+ `;
5038
+ writeFileSync(filePath, template, "utf8");
5039
+ created.push(assignmentName);
5040
+ } catch (err) {
5041
+ errors.push(`Failed to write ${assignmentName}.ts: ${err}`);
5042
+ }
5043
+ }
5044
+ send({
5045
+ status: "complete",
5046
+ message: `Created ${created.length} font assignment${created.length !== 1 ? "s" : ""}`,
5047
+ created,
5048
+ errors: errors.length > 0 ? errors : void 0
5049
+ });
5050
+ } catch (err) {
5051
+ send({ status: "error", message: String(err) });
5052
+ }
5053
+ controller.close();
5054
+ }
5055
+ });
5056
+ return new Response(stream, {
5057
+ headers: {
5058
+ "Content-Type": "text/event-stream",
5059
+ "Cache-Control": "no-cache",
5060
+ "Connection": "keep-alive"
5061
+ }
5062
+ });
5063
+ } catch (error) {
5064
+ console.error("Error assigning fonts:", error);
5065
+ return jsonResponse({ error: "Failed to assign fonts" }, { status: 500 });
5066
+ }
5067
+ }
4790
5068
 
4791
5069
  // src/server/index.ts
4792
5070
  var __filename = fileURLToPath(import.meta.url);
4793
5071
  var __dirname = dirname(__filename);
4794
5072
  var packageJsonPath = resolve(__dirname, "../../package.json");
4795
- var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
5073
+ var packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
4796
5074
  var version = packageJson.version;
4797
5075
  function isPortAvailable(port) {
4798
5076
  return new Promise((resolve2) => {
@@ -4827,7 +5105,7 @@ async function startServer(options) {
4827
5105
  const app = express();
4828
5106
  process.env.STUDIO_WORKSPACE = workspace;
4829
5107
  const envLocalPath = join(workspace, ".env.local");
4830
- if (existsSync(envLocalPath)) {
5108
+ if (existsSync2(envLocalPath)) {
4831
5109
  loadEnv({ path: envLocalPath, quiet: true });
4832
5110
  }
4833
5111
  if (!process.env.STUDIO_DEV_SITE_URL && process.env.NEXT_PUBLIC_PRODUCTION_URL) {
@@ -4867,6 +5145,10 @@ async function startServer(options) {
4867
5145
  app.post("/api/studio/fonts/create-folder", wrapHandler(handleFontsCreateFolder));
4868
5146
  app.post("/api/studio/fonts/delete", wrapHandler(handleFontsDelete));
4869
5147
  app.post("/api/studio/fonts/rename", wrapHandler(handleFontsRename));
5148
+ app.post("/api/studio/fonts/scan", wrapHandler(handleFontsScan));
5149
+ app.get("/api/studio/fonts/assignments", wrapHandler(handleFontsListAssignments));
5150
+ app.post("/api/studio/fonts/assign-stream", wrapHandler(handleFontsAssign, true));
5151
+ app.post("/api/studio/fonts/delete-assignment", wrapHandler(handleFontsDeleteAssignment));
4870
5152
  app.post("/api/studio/upload", wrapRawHandler(handleUpload));
4871
5153
  app.post("/api/studio/create-folder", wrapHandler(handleCreateFolder));
4872
5154
  app.post("/api/studio/rename", wrapHandler(handleRename));
@@ -4914,8 +5196,8 @@ async function startServer(options) {
4914
5196
  const clientDir = resolve(__dirname, "../client");
4915
5197
  app.get("/", (req, res) => {
4916
5198
  const htmlPath = join(clientDir, "index.html");
4917
- if (existsSync(htmlPath)) {
4918
- let html = readFileSync(htmlPath, "utf-8");
5199
+ if (existsSync2(htmlPath)) {
5200
+ let html = readFileSync2(htmlPath, "utf-8");
4919
5201
  const siteUrl = process.env.STUDIO_DEV_SITE_URL || "";
4920
5202
  const script = `<script>
4921
5203
  window.__STUDIO_WORKSPACE__ = ${JSON.stringify(workspace)};