@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.
- package/app/api/studio/[...path]/route.ts +1 -0
- package/app/layout.tsx +23 -0
- package/app/page.tsx +90 -0
- package/bin/studio.mjs +110 -0
- package/dist/handlers/index.js +77 -55
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/index.mjs +128 -106
- package/dist/handlers/index.mjs.map +1 -1
- package/dist/index.d.mts +14 -10
- package/dist/index.d.ts +14 -10
- package/dist/index.js +2 -177
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4 -179
- package/dist/index.mjs.map +1 -1
- package/next.config.mjs +22 -0
- package/package.json +18 -10
- package/src/components/AddNewModal.tsx +402 -0
- package/src/components/ErrorModal.tsx +89 -0
- package/src/components/R2SetupModal.tsx +400 -0
- package/src/components/StudioBreadcrumb.tsx +115 -0
- package/src/components/StudioButton.tsx +200 -0
- package/src/components/StudioContext.tsx +219 -0
- package/src/components/StudioDetailView.tsx +714 -0
- package/src/components/StudioFileGrid.tsx +704 -0
- package/src/components/StudioFileList.tsx +743 -0
- package/src/components/StudioFolderPicker.tsx +342 -0
- package/src/components/StudioModal.tsx +473 -0
- package/src/components/StudioPreview.tsx +399 -0
- package/src/components/StudioSettings.tsx +536 -0
- package/src/components/StudioToolbar.tsx +1448 -0
- package/src/components/StudioUI.tsx +731 -0
- package/src/components/styles/common.ts +236 -0
- package/src/components/tokens.ts +78 -0
- package/src/components/useStudioActions.tsx +497 -0
- package/src/config/index.ts +7 -0
- package/src/config/workspace.ts +52 -0
- package/src/handlers/favicon.ts +152 -0
- package/src/handlers/files.ts +784 -0
- package/src/handlers/images.ts +949 -0
- package/src/handlers/import.ts +190 -0
- package/src/handlers/index.ts +168 -0
- package/src/handlers/list.ts +627 -0
- package/src/handlers/scan.ts +311 -0
- package/src/handlers/utils/cdn.ts +234 -0
- package/src/handlers/utils/files.ts +64 -0
- package/src/handlers/utils/index.ts +4 -0
- package/src/handlers/utils/meta.ts +102 -0
- package/src/handlers/utils/thumbnails.ts +98 -0
- package/src/hooks/useFileList.ts +143 -0
- package/src/index.tsx +36 -0
- package/src/lib/api.ts +176 -0
- package/src/types.ts +119 -0
- package/dist/StudioUI-GJK45R3T.js +0 -6500
- package/dist/StudioUI-GJK45R3T.js.map +0 -1
- package/dist/StudioUI-QZ54STXE.mjs +0 -6500
- package/dist/StudioUI-QZ54STXE.mjs.map +0 -1
- package/dist/chunk-N6JYTJCB.js +0 -68
- package/dist/chunk-N6JYTJCB.js.map +0 -1
- package/dist/chunk-RHI3UROE.mjs +0 -68
- 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
|
+
})
|
package/dist/handlers/index.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
51
|
+
const dataDir = getDataPath();
|
|
29
52
|
await _fs.promises.mkdir(dataDir, { recursive: true });
|
|
30
|
-
const metaPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1010
|
-
if (!folderPath.startsWith(
|
|
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 =
|
|
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(
|
|
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 =
|
|
1068
|
-
const newThumbPath =
|
|
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 =
|
|
1110
|
-
if (!absoluteDestination.startsWith(
|
|
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 =
|
|
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 =
|
|
1216
|
-
const newThumbPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1381
|
+
const originalLocalPath = getPublicPath(imageKey);
|
|
1360
1382
|
for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
1361
|
-
const localPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
2242
|
-
return { base, path:
|
|
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:
|
|
2292
|
-
const existingEntry = getMetaEntry(meta,
|
|
2313
|
+
const { base, path: path9 } = parseImageUrl(url);
|
|
2314
|
+
const existingEntry = getMetaEntry(meta, path9);
|
|
2293
2315
|
if (existingEntry) {
|
|
2294
|
-
skipped.push(
|
|
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,
|
|
2321
|
+
setMetaEntry(meta, path9, {
|
|
2300
2322
|
o: imageData.o,
|
|
2301
2323
|
b: imageData.b,
|
|
2302
2324
|
c: cdnIndex
|
|
2303
2325
|
});
|
|
2304
|
-
added.push(
|
|
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 =
|
|
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 =
|
|
2421
|
+
const outputDir = getSrcAppPath();
|
|
2400
2422
|
try {
|
|
2401
2423
|
await _promises2.default.access(outputDir);
|
|
2402
2424
|
} catch (e48) {
|