@gallop.software/studio 1.5.10 → 2.0.1

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.
Files changed (60) hide show
  1. package/app/api/studio/[...path]/route.ts +1 -0
  2. package/app/layout.tsx +23 -0
  3. package/app/page.tsx +90 -0
  4. package/bin/studio.mjs +110 -0
  5. package/dist/handlers/index.js +77 -55
  6. package/dist/handlers/index.js.map +1 -1
  7. package/dist/handlers/index.mjs +128 -106
  8. package/dist/handlers/index.mjs.map +1 -1
  9. package/dist/index.d.mts +14 -10
  10. package/dist/index.d.ts +14 -10
  11. package/dist/index.js +2 -177
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +4 -179
  14. package/dist/index.mjs.map +1 -1
  15. package/next.config.mjs +22 -0
  16. package/package.json +18 -10
  17. package/src/components/AddNewModal.tsx +402 -0
  18. package/src/components/ErrorModal.tsx +89 -0
  19. package/src/components/R2SetupModal.tsx +400 -0
  20. package/src/components/StudioBreadcrumb.tsx +115 -0
  21. package/src/components/StudioButton.tsx +200 -0
  22. package/src/components/StudioContext.tsx +219 -0
  23. package/src/components/StudioDetailView.tsx +714 -0
  24. package/src/components/StudioFileGrid.tsx +704 -0
  25. package/src/components/StudioFileList.tsx +743 -0
  26. package/src/components/StudioFolderPicker.tsx +342 -0
  27. package/src/components/StudioModal.tsx +473 -0
  28. package/src/components/StudioPreview.tsx +399 -0
  29. package/src/components/StudioSettings.tsx +536 -0
  30. package/src/components/StudioToolbar.tsx +1448 -0
  31. package/src/components/StudioUI.tsx +731 -0
  32. package/src/components/styles/common.ts +236 -0
  33. package/src/components/tokens.ts +78 -0
  34. package/src/components/useStudioActions.tsx +497 -0
  35. package/src/config/index.ts +7 -0
  36. package/src/config/workspace.ts +52 -0
  37. package/src/handlers/favicon.ts +152 -0
  38. package/src/handlers/files.ts +784 -0
  39. package/src/handlers/images.ts +949 -0
  40. package/src/handlers/import.ts +190 -0
  41. package/src/handlers/index.ts +168 -0
  42. package/src/handlers/list.ts +627 -0
  43. package/src/handlers/scan.ts +311 -0
  44. package/src/handlers/utils/cdn.ts +234 -0
  45. package/src/handlers/utils/files.ts +64 -0
  46. package/src/handlers/utils/index.ts +4 -0
  47. package/src/handlers/utils/meta.ts +102 -0
  48. package/src/handlers/utils/thumbnails.ts +98 -0
  49. package/src/hooks/useFileList.ts +143 -0
  50. package/src/index.tsx +36 -0
  51. package/src/lib/api.ts +176 -0
  52. package/src/types.ts +119 -0
  53. package/dist/StudioUI-GJK45R3T.js +0 -6500
  54. package/dist/StudioUI-GJK45R3T.js.map +0 -1
  55. package/dist/StudioUI-QZ54STXE.mjs +0 -6500
  56. package/dist/StudioUI-QZ54STXE.mjs.map +0 -1
  57. package/dist/chunk-N6JYTJCB.js +0 -68
  58. package/dist/chunk-N6JYTJCB.js.map +0 -1
  59. package/dist/chunk-RHI3UROE.mjs +0 -68
  60. package/dist/chunk-RHI3UROE.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ export { GET, POST, DELETE } from '../../../../src/handlers'
package/app/layout.tsx ADDED
@@ -0,0 +1,23 @@
1
+ /** @jsxRuntime automatic */
2
+ /** @jsxImportSource react */
3
+ import type { Metadata } from 'next'
4
+ import type { ReactNode } from 'react'
5
+
6
+ export const metadata: Metadata = {
7
+ title: 'Studio - Media Manager',
8
+ description: 'Manage images and media files for your project',
9
+ }
10
+
11
+ export default function RootLayout({
12
+ children,
13
+ }: {
14
+ children: ReactNode
15
+ }) {
16
+ return (
17
+ <html lang="en">
18
+ <body style={{ margin: 0, padding: 0, backgroundColor: '#0a0a0a' }}>
19
+ {children}
20
+ </body>
21
+ </html>
22
+ )
23
+ }
package/app/page.tsx ADDED
@@ -0,0 +1,90 @@
1
+ 'use client'
2
+
3
+ import dynamic from 'next/dynamic'
4
+ import { css } from '@emotion/react'
5
+
6
+ const colors = {
7
+ background: '#0a0a0a',
8
+ primary: '#635bff',
9
+ border: '#2a2a2a',
10
+ textSecondary: '#888888',
11
+ }
12
+
13
+ const fontStack = `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif`
14
+
15
+ // Dynamically import StudioUI from compiled dist
16
+ const StudioUI = dynamic(() => import('../src/components/StudioUI'), {
17
+ ssr: false,
18
+ loading: () => <LoadingState />,
19
+ })
20
+
21
+ const styles = {
22
+ container: css`
23
+ position: fixed;
24
+ top: 0;
25
+ left: 0;
26
+ right: 0;
27
+ bottom: 0;
28
+ background: ${colors.background};
29
+ font-family: ${fontStack};
30
+ `,
31
+ loading: css`
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ height: 100vh;
36
+ background: ${colors.background};
37
+ font-family: ${fontStack};
38
+ `,
39
+ loadingContent: css`
40
+ display: flex;
41
+ flex-direction: column;
42
+ align-items: center;
43
+ gap: 16px;
44
+ `,
45
+ spinner: css`
46
+ width: 36px;
47
+ height: 36px;
48
+ border-radius: 50%;
49
+ border: 3px solid ${colors.border};
50
+ border-top-color: ${colors.primary};
51
+ animation: spin 0.8s linear infinite;
52
+
53
+ @keyframes spin {
54
+ to {
55
+ transform: rotate(360deg);
56
+ }
57
+ }
58
+ `,
59
+ loadingText: css`
60
+ color: ${colors.textSecondary};
61
+ font-size: 14px;
62
+ font-weight: 500;
63
+ margin: 0;
64
+ `,
65
+ }
66
+
67
+ function LoadingState() {
68
+ return (
69
+ <div css={styles.loading}>
70
+ <div css={styles.loadingContent}>
71
+ <div css={styles.spinner} />
72
+ <p css={styles.loadingText}>Loading Studio...</p>
73
+ </div>
74
+ </div>
75
+ )
76
+ }
77
+
78
+ export default function StudioPage() {
79
+ const workspace = process.env.NEXT_PUBLIC_STUDIO_WORKSPACE || 'Unknown'
80
+
81
+ return (
82
+ <div css={styles.container}>
83
+ <StudioUI
84
+ isVisible={true}
85
+ standaloneMode={true}
86
+ workspacePath={workspace}
87
+ />
88
+ </div>
89
+ )
90
+ }
package/bin/studio.mjs ADDED
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from 'child_process'
4
+ import { resolve, dirname } from 'path'
5
+ import { fileURLToPath } from 'url'
6
+ import { existsSync } from 'fs'
7
+
8
+ const __filename = fileURLToPath(import.meta.url)
9
+ const __dirname = dirname(__filename)
10
+
11
+ // Parse command line arguments
12
+ const args = process.argv.slice(2)
13
+ let workspace = process.cwd()
14
+ let shouldOpen = false
15
+
16
+ for (let i = 0; i < args.length; i++) {
17
+ if (args[i] === '--workspace' && args[i + 1]) {
18
+ workspace = resolve(args[i + 1])
19
+ i++
20
+ } else if (args[i] === '--open' || args[i] === '-o') {
21
+ shouldOpen = true
22
+ } else if (args[i] === '--help' || args[i] === '-h') {
23
+ console.log(`
24
+ Studio - Media Manager
25
+
26
+ Usage:
27
+ studio [options]
28
+
29
+ Options:
30
+ --workspace <path> Path to the project workspace (default: current directory)
31
+ --open, -o Open browser automatically
32
+ --help, -h Show this help message
33
+
34
+ Examples:
35
+ studio # Run in current directory
36
+ studio --workspace ~/my-project # Run for specific project
37
+ studio --workspace . --open # Open browser automatically
38
+ `)
39
+ process.exit(0)
40
+ }
41
+ }
42
+
43
+ // Validate workspace exists
44
+ if (!existsSync(workspace)) {
45
+ console.error(`Error: Workspace path does not exist: ${workspace}`)
46
+ process.exit(1)
47
+ }
48
+
49
+ // Check for public folder
50
+ const publicPath = resolve(workspace, 'public')
51
+ if (!existsSync(publicPath)) {
52
+ console.error(`Error: No 'public' folder found in workspace: ${workspace}`)
53
+ console.error('Studio requires a public folder to manage media files.')
54
+ process.exit(1)
55
+ }
56
+
57
+ console.log(`
58
+ ┌─────────────────────────────────────┐
59
+ │ Studio - Media Manager │
60
+ ├─────────────────────────────────────┤
61
+ │ Workspace: ${workspace.length > 24 ? '...' + workspace.slice(-21) : workspace.padEnd(24)}│
62
+ │ URL: http://localhost:3001 │
63
+ └─────────────────────────────────────┘
64
+ `)
65
+
66
+ // Find the next binary
67
+ const studioRoot = resolve(__dirname, '..')
68
+ const nextBin = resolve(studioRoot, 'node_modules', '.bin', 'next')
69
+
70
+ if (!existsSync(nextBin)) {
71
+ console.error('Error: Next.js not found. Please run: npm install')
72
+ process.exit(1)
73
+ }
74
+
75
+ // Set up environment
76
+ const env = {
77
+ ...process.env,
78
+ STUDIO_WORKSPACE: workspace,
79
+ NODE_ENV: 'development',
80
+ }
81
+
82
+ // Start Next.js dev server
83
+ const child = spawn(nextBin, ['dev', '-p', '3001'], {
84
+ stdio: 'inherit',
85
+ cwd: studioRoot,
86
+ env,
87
+ })
88
+
89
+ // Open browser if requested
90
+ if (shouldOpen) {
91
+ setTimeout(async () => {
92
+ const open = (await import('open')).default
93
+ open('http://localhost:3001')
94
+ }, 2000)
95
+ }
96
+
97
+ // Handle process termination
98
+ process.on('SIGINT', () => {
99
+ child.kill('SIGINT')
100
+ process.exit(0)
101
+ })
102
+
103
+ process.on('SIGTERM', () => {
104
+ child.kill('SIGTERM')
105
+ process.exit(0)
106
+ })
107
+
108
+ child.on('close', (code) => {
109
+ process.exit(code || 0)
110
+ })
@@ -15,8 +15,31 @@ var _path = require('path'); var _path2 = _interopRequireDefault(_path);
15
15
  // src/handlers/utils/meta.ts
16
16
 
17
17
 
18
+ // src/config/workspace.ts
19
+
20
+ var workspacePath = null;
21
+ function getWorkspace() {
22
+ if (workspacePath === null) {
23
+ workspacePath = process.env.STUDIO_WORKSPACE || process.cwd();
24
+ }
25
+ return workspacePath;
26
+ }
27
+ function getPublicPath(...segments) {
28
+ return _path2.default.join(getWorkspace(), "public", ...segments);
29
+ }
30
+ function getDataPath(...segments) {
31
+ return _path2.default.join(getWorkspace(), "_data", ...segments);
32
+ }
33
+ function getSrcAppPath(...segments) {
34
+ return _path2.default.join(getWorkspace(), "src", "app", ...segments);
35
+ }
36
+ function getWorkspacePath(...segments) {
37
+ return _path2.default.join(getWorkspace(), ...segments);
38
+ }
39
+
40
+ // src/handlers/utils/meta.ts
18
41
  async function loadMeta() {
19
- const metaPath = _path2.default.join(process.cwd(), "_data", "_studio.json");
42
+ const metaPath = getDataPath("_studio.json");
20
43
  try {
21
44
  const content = await _fs.promises.readFile(metaPath, "utf-8");
22
45
  return JSON.parse(content);
@@ -25,9 +48,9 @@ async function loadMeta() {
25
48
  }
26
49
  }
27
50
  async function saveMeta(meta) {
28
- const dataDir = _path2.default.join(process.cwd(), "_data");
51
+ const dataDir = getDataPath();
29
52
  await _fs.promises.mkdir(dataDir, { recursive: true });
30
- const metaPath = _path2.default.join(dataDir, "_studio.json");
53
+ const metaPath = getDataPath("_studio.json");
31
54
  const ordered = {};
32
55
  if (meta._cdns) {
33
56
  ordered._cdns = meta._cdns;
@@ -123,7 +146,7 @@ async function processImage(buffer, imageKey) {
123
146
  const baseName = _path2.default.basename(keyWithoutSlash, _path2.default.extname(keyWithoutSlash));
124
147
  const ext = _path2.default.extname(keyWithoutSlash).toLowerCase();
125
148
  const imageDir = _path2.default.dirname(keyWithoutSlash);
126
- const imagesPath = _path2.default.join(process.cwd(), "public", "images", imageDir === "." ? "" : imageDir);
149
+ const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
127
150
  await _fs.promises.mkdir(imagesPath, { recursive: true });
128
151
  const isPng = ext === ".png";
129
152
  const outputExt = isPng ? ".png" : ".jpg";
@@ -131,7 +154,7 @@ async function processImage(buffer, imageKey) {
131
154
  o: { w: originalWidth, h: originalHeight }
132
155
  };
133
156
  const fullFileName = imageDir === "." ? `${baseName}${outputExt}` : `${imageDir}/${baseName}${outputExt}`;
134
- const fullPath = _path2.default.join(process.cwd(), "public", "images", fullFileName);
157
+ const fullPath = getPublicPath("images", fullFileName);
135
158
  let fullWidth = originalWidth;
136
159
  let fullHeight = originalHeight;
137
160
  if (originalWidth > FULL_MAX_WIDTH) {
@@ -158,7 +181,7 @@ async function processImage(buffer, imageKey) {
158
181
  const newHeight = Math.round(maxWidth * ratio);
159
182
  const sizeFileName = `${baseName}${suffix}${outputExt}`;
160
183
  const sizeFilePath = imageDir === "." ? sizeFileName : `${imageDir}/${sizeFileName}`;
161
- const sizePath = _path2.default.join(process.cwd(), "public", "images", sizeFilePath);
184
+ const sizePath = getPublicPath("images", sizeFilePath);
162
185
  if (isPng) {
163
186
  await _sharp2.default.call(void 0, buffer).resize(maxWidth, newHeight).png({ quality: 80 }).toFile(sizePath);
164
187
  } else {
@@ -173,7 +196,6 @@ async function processImage(buffer, imageKey) {
173
196
 
174
197
  // src/handlers/utils/cdn.ts
175
198
 
176
-
177
199
  var _clients3 = require('@aws-sdk/client-s3');
178
200
  async function purgeCloudflareCache(urls) {
179
201
  const zoneId = process.env.CLOUDFLARE_ZONE_ID;
@@ -247,7 +269,7 @@ async function uploadToCdn(imageKey) {
247
269
  if (!bucketName) throw new Error("R2 bucket not configured");
248
270
  const r2 = getR2Client();
249
271
  for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
250
- const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
272
+ const localPath = getPublicPath(thumbPath);
251
273
  try {
252
274
  const fileBuffer = await _fs.promises.readFile(localPath);
253
275
  await r2.send(
@@ -264,7 +286,7 @@ async function uploadToCdn(imageKey) {
264
286
  }
265
287
  async function deleteLocalThumbnails(imageKey) {
266
288
  for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
267
- const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
289
+ const localPath = getPublicPath(thumbPath);
268
290
  try {
269
291
  await _fs.promises.unlink(localPath);
270
292
  } catch (e3) {
@@ -295,7 +317,7 @@ async function uploadOriginalToCdn(imageKey) {
295
317
  const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
296
318
  if (!bucketName) throw new Error("R2 bucket not configured");
297
319
  const r2 = getR2Client();
298
- const localPath = _path2.default.join(process.cwd(), "public", imageKey);
320
+ const localPath = getPublicPath(imageKey);
299
321
  const fileBuffer = await _fs.promises.readFile(localPath);
300
322
  await r2.send(
301
323
  new (0, _clients3.PutObjectCommand)({
@@ -495,7 +517,7 @@ async function handleList(request) {
495
517
  }
496
518
  return _server.NextResponse.json({ items });
497
519
  }
498
- const absoluteDir = _path2.default.join(process.cwd(), requestedPath);
520
+ const absoluteDir = getWorkspacePath(requestedPath);
499
521
  try {
500
522
  const dirEntries = await _fs.promises.readdir(absoluteDir, { withFileTypes: true });
501
523
  for (const entry of dirEntries) {
@@ -606,7 +628,7 @@ async function handleList(request) {
606
628
  hasThumbnail = true;
607
629
  }
608
630
  } else {
609
- const localThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
631
+ const localThumbPath = getPublicPath(thumbPath);
610
632
  try {
611
633
  await _fs.promises.access(localThumbPath);
612
634
  thumbnail = thumbPath;
@@ -627,7 +649,7 @@ async function handleList(request) {
627
649
  }
628
650
  if (!isPushedToCloud) {
629
651
  try {
630
- const filePath = _path2.default.join(process.cwd(), "public", key);
652
+ const filePath = getPublicPath(key);
631
653
  const stats = await _fs.promises.stat(filePath);
632
654
  fileSize = stats.size;
633
655
  } catch (e9) {
@@ -687,7 +709,7 @@ async function handleSearch(request) {
687
709
  hasThumbnail = true;
688
710
  }
689
711
  } else {
690
- const localThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
712
+ const localThumbPath = getPublicPath(thumbPath);
691
713
  try {
692
714
  await _fs.promises.access(localThumbPath);
693
715
  thumbnail = thumbPath;
@@ -751,7 +773,7 @@ async function handleListFolders() {
751
773
  } catch (e11) {
752
774
  }
753
775
  }
754
- const publicDir = _path2.default.join(process.cwd(), "public");
776
+ const publicDir = getPublicPath();
755
777
  await scanDir(publicDir, "");
756
778
  const folders = [];
757
779
  folders.push({ path: "public", name: "public", depth: 0 });
@@ -871,7 +893,7 @@ async function handleUpload(request) {
871
893
  imageKey = newKey;
872
894
  }
873
895
  const actualFileName = _path2.default.basename(imageKey);
874
- const uploadDir = _path2.default.join(process.cwd(), "public", relativeDir);
896
+ const uploadDir = getPublicPath(relativeDir);
875
897
  await _fs.promises.mkdir(uploadDir, { recursive: true });
876
898
  await _fs.promises.writeFile(_path2.default.join(uploadDir, actualFileName), buffer);
877
899
  if (!isMedia) {
@@ -920,7 +942,7 @@ async function handleDelete(request) {
920
942
  errors.push(`Invalid path: ${itemPath}`);
921
943
  continue;
922
944
  }
923
- const absolutePath = _path2.default.join(process.cwd(), itemPath);
945
+ const absolutePath = getWorkspacePath(itemPath);
924
946
  const imageKey = "/" + itemPath.replace(/^public\//, "");
925
947
  const entry = meta[imageKey];
926
948
  const isPushedToCloud = _optionalChain([entry, 'optionalAccess', _18 => _18.c]) !== void 0;
@@ -934,7 +956,7 @@ async function handleDelete(request) {
934
956
  const keyEntry = meta[key];
935
957
  if (keyEntry && keyEntry.c === void 0) {
936
958
  for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, key)) {
937
- const absoluteThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
959
+ const absoluteThumbPath = getPublicPath(thumbPath);
938
960
  try {
939
961
  await _fs.promises.unlink(absoluteThumbPath);
940
962
  } catch (e13) {
@@ -950,7 +972,7 @@ async function handleDelete(request) {
950
972
  if (!isInImagesFolder && entry) {
951
973
  if (!isPushedToCloud) {
952
974
  for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
953
- const absoluteThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
975
+ const absoluteThumbPath = getPublicPath(thumbPath);
954
976
  try {
955
977
  await _fs.promises.unlink(absoluteThumbPath);
956
978
  } catch (e14) {
@@ -1006,8 +1028,8 @@ async function handleCreateFolder(request) {
1006
1028
  return _server.NextResponse.json({ error: "Invalid folder name" }, { status: 400 });
1007
1029
  }
1008
1030
  const safePath = (parentPath || "public").replace(/\.\./g, "");
1009
- const folderPath = _path2.default.join(process.cwd(), safePath, sanitizedName);
1010
- if (!folderPath.startsWith(_path2.default.join(process.cwd(), "public"))) {
1031
+ const folderPath = getWorkspacePath(safePath, sanitizedName);
1032
+ if (!folderPath.startsWith(getPublicPath())) {
1011
1033
  return _server.NextResponse.json({ error: "Invalid path" }, { status: 400 });
1012
1034
  }
1013
1035
  try {
@@ -1033,10 +1055,10 @@ async function handleRename(request) {
1033
1055
  return _server.NextResponse.json({ error: "Invalid name" }, { status: 400 });
1034
1056
  }
1035
1057
  const safePath = oldPath.replace(/\.\./g, "");
1036
- const absoluteOldPath = _path2.default.join(process.cwd(), safePath);
1058
+ const absoluteOldPath = getWorkspacePath(safePath);
1037
1059
  const parentDir = _path2.default.dirname(absoluteOldPath);
1038
1060
  const absoluteNewPath = _path2.default.join(parentDir, sanitizedName);
1039
- if (!absoluteOldPath.startsWith(_path2.default.join(process.cwd(), "public"))) {
1061
+ if (!absoluteOldPath.startsWith(getPublicPath())) {
1040
1062
  return _server.NextResponse.json({ error: "Invalid path" }, { status: 400 });
1041
1063
  }
1042
1064
  try {
@@ -1064,8 +1086,8 @@ async function handleRename(request) {
1064
1086
  const oldThumbPaths = _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, oldKey);
1065
1087
  const newThumbPaths = _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, newKey);
1066
1088
  for (let i = 0; i < oldThumbPaths.length; i++) {
1067
- const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[i]);
1068
- const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[i]);
1089
+ const oldThumbPath = getPublicPath(oldThumbPaths[i]);
1090
+ const newThumbPath = getPublicPath(newThumbPaths[i]);
1069
1091
  await _fs.promises.mkdir(_path2.default.dirname(newThumbPath), { recursive: true });
1070
1092
  try {
1071
1093
  await _fs.promises.rename(oldThumbPath, newThumbPath);
@@ -1106,8 +1128,8 @@ async function handleMoveStream(request) {
1106
1128
  return;
1107
1129
  }
1108
1130
  const safeDestination = destination.replace(/\.\./g, "");
1109
- const absoluteDestination = _path2.default.join(process.cwd(), safeDestination);
1110
- if (!absoluteDestination.startsWith(_path2.default.join(process.cwd(), "public"))) {
1131
+ const absoluteDestination = getWorkspacePath(safeDestination);
1132
+ if (!absoluteDestination.startsWith(getPublicPath())) {
1111
1133
  sendEvent({ type: "error", message: "Invalid destination" });
1112
1134
  controller.close();
1113
1135
  return;
@@ -1189,7 +1211,7 @@ async function handleMoveStream(request) {
1189
1211
  meta[newKey] = newEntry;
1190
1212
  moved.push(itemPath);
1191
1213
  } else {
1192
- const absolutePath = _path2.default.join(process.cwd(), safePath);
1214
+ const absolutePath = getWorkspacePath(safePath);
1193
1215
  if (absoluteDestination.startsWith(absolutePath + _path2.default.sep)) {
1194
1216
  errors.push(`Cannot move ${itemName} into itself`);
1195
1217
  continue;
@@ -1212,8 +1234,8 @@ async function handleMoveStream(request) {
1212
1234
  const oldThumbPaths = _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, oldKey);
1213
1235
  const newThumbPaths = _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, newKey);
1214
1236
  for (let j = 0; j < oldThumbPaths.length; j++) {
1215
- const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[j]);
1216
- const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[j]);
1237
+ const oldThumbPath = getPublicPath(oldThumbPaths[j]);
1238
+ const newThumbPath = getPublicPath(newThumbPaths[j]);
1217
1239
  await _fs.promises.mkdir(_path2.default.dirname(newThumbPath), { recursive: true });
1218
1240
  try {
1219
1241
  await _fs.promises.rename(oldThumbPath, newThumbPath);
@@ -1319,7 +1341,7 @@ async function handleSync(request) {
1319
1341
  const remoteUrl = `${existingCdnUrl}${imageKey}`;
1320
1342
  originalBuffer = await downloadFromRemoteUrl(remoteUrl);
1321
1343
  } else {
1322
- const originalLocalPath = _path2.default.join(process.cwd(), "public", imageKey);
1344
+ const originalLocalPath = getPublicPath(imageKey);
1323
1345
  try {
1324
1346
  originalBuffer = await _fs.promises.readFile(originalLocalPath);
1325
1347
  } catch (e24) {
@@ -1338,7 +1360,7 @@ async function handleSync(request) {
1338
1360
  urlsToPurge.push(`${publicUrl}${imageKey}`);
1339
1361
  if (!isRemote && _chunkVI6QG6WTjs.isProcessed.call(void 0, entry)) {
1340
1362
  for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
1341
- const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
1363
+ const localPath = getPublicPath(thumbPath);
1342
1364
  try {
1343
1365
  const fileBuffer = await _fs.promises.readFile(localPath);
1344
1366
  await r2.send(
@@ -1356,9 +1378,9 @@ async function handleSync(request) {
1356
1378
  }
1357
1379
  entry.c = cdnIndex;
1358
1380
  if (!isRemote) {
1359
- const originalLocalPath = _path2.default.join(process.cwd(), "public", imageKey);
1381
+ const originalLocalPath = getPublicPath(imageKey);
1360
1382
  for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
1361
- const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
1383
+ const localPath = getPublicPath(thumbPath);
1362
1384
  try {
1363
1385
  await _fs.promises.unlink(localPath);
1364
1386
  } catch (e26) {
@@ -1412,7 +1434,7 @@ async function handleReprocess(request) {
1412
1434
  const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
1413
1435
  const isInOurR2 = existingCdnUrl === publicUrl;
1414
1436
  const isRemote = existingCdnIndex !== void 0 && !isInOurR2;
1415
- const originalPath = _path2.default.join(process.cwd(), "public", imageKey);
1437
+ const originalPath = getPublicPath(imageKey);
1416
1438
  try {
1417
1439
  buffer = await _fs.promises.readFile(originalPath);
1418
1440
  } catch (e28) {
@@ -1622,7 +1644,7 @@ async function handleReprocessStream(request) {
1622
1644
  const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
1623
1645
  const isInOurR2 = existingCdnUrl === publicUrl;
1624
1646
  const isRemote = existingCdnIndex !== void 0 && !isInOurR2;
1625
- const originalPath = _path2.default.join(process.cwd(), "public", imageKey);
1647
+ const originalPath = getPublicPath(imageKey);
1626
1648
  try {
1627
1649
  buffer = await _fs.promises.readFile(originalPath);
1628
1650
  } catch (e32) {
@@ -1645,7 +1667,7 @@ async function handleReprocessStream(request) {
1645
1667
  const isSvg = ext === ".svg";
1646
1668
  if (isSvg) {
1647
1669
  const imageDir = _path2.default.dirname(imageKey.slice(1));
1648
- const imagesPath = _path2.default.join(process.cwd(), "public", "images", imageDir === "." ? "" : imageDir);
1670
+ const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
1649
1671
  await _fs.promises.mkdir(imagesPath, { recursive: true });
1650
1672
  const fileName = _path2.default.basename(imageKey);
1651
1673
  const destPath = _path2.default.join(imagesPath, fileName);
@@ -1745,7 +1767,7 @@ async function handleProcessAllStream() {
1745
1767
  sendEvent({ type: "start", total });
1746
1768
  for (let i = 0; i < imagesToProcess.length; i++) {
1747
1769
  const { key, entry } = imagesToProcess[i];
1748
- const fullPath = _path2.default.join(process.cwd(), "public", key);
1770
+ const fullPath = getPublicPath(key);
1749
1771
  const existingCdnIndex = entry.c;
1750
1772
  const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
1751
1773
  const isInOurR2 = existingCdnUrl === publicUrl;
@@ -1778,7 +1800,7 @@ async function handleProcessAllStream() {
1778
1800
  const isSvg = ext === ".svg";
1779
1801
  if (isSvg) {
1780
1802
  const imageDir = _path2.default.dirname(key.slice(1));
1781
- const imagesPath = _path2.default.join(process.cwd(), "public", "images", imageDir === "." ? "" : imageDir);
1803
+ const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
1782
1804
  await _fs.promises.mkdir(imagesPath, { recursive: true });
1783
1805
  const fileName = _path2.default.basename(key);
1784
1806
  const destPath = _path2.default.join(imagesPath, fileName);
@@ -1850,7 +1872,7 @@ async function handleProcessAllStream() {
1850
1872
  } catch (e35) {
1851
1873
  }
1852
1874
  }
1853
- const imagesDir = _path2.default.join(process.cwd(), "public", "images");
1875
+ const imagesDir = getPublicPath("images");
1854
1876
  try {
1855
1877
  await findOrphans(imagesDir);
1856
1878
  } catch (e36) {
@@ -1940,7 +1962,7 @@ async function handleDownloadStream(request) {
1940
1962
  }
1941
1963
  try {
1942
1964
  const imageBuffer = await downloadFromCdn(imageKey);
1943
- const localPath = _path2.default.join(process.cwd(), "public", imageKey.replace(/^\//, ""));
1965
+ const localPath = getPublicPath(imageKey.replace(/^\//, ""));
1944
1966
  await _fs.promises.mkdir(_path2.default.dirname(localPath), { recursive: true });
1945
1967
  await _fs.promises.writeFile(localPath, imageBuffer);
1946
1968
  await deleteThumbnailsFromCdn(imageKey);
@@ -2044,7 +2066,7 @@ async function handleScanStream() {
2044
2066
  } catch (e39) {
2045
2067
  }
2046
2068
  }
2047
- const publicDir = _path2.default.join(process.cwd(), "public");
2069
+ const publicDir = getPublicPath();
2048
2070
  await scanDir(publicDir);
2049
2071
  const total = allFiles.length;
2050
2072
  sendEvent({ type: "start", total });
@@ -2071,7 +2093,7 @@ async function handleScanStream() {
2071
2093
  newKey = `/${baseName}-${counter}${ext}`;
2072
2094
  }
2073
2095
  const newRelativePath = `${baseName}-${counter}${ext}`;
2074
- const newFullPath = _path2.default.join(process.cwd(), "public", newRelativePath);
2096
+ const newFullPath = getPublicPath(newRelativePath);
2075
2097
  try {
2076
2098
  await _fs.promises.rename(fullPath, newFullPath);
2077
2099
  renamed.push({ from: relativePath, to: newRelativePath });
@@ -2143,7 +2165,7 @@ async function handleScanStream() {
2143
2165
  } catch (e41) {
2144
2166
  }
2145
2167
  }
2146
- const imagesDir = _path2.default.join(process.cwd(), "public", "images");
2168
+ const imagesDir = getPublicPath("images");
2147
2169
  try {
2148
2170
  await findOrphans(imagesDir);
2149
2171
  } catch (e42) {
@@ -2187,7 +2209,7 @@ async function handleDeleteOrphans(request) {
2187
2209
  errors.push(`Invalid path: ${orphanPath}`);
2188
2210
  continue;
2189
2211
  }
2190
- const fullPath = _path2.default.join(process.cwd(), "public", orphanPath);
2212
+ const fullPath = getPublicPath(orphanPath);
2191
2213
  try {
2192
2214
  await _fs.promises.unlink(fullPath);
2193
2215
  deleted.push(orphanPath);
@@ -2196,7 +2218,7 @@ async function handleDeleteOrphans(request) {
2196
2218
  errors.push(orphanPath);
2197
2219
  }
2198
2220
  }
2199
- const imagesDir = _path2.default.join(process.cwd(), "public", "images");
2221
+ const imagesDir = getPublicPath("images");
2200
2222
  async function removeEmptyDirs(dir) {
2201
2223
  try {
2202
2224
  const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
@@ -2238,8 +2260,8 @@ async function handleDeleteOrphans(request) {
2238
2260
  function parseImageUrl(url) {
2239
2261
  const parsed = new URL(url);
2240
2262
  const base = `${parsed.protocol}//${parsed.host}`;
2241
- const path10 = parsed.pathname;
2242
- return { base, path: path10 };
2263
+ const path9 = parsed.pathname;
2264
+ return { base, path: path9 };
2243
2265
  }
2244
2266
  async function processRemoteImage(url) {
2245
2267
  const response = await fetch(url);
@@ -2288,20 +2310,20 @@ async function handleImportUrls(request) {
2288
2310
  currentFile: url
2289
2311
  });
2290
2312
  try {
2291
- const { base, path: path10 } = parseImageUrl(url);
2292
- const existingEntry = getMetaEntry(meta, path10);
2313
+ const { base, path: path9 } = parseImageUrl(url);
2314
+ const existingEntry = getMetaEntry(meta, path9);
2293
2315
  if (existingEntry) {
2294
- skipped.push(path10);
2316
+ skipped.push(path9);
2295
2317
  continue;
2296
2318
  }
2297
2319
  const cdnIndex = getOrAddCdnIndex(meta, base);
2298
2320
  const imageData = await processRemoteImage(url);
2299
- setMetaEntry(meta, path10, {
2321
+ setMetaEntry(meta, path9, {
2300
2322
  o: imageData.o,
2301
2323
  b: imageData.b,
2302
2324
  c: cdnIndex
2303
2325
  });
2304
- added.push(path10);
2326
+ added.push(path9);
2305
2327
  } catch (error) {
2306
2328
  console.error(`Failed to import ${url}:`, error);
2307
2329
  errors.push(url);
@@ -2384,7 +2406,7 @@ async function handleGenerateFavicon(request) {
2384
2406
  error: "Source file must be named favicon.png or favicon.jpg"
2385
2407
  }, { status: 400 });
2386
2408
  }
2387
- const sourcePath = _path2.default.join(process.cwd(), "public", imagePath.replace(/^\//, ""));
2409
+ const sourcePath = getPublicPath(imagePath.replace(/^\//, ""));
2388
2410
  try {
2389
2411
  await _promises2.default.access(sourcePath);
2390
2412
  } catch (e46) {
@@ -2396,7 +2418,7 @@ async function handleGenerateFavicon(request) {
2396
2418
  } catch (e47) {
2397
2419
  return _server.NextResponse.json({ error: "Source file is not a valid image" }, { status: 400 });
2398
2420
  }
2399
- const outputDir = _path2.default.join(process.cwd(), "src", "app");
2421
+ const outputDir = getSrcAppPath();
2400
2422
  try {
2401
2423
  await _promises2.default.access(outputDir);
2402
2424
  } catch (e48) {