launchpd 1.0.2 → 1.0.5

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.
@@ -1,12 +1,12 @@
1
- import { readdir, readFile } from 'node:fs/promises';
2
- import { join, relative, posix, sep } from 'node:path';
3
- import mime from 'mime-types';
4
- import { config } from '../config.js';
5
- import { getApiKey, getApiSecret } from './credentials.js';
6
- import { createHmac } from 'node:crypto';
7
- import { isIgnored } from './ignore.js';
1
+ import { readdir, readFile } from 'node:fs/promises'
2
+ import { join, relative, posix, sep } from 'node:path'
3
+ import mime from 'mime-types'
4
+ import { config } from '../config.js'
5
+ import { getApiKey, getApiSecret } from './credentials.js'
6
+ import { createHmac } from 'node:crypto'
7
+ import { isIgnored } from './ignore.js'
8
8
 
9
- const API_BASE_URL = config.apiUrl;
9
+ const API_BASE_URL = config.apiUrl
10
10
 
11
11
  /**
12
12
  * Convert Windows path to POSIX for R2 keys
@@ -14,7 +14,7 @@ const API_BASE_URL = config.apiUrl;
14
14
  * @returns {string}
15
15
  */
16
16
  function toPosixPath(windowsPath) {
17
- return windowsPath.split(sep).join(posix.sep);
17
+ return windowsPath.split(sep).join(posix.sep)
18
18
  }
19
19
 
20
20
  /**
@@ -26,42 +26,49 @@ function toPosixPath(windowsPath) {
26
26
  * @param {string} contentType - MIME type
27
27
  */
28
28
  async function uploadFile(content, subdomain, version, filePath, contentType) {
29
- const apiKey = await getApiKey();
30
- const apiSecret = await getApiSecret();
31
- const headers = {
32
- 'X-API-Key': apiKey,
33
- 'X-Subdomain': subdomain,
34
- 'X-Version': String(version),
35
- 'X-File-Path': filePath,
36
- 'X-Content-Type': contentType,
37
- 'Content-Type': 'application/octet-stream',
38
- };
39
-
40
- if (apiSecret) {
41
- const timestamp = Date.now().toString();
42
- const endpoint = '/api/upload/file'; // Match the worker path
43
- const hmac = createHmac('sha256', apiSecret);
44
- hmac.update('POST');
45
- hmac.update(endpoint);
46
- hmac.update(timestamp);
47
- hmac.update(content); // Buffer is fine for update()
48
-
49
- headers['X-Timestamp'] = timestamp;
50
- headers['X-Signature'] = hmac.digest('hex');
29
+ const apiKey = await getApiKey()
30
+ const apiSecret = await getApiSecret()
31
+ const headers = {
32
+ 'X-API-Key': apiKey,
33
+ 'X-Subdomain': subdomain,
34
+ 'X-Version': String(version),
35
+ 'X-File-Path': filePath,
36
+ 'X-Content-Type': contentType,
37
+ 'Content-Type': 'application/octet-stream'
38
+ }
39
+
40
+ if (apiSecret) {
41
+ const timestamp = Date.now().toString()
42
+ const endpoint = '/api/upload/file' // Match the worker path
43
+ const hmac = createHmac('sha256', apiSecret)
44
+ hmac.update('POST')
45
+ hmac.update(endpoint)
46
+ hmac.update(timestamp)
47
+ hmac.update(content) // Buffer is fine for update()
48
+
49
+ headers['X-Timestamp'] = timestamp
50
+ headers['X-Signature'] = hmac.digest('hex')
51
+ }
52
+
53
+ const response = await fetch(`${API_BASE_URL}/api/upload/file`, {
54
+ method: 'POST',
55
+ headers,
56
+ body: content
57
+ })
58
+
59
+ if (!response.ok) {
60
+ const text = await response.text().catch(() => '')
61
+ let errorMsg = ''
62
+ try {
63
+ const data = JSON.parse(text)
64
+ errorMsg = data.error
65
+ } catch {
66
+ if (text) errorMsg = text
51
67
  }
68
+ throw new Error(errorMsg || `Upload failed: ${response.status}`)
69
+ }
52
70
 
53
- const response = await fetch(`${API_BASE_URL}/api/upload/file`, {
54
- method: 'POST',
55
- headers,
56
- body: content,
57
- });
58
-
59
- if (!response.ok) {
60
- const error = await response.json().catch(() => ({ error: 'Upload failed' }));
61
- throw new Error(error.error || `Upload failed: ${response.status}`);
62
- }
63
-
64
- return response.json();
71
+ return response.json()
65
72
  }
66
73
 
67
74
  /**
@@ -73,56 +80,67 @@ async function uploadFile(content, subdomain, version, filePath, contentType) {
73
80
  * @param {string} folderName - Original folder name
74
81
  * @param {string|null} expiresAt - ISO expiration timestamp
75
82
  */
76
- async function completeUpload(subdomain, version, fileCount, totalBytes, folderName, expiresAt, message) {
77
- const apiKey = await getApiKey();
78
- const apiSecret = await getApiSecret();
79
- const headers = {
80
- 'X-API-Key': apiKey,
81
- 'Content-Type': 'application/json',
82
- };
83
-
84
- const body = JSON.stringify({
85
- subdomain,
86
- version,
87
- fileCount,
88
- totalBytes,
89
- folderName,
90
- expiresAt,
91
- message,
92
- });
93
-
94
- if (apiSecret) {
95
- const timestamp = Date.now().toString();
96
- const endpoint = '/api/upload/complete';
97
- const hmac = createHmac('sha256', apiSecret);
98
- hmac.update('POST');
99
- hmac.update(endpoint);
100
- hmac.update(timestamp);
101
- hmac.update(body);
102
-
103
- headers['X-Timestamp'] = timestamp;
104
- headers['X-Signature'] = hmac.digest('hex');
83
+ async function completeUpload(
84
+ subdomain,
85
+ version,
86
+ fileCount,
87
+ totalBytes,
88
+ folderName,
89
+ expiresAt,
90
+ message
91
+ ) {
92
+ const apiKey = await getApiKey()
93
+ const apiSecret = await getApiSecret()
94
+ const headers = {
95
+ 'X-API-Key': apiKey,
96
+ 'Content-Type': 'application/json'
97
+ }
98
+
99
+ const body = JSON.stringify({
100
+ subdomain,
101
+ version,
102
+ fileCount,
103
+ totalBytes,
104
+ folderName,
105
+ expiresAt,
106
+ message,
107
+ cliVersion: config.version
108
+ })
109
+
110
+ if (apiSecret) {
111
+ const timestamp = Date.now().toString()
112
+ const endpoint = '/api/upload/complete'
113
+ const hmac = createHmac('sha256', apiSecret)
114
+ hmac.update('POST')
115
+ hmac.update(endpoint)
116
+ hmac.update(timestamp)
117
+ hmac.update(body)
118
+
119
+ headers['X-Timestamp'] = timestamp
120
+ headers['X-Signature'] = hmac.digest('hex')
121
+ }
122
+
123
+ const response = await fetch(`${API_BASE_URL}/api/upload/complete`, {
124
+ method: 'POST',
125
+ headers,
126
+ body
127
+ })
128
+
129
+ if (!response.ok) {
130
+ let errorMsg = 'Complete upload failed'
131
+ const text = await response.text()
132
+ try {
133
+ const data = JSON.parse(text)
134
+ errorMsg =
135
+ data.error ||
136
+ `Complete upload failed: ${response.status} ${response.statusText}`
137
+ } catch {
138
+ errorMsg = `Complete upload failed: ${response.status} ${response.statusText} - ${text.substring(0, 100)}`
105
139
  }
140
+ throw new Error(errorMsg)
141
+ }
106
142
 
107
- const response = await fetch(`${API_BASE_URL}/api/upload/complete`, {
108
- method: 'POST',
109
- headers,
110
- body,
111
- });
112
-
113
- if (!response.ok) {
114
- let errorMsg = 'Complete upload failed';
115
- const text = await response.text();
116
- try {
117
- const data = JSON.parse(text);
118
- errorMsg = data.error || `Complete upload failed: ${response.status} ${response.statusText}`;
119
- } catch {
120
- errorMsg = `Complete upload failed: ${response.status} ${response.statusText} - ${text.substring(0, 100)}`;
121
- }
122
- throw new Error(errorMsg);
123
- }
124
-
125
- return response.json();
143
+ return response.json()
126
144
  }
127
145
 
128
146
  /**
@@ -132,56 +150,64 @@ async function completeUpload(subdomain, version, fileCount, totalBytes, folderN
132
150
  * @param {number} version - Version number for this deployment
133
151
  * @param {function} onProgress - Progress callback (uploaded, total, fileName)
134
152
  */
135
- export async function uploadFolder(localPath, subdomain, version = 1, onProgress = null) {
136
- const files = await readdir(localPath, { recursive: true, withFileTypes: true });
137
-
138
- let uploaded = 0;
139
- let totalBytes = 0;
140
- const total = files.filter(f => f.isFile()).length;
141
-
142
- for (const file of files) {
143
- if (!file.isFile()) continue;
144
-
145
- const fileName = file.name;
146
- const parentDir = file.parentPath || file.path;
147
-
148
- // Skip ignored directories in the path
149
- const relativePath = relative(localPath, join(parentDir, fileName));
150
- const pathParts = relativePath.split(sep);
151
-
152
- if (pathParts.some(part => isIgnored(part, true))) {
153
- continue;
154
- }
153
+ export async function uploadFolder(
154
+ localPath,
155
+ subdomain,
156
+ version = 1,
157
+ onProgress = null
158
+ ) {
159
+ const files = await readdir(localPath, {
160
+ recursive: true,
161
+ withFileTypes: true
162
+ })
163
+
164
+ let uploaded = 0
165
+ let totalBytes = 0
166
+ const total = files.filter((f) => f.isFile()).length
167
+
168
+ for (const file of files) {
169
+ if (!file.isFile()) continue
170
+
171
+ const fileName = file.name
172
+ const parentDir = file.parentPath || file.path
173
+
174
+ // Skip ignored directories in the path
175
+ const relativePath = relative(localPath, join(parentDir, fileName))
176
+ const pathParts = relativePath.split(sep)
177
+
178
+ if (pathParts.some((part) => isIgnored(part, true))) {
179
+ continue
180
+ }
155
181
 
156
- // Skip ignored files
157
- if (isIgnored(fileName, false)) {
158
- continue;
159
- }
182
+ // Skip ignored files
183
+ if (isIgnored(fileName, false)) {
184
+ continue
185
+ }
160
186
 
161
- // Build full local path
162
- const fullPath = join(parentDir, fileName);
187
+ // Build full local path
188
+ const fullPath = join(parentDir, fileName)
163
189
 
164
- // Build relative path for R2 key
165
- const posixPath = toPosixPath(relativePath);
190
+ // Build relative path for R2 key
191
+ const posixPath = toPosixPath(relativePath)
166
192
 
167
- // Detect content type
168
- const contentType = mime.lookup(file.name) || 'application/octet-stream';
193
+ // Detect content type
194
+ const contentType = mime.lookup(file.name) || 'application/octet-stream'
169
195
 
170
- // Read file and upload via API
171
- const body = await readFile(fullPath);
172
- totalBytes += body.length;
196
+ // Read file and upload via API
197
+ const body = await readFile(fullPath)
198
+ totalBytes += body.length
173
199
 
174
- await uploadFile(body, subdomain, version, posixPath, contentType);
200
+ await uploadFile(body, subdomain, version, posixPath, contentType)
175
201
 
176
- uploaded++;
202
+ uploaded++
177
203
 
178
- // Call progress callback if provided
179
- if (onProgress) {
180
- onProgress(uploaded, total, posixPath);
181
- }
204
+ // Call progress callback if provided
205
+ if (onProgress) {
206
+ onProgress(uploaded, total, posixPath)
182
207
  }
208
+ }
183
209
 
184
- return { uploaded, subdomain, totalBytes };
210
+ return { uploaded, subdomain, totalBytes }
185
211
  }
186
212
 
187
213
  /**
@@ -193,6 +219,22 @@ export async function uploadFolder(localPath, subdomain, version = 1, onProgress
193
219
  * @param {string} folderName - Folder name
194
220
  * @param {string|null} expiresAt - Expiration ISO timestamp
195
221
  */
196
- export async function finalizeUpload(subdomain, version, fileCount, totalBytes, folderName, expiresAt = null, message = null) {
197
- return completeUpload(subdomain, version, fileCount, totalBytes, folderName, expiresAt, message);
222
+ export async function finalizeUpload(
223
+ subdomain,
224
+ version,
225
+ fileCount,
226
+ totalBytes,
227
+ folderName,
228
+ expiresAt = null,
229
+ message = null
230
+ ) {
231
+ return await completeUpload(
232
+ subdomain,
233
+ version,
234
+ fileCount,
235
+ totalBytes,
236
+ folderName,
237
+ expiresAt,
238
+ message
239
+ )
198
240
  }
@@ -1,90 +1,138 @@
1
- import { readdir } from 'node:fs/promises';
2
- import { extname } from 'node:path';
3
- import { isIgnored } from './ignore.js';
1
+ import { readdir } from 'node:fs/promises'
2
+ import { extname } from 'node:path'
3
+ import { isIgnored } from './ignore.js'
4
4
 
5
5
  // Allowed static file extensions
6
6
  const ALLOWED_EXTENSIONS = new Set([
7
- '.html', '.htm',
8
- '.css', '.scss', '.sass',
9
- '.js', '.mjs', '.cjs',
10
- '.json', '.jsonld',
11
- '.svg', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.avif',
12
- '.woff', '.woff2', '.ttf', '.otf', '.eot',
13
- '.mp4', '.webm', '.ogg', '.mp3', '.wav', '.flac',
14
- '.pdf', '.txt', '.md', '.xml', '.yaml', '.yml'
15
- ]);
7
+ '.html',
8
+ '.htm',
9
+ '.css',
10
+ '.scss',
11
+ '.sass',
12
+ '.js',
13
+ '.mjs',
14
+ '.cjs',
15
+ '.json',
16
+ '.jsonld',
17
+ '.svg',
18
+ '.png',
19
+ '.jpg',
20
+ '.jpeg',
21
+ '.gif',
22
+ '.webp',
23
+ '.ico',
24
+ '.avif',
25
+ '.woff',
26
+ '.woff2',
27
+ '.ttf',
28
+ '.otf',
29
+ '.eot',
30
+ '.mp4',
31
+ '.webm',
32
+ '.ogg',
33
+ '.mp3',
34
+ '.wav',
35
+ '.flac',
36
+ '.pdf',
37
+ '.txt',
38
+ '.md',
39
+ '.xml',
40
+ '.yaml',
41
+ '.yml'
42
+ ])
16
43
 
17
44
  // Forbidden indicators (frameworks, build tools, backend code)
18
- const FORBIDDEN_INDICATORS = [
19
- // Build systems & Frameworks
20
- 'package.json',
21
- 'package-lock.json',
22
- 'yarn.lock',
23
- 'pnpm-lock.yaml',
24
- 'node_modules',
25
- 'composer.json',
26
- 'vendor',
27
- 'requirements.txt',
28
- 'Gemfile',
29
- 'Makefile',
30
- 'tsconfig.json',
31
- 'next.config.js',
32
- 'nuxt.config.js',
33
- 'svelte.config.js',
34
- 'vite.config.js',
35
- 'webpack.config.js',
36
- 'rollup.config.js',
37
- 'angular.json',
45
+ const FORBIDDEN_INDICATORS = new Set([
46
+ // Build systems & Frameworks
47
+ 'package.json',
48
+ 'package-lock.json',
49
+ 'yarn.lock',
50
+ 'pnpm-lock.yaml',
51
+ 'node_modules',
52
+ 'composer.json',
53
+ 'vendor',
54
+ 'requirements.txt',
55
+ 'Gemfile',
56
+ 'Makefile',
57
+ 'tsconfig.json',
58
+ 'next.config.js',
59
+ 'nuxt.config.js',
60
+ 'svelte.config.js',
61
+ 'vite.config.js',
62
+ 'webpack.config.js',
63
+ 'rollup.config.js',
64
+ 'angular.json',
38
65
 
39
- // Backend/Source
40
- '.jsx', '.tsx', '.ts', '.vue', '.svelte', '.php', '.py', '.rb', '.go', '.rs', '.java', '.cs', '.cpp', '.c',
41
- '.env', '.env.local', '.env.production',
42
- '.dockerfile', 'docker-compose.yml',
66
+ // Backend/Source
67
+ '.jsx',
68
+ '.tsx',
69
+ '.ts',
70
+ '.vue',
71
+ '.svelte',
72
+ '.php',
73
+ '.py',
74
+ '.rb',
75
+ '.go',
76
+ '.rs',
77
+ '.java',
78
+ '.cs',
79
+ '.cpp',
80
+ '.c',
81
+ '.env',
82
+ '.env.local',
83
+ '.env.production',
84
+ '.dockerfile',
85
+ 'docker-compose.yml',
43
86
 
44
- // Hidden/System
45
- '.git', '.svn', '.hg'
46
- ];
87
+ // Hidden/System
88
+ '.git',
89
+ '.svn',
90
+ '.hg'
91
+ ])
47
92
 
48
93
  /**
49
94
  * Validates that a folder contains ONLY static files.
50
95
  * @param {string} folderPath
51
96
  * @returns {Promise<{success: boolean, violations: string[]}>}
52
97
  */
53
- export async function validateStaticOnly(folderPath) {
54
- const violations = [];
98
+ export async function validateStaticOnly (folderPath) {
99
+ const violations = []
55
100
 
56
- try {
57
- const files = await readdir(folderPath, { recursive: true, withFileTypes: true });
101
+ try {
102
+ const files = await readdir(folderPath, {
103
+ recursive: true,
104
+ withFileTypes: true
105
+ })
58
106
 
59
- for (const file of files) {
60
- const fileName = file.name.toLowerCase();
61
- const ext = extname(fileName);
107
+ for (const file of files) {
108
+ const fileName = file.name.toLowerCase()
109
+ const ext = extname(fileName)
62
110
 
63
- // 1. Check if the file/dir itself is a forbidden indicator
64
- if (FORBIDDEN_INDICATORS.includes(fileName) || FORBIDDEN_INDICATORS.includes(ext)) {
65
- violations.push(file.name);
66
- continue;
67
- }
111
+ // 1. Check if the file/dir itself is a forbidden indicator
112
+ if (FORBIDDEN_INDICATORS.has(fileName) || FORBIDDEN_INDICATORS.has(ext)) {
113
+ violations.push(file.name)
114
+ continue
115
+ }
68
116
 
69
- // 2. Skip ignored files and directories
70
- if (isIgnored(fileName, file.isDirectory())) {
71
- continue;
72
- }
117
+ // 2. Skip ignored files and directories
118
+ if (isIgnored(fileName, file.isDirectory())) {
119
+ continue
120
+ }
73
121
 
74
- // 2. Check extension for non-allowed types (only for files)
75
- if (file.isFile()) {
76
- // Ignore files without extensions or if they start with a dot (but handle indicators above)
77
- if (ext && !ALLOWED_EXTENSIONS.has(ext)) {
78
- violations.push(file.name);
79
- }
80
- }
122
+ // 2. Check extension for non-allowed types (only for files)
123
+ if (file.isFile()) {
124
+ // Ignore files without extensions or if they start with a dot (but handle indicators above)
125
+ if (ext && !ALLOWED_EXTENSIONS.has(ext)) {
126
+ violations.push(file.name)
81
127
  }
128
+ }
129
+ }
82
130
 
83
- return {
84
- success: violations.length === 0,
85
- violations: [...new Set(violations)] // Deduplicate
86
- };
87
- } catch (err) {
88
- throw new Error(`Failed to validate folder: ${err.message}`);
131
+ return {
132
+ success: violations.length === 0,
133
+ violations: [...new Set(violations)] // Deduplicate
89
134
  }
135
+ } catch (err) {
136
+ throw new Error(`Failed to validate folder: ${err.message}`)
137
+ }
90
138
  }