launchpd 0.1.2 → 0.1.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.
Potentially problematic release.
This version of launchpd might be problematic. Click here for more details.
- package/LICENSE +4 -0
- package/README.md +1 -1
- package/bin/cli.js +5 -0
- package/bin/setup.js +18 -40
- package/package.json +3 -6
- package/src/commands/auth.js +95 -65
- package/src/commands/deploy.js +71 -38
- package/src/commands/list.js +37 -15
- package/src/commands/rollback.js +29 -12
- package/src/commands/versions.js +34 -10
- package/src/config.js +8 -30
- package/src/utils/logger.js +124 -1
- package/src/utils/metadata.js +96 -249
- package/src/utils/upload.js +94 -25
package/src/utils/upload.js
CHANGED
|
@@ -1,22 +1,15 @@
|
|
|
1
|
-
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
2
1
|
import { readdir, readFile } from 'node:fs/promises';
|
|
3
2
|
import { join, relative, posix, sep } from 'node:path';
|
|
4
3
|
import mime from 'mime-types';
|
|
5
4
|
import { config } from '../config.js';
|
|
6
|
-
|
|
5
|
+
|
|
6
|
+
const API_BASE_URL = `https://api.${config.domain}`;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Get API key for requests
|
|
10
10
|
*/
|
|
11
|
-
function
|
|
12
|
-
return
|
|
13
|
-
region: 'auto',
|
|
14
|
-
endpoint: `https://${config.r2.accountId}.r2.cloudflarestorage.com`,
|
|
15
|
-
credentials: {
|
|
16
|
-
accessKeyId: config.r2.accessKeyId,
|
|
17
|
-
secretAccessKey: config.r2.secretAccessKey,
|
|
18
|
-
},
|
|
19
|
-
});
|
|
11
|
+
function getApiKey() {
|
|
12
|
+
return process.env.STATICLAUNCH_API_KEY || 'public-beta-key';
|
|
20
13
|
}
|
|
21
14
|
|
|
22
15
|
/**
|
|
@@ -29,13 +22,77 @@ function toPosixPath(windowsPath) {
|
|
|
29
22
|
}
|
|
30
23
|
|
|
31
24
|
/**
|
|
32
|
-
* Upload a
|
|
25
|
+
* Upload a single file via API proxy
|
|
26
|
+
* @param {Buffer} content - File content
|
|
27
|
+
* @param {string} subdomain - Target subdomain
|
|
28
|
+
* @param {number} version - Version number
|
|
29
|
+
* @param {string} filePath - Relative file path
|
|
30
|
+
* @param {string} contentType - MIME type
|
|
31
|
+
*/
|
|
32
|
+
async function uploadFile(content, subdomain, version, filePath, contentType) {
|
|
33
|
+
const response = await fetch(`${API_BASE_URL}/api/upload/file`, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: {
|
|
36
|
+
'X-API-Key': getApiKey(),
|
|
37
|
+
'X-Subdomain': subdomain,
|
|
38
|
+
'X-Version': String(version),
|
|
39
|
+
'X-File-Path': filePath,
|
|
40
|
+
'X-Content-Type': contentType,
|
|
41
|
+
'Content-Type': 'application/octet-stream',
|
|
42
|
+
},
|
|
43
|
+
body: content,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
const error = await response.json().catch(() => ({ error: 'Upload failed' }));
|
|
48
|
+
throw new Error(error.error || `Upload failed: ${response.status}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return response.json();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Mark upload complete and set active version
|
|
56
|
+
* @param {string} subdomain - Target subdomain
|
|
57
|
+
* @param {number} version - Version number
|
|
58
|
+
* @param {number} fileCount - Number of files uploaded
|
|
59
|
+
* @param {number} totalBytes - Total bytes uploaded
|
|
60
|
+
* @param {string} folderName - Original folder name
|
|
61
|
+
* @param {string|null} expiresAt - ISO expiration timestamp
|
|
62
|
+
*/
|
|
63
|
+
async function completeUpload(subdomain, version, fileCount, totalBytes, folderName, expiresAt) {
|
|
64
|
+
const response = await fetch(`${API_BASE_URL}/api/upload/complete`, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: {
|
|
67
|
+
'X-API-Key': getApiKey(),
|
|
68
|
+
'Content-Type': 'application/json',
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
subdomain,
|
|
72
|
+
version,
|
|
73
|
+
fileCount,
|
|
74
|
+
totalBytes,
|
|
75
|
+
folderName,
|
|
76
|
+
expiresAt,
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
const error = await response.json().catch(() => ({ error: 'Complete upload failed' }));
|
|
82
|
+
throw new Error(error.error || `Complete upload failed: ${response.status}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return response.json();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Upload a folder to Launchpd via API proxy
|
|
33
90
|
* @param {string} localPath - Local folder path
|
|
34
91
|
* @param {string} subdomain - Subdomain to use as bucket prefix
|
|
35
92
|
* @param {number} version - Version number for this deployment
|
|
93
|
+
* @param {function} onProgress - Progress callback (uploaded, total, fileName)
|
|
36
94
|
*/
|
|
37
|
-
export async function uploadFolder(localPath, subdomain, version = 1) {
|
|
38
|
-
const client = createR2Client();
|
|
95
|
+
export async function uploadFolder(localPath, subdomain, version = 1, onProgress = null) {
|
|
39
96
|
const files = await readdir(localPath, { recursive: true, withFileTypes: true });
|
|
40
97
|
|
|
41
98
|
let uploaded = 0;
|
|
@@ -48,27 +105,39 @@ export async function uploadFolder(localPath, subdomain, version = 1) {
|
|
|
48
105
|
// Build full local path
|
|
49
106
|
const fullPath = join(file.parentPath || file.path, file.name);
|
|
50
107
|
|
|
51
|
-
// Build R2 key
|
|
108
|
+
// Build relative path for R2 key
|
|
52
109
|
const relativePath = relative(localPath, fullPath);
|
|
53
|
-
const
|
|
110
|
+
const posixPath = toPosixPath(relativePath);
|
|
54
111
|
|
|
55
112
|
// Detect content type
|
|
56
113
|
const contentType = mime.lookup(file.name) || 'application/octet-stream';
|
|
57
114
|
|
|
58
|
-
// Read file and upload
|
|
115
|
+
// Read file and upload via API
|
|
59
116
|
const body = await readFile(fullPath);
|
|
60
117
|
totalBytes += body.length;
|
|
61
118
|
|
|
62
|
-
await
|
|
63
|
-
Bucket: config.r2.bucketName,
|
|
64
|
-
Key: key,
|
|
65
|
-
Body: body,
|
|
66
|
-
ContentType: contentType,
|
|
67
|
-
}));
|
|
119
|
+
await uploadFile(body, subdomain, version, posixPath, contentType);
|
|
68
120
|
|
|
69
121
|
uploaded++;
|
|
70
|
-
|
|
122
|
+
|
|
123
|
+
// Call progress callback if provided
|
|
124
|
+
if (onProgress) {
|
|
125
|
+
onProgress(uploaded, total, posixPath);
|
|
126
|
+
}
|
|
71
127
|
}
|
|
72
128
|
|
|
73
129
|
return { uploaded, subdomain, totalBytes };
|
|
74
130
|
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Complete the upload and set active version
|
|
134
|
+
* @param {string} subdomain - Target subdomain
|
|
135
|
+
* @param {number} version - Version number
|
|
136
|
+
* @param {number} fileCount - Number of files
|
|
137
|
+
* @param {number} totalBytes - Total bytes
|
|
138
|
+
* @param {string} folderName - Folder name
|
|
139
|
+
* @param {string|null} expiresAt - Expiration ISO timestamp
|
|
140
|
+
*/
|
|
141
|
+
export async function finalizeUpload(subdomain, version, fileCount, totalBytes, folderName, expiresAt = null) {
|
|
142
|
+
return completeUpload(subdomain, version, fileCount, totalBytes, folderName, expiresAt);
|
|
143
|
+
}
|