@hyperframes/studio-server 0.8.113 → 0.8.114

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/dist/index.js CHANGED
@@ -165,7 +165,8 @@ import {
165
165
  statSync as statSync3,
166
166
  fstatSync,
167
167
  renameSync as renameSync2,
168
- readdirSync as readdirSync2
168
+ readdirSync as readdirSync2,
169
+ realpathSync
169
170
  } from "fs";
170
171
  import { resolve as resolve2, dirname as dirname3, join as join5 } from "path";
171
172
 
@@ -1179,18 +1180,83 @@ function readableText(file) {
1179
1180
  throw error;
1180
1181
  }
1181
1182
  }
1182
- function updateReferences(projectDir, oldPath, newPath) {
1183
+ var escapeRegExp = (text2) => text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1184
+ var SEPARATOR = String.raw`\\{0,2}[\\/]`;
1185
+ var REFERENCE_START = String.raw`(?<![\w./\\+-])`;
1186
+ var FILE_END = String.raw`(?![\w-]|\.\w)`;
1187
+ function referencePattern(oldPath, isDirectory) {
1188
+ const name = oldPath.split("/").map(escapeRegExp).join(SEPARATOR);
1189
+ const end = isDirectory ? `(?=${SEPARATOR})` : FILE_END;
1190
+ return new RegExp(
1191
+ String.raw`${REFERENCE_START}(?<lead>(?:\.{1,2}${SEPARATOR}|${SEPARATOR}){0,4})${name}${end}`,
1192
+ "g"
1193
+ );
1194
+ }
1195
+ function referenceRewriter(oldPath, newPath, isDirectory, existing = []) {
1196
+ const pattern = referencePattern(oldPath, isDirectory);
1197
+ const around = /* @__PURE__ */ new Map();
1198
+ const afterLengths = /* @__PURE__ */ new Set();
1199
+ let window = 0;
1200
+ for (const path of existing) {
1201
+ if (path.length <= oldPath.length) continue;
1202
+ for (const start of occurrences(path, oldPath)) {
1203
+ const [before, after] = [path.slice(0, start), path.slice(start + oldPath.length)];
1204
+ const befores = around.get(after) ?? around.set(after, /* @__PURE__ */ new Map()).get(after);
1205
+ (befores.get(before.length) ?? befores.set(before.length, /* @__PURE__ */ new Set()).get(before.length)).add(
1206
+ before
1207
+ );
1208
+ afterLengths.add(after.length);
1209
+ window = Math.max(window, path.length * 3);
1210
+ }
1211
+ }
1212
+ const normalized = (text2) => text2.replace(/\\{0,2}[\\/]/g, "/");
1213
+ return (text2) => text2.replace(pattern, (...args) => {
1214
+ const [match, offset] = [args[0], args.at(-3)];
1215
+ const { lead } = args.at(-1);
1216
+ const at = offset + lead.length;
1217
+ const head = normalized(text2.slice(Math.max(0, at - window), at));
1218
+ const tail = normalized(text2.slice(offset + match.length, offset + match.length + window));
1219
+ const inLonger = [...afterLengths].some((after) => {
1220
+ const befores = tail.length >= after && around.get(tail.slice(0, after));
1221
+ return befores && [...befores].some(
1222
+ ([length, set]) => head.length >= length && set.has(head.slice(head.length - length))
1223
+ );
1224
+ });
1225
+ return inLonger ? match : `${lead}${newPath}`;
1226
+ });
1227
+ }
1228
+ function occurrences(text2, part) {
1229
+ const at = [];
1230
+ for (let i = text2.indexOf(part); i >= 0; i = text2.indexOf(part, i + 1)) at.push(i);
1231
+ return at;
1232
+ }
1233
+ function projectPaths(root, dir = root, prefix = "", ancestors = /* @__PURE__ */ new Set()) {
1234
+ return readableEntries(dir).flatMap((entry) => {
1235
+ const [path, full] = [`${prefix}${entry.name}`, join5(dir, entry.name)];
1236
+ const real = entry.name === "node_modules" ? null : directoryReal(entry, full);
1237
+ if (real === null || ancestors.has(real) || !isSafePath(root, full)) return [path];
1238
+ return [path, ...projectPaths(root, full, `${path}/`, /* @__PURE__ */ new Set([...ancestors, real]))];
1239
+ });
1240
+ }
1241
+ function directoryReal(entry, full) {
1242
+ try {
1243
+ return entry.isDirectory() || entry.isSymbolicLink() && statSync3(full).isDirectory() ? realpathSync(full) : null;
1244
+ } catch {
1245
+ return null;
1246
+ }
1247
+ }
1248
+ function updateReferences(projectDir, oldPath, newPath, isDirectory) {
1183
1249
  const textFiles = walkFiles(
1184
1250
  projectDir,
1185
1251
  (name) => /\.(html|css|js|jsx|ts|tsx|json|mjs|cjs|md|mdx)$/i.test(name)
1186
1252
  );
1253
+ const rewrite = referenceRewriter(oldPath, newPath, isDirectory, projectPaths(projectDir));
1187
1254
  let updatedCount = 0;
1188
1255
  for (const file of textFiles) {
1189
1256
  if (!isSafePath(projectDir, file)) continue;
1190
1257
  const content = readableText(file);
1191
1258
  if (content === null) continue;
1192
- if (!content.includes(oldPath)) continue;
1193
- const updated = content.split(oldPath).join(newPath);
1259
+ const updated = rewrite(content);
1194
1260
  if (updated !== content) {
1195
1261
  replaceFileAtomically2(file, updated, statSync3(file).mode);
1196
1262
  updatedCount++;
@@ -3027,9 +3093,10 @@ function registerFileRoutes(api, adapter) {
3027
3093
  if (existsSync3(newAbs)) {
3028
3094
  return c.json({ error: "already exists" }, 409);
3029
3095
  }
3096
+ const isDirectory = statSync3(res.absPath, { throwIfNoEntry: false })?.isDirectory() ?? false;
3030
3097
  ensureDir(res.project.dir, newAbs);
3031
3098
  renameSync2(res.absPath, newAbs);
3032
- const updatedFiles = updateReferences(res.project.dir, res.filePath, body.newPath);
3099
+ const updatedFiles = updateReferences(res.project.dir, res.filePath, body.newPath, isDirectory);
3033
3100
  return c.json({ ok: true, path: body.newPath, updatedReferences: updatedFiles });
3034
3101
  });
3035
3102
  api.post("/projects/:id/duplicate-file", async (c) => {