@tothalex/nulljs 0.0.43 → 0.0.46
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/package.json +1 -1
- package/scripts/install-server.js +83 -16
- package/src/lib/update-server.ts +96 -14
package/package.json
CHANGED
|
@@ -6,6 +6,12 @@ const path = require('path')
|
|
|
6
6
|
const { createWriteStream } = require('fs')
|
|
7
7
|
const tar = require('tar')
|
|
8
8
|
|
|
9
|
+
// --- Configuration ---
|
|
10
|
+
const S3_BASE_URL = 'https://nulljs.s3.eu-north-1.amazonaws.com'
|
|
11
|
+
const S3_PREFIX = 'releases/'
|
|
12
|
+
const DOWNLOAD_BASE_URL = `${S3_BASE_URL}/${S3_PREFIX}`
|
|
13
|
+
// ---------------------
|
|
14
|
+
|
|
9
15
|
function getPlatformInfo() {
|
|
10
16
|
const platform = process.platform
|
|
11
17
|
const arch = process.arch
|
|
@@ -32,6 +38,67 @@ function getPlatformInfo() {
|
|
|
32
38
|
return { target, extension, binaryName }
|
|
33
39
|
}
|
|
34
40
|
|
|
41
|
+
// Helper to fetch XML and return response data
|
|
42
|
+
async function fetchXml(url) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
https
|
|
45
|
+
.get(url, (res) => {
|
|
46
|
+
if (res.statusCode !== 200) {
|
|
47
|
+
reject(
|
|
48
|
+
new Error(`S3 Request Failed: ${res.statusCode}. Check if public listing is enabled.`)
|
|
49
|
+
)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
let data = ''
|
|
53
|
+
res.on('data', (chunk) => (data += chunk))
|
|
54
|
+
res.on('end', () => resolve(data))
|
|
55
|
+
})
|
|
56
|
+
.on('error', reject)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Lists the S3 prefix, parses the XML, and determines the latest version tag.
|
|
62
|
+
*/
|
|
63
|
+
async function getLatestVersionFromS3() {
|
|
64
|
+
// Query S3 for folders using delimiter and prefix
|
|
65
|
+
const listUrl = `${S3_BASE_URL}/?delimiter=/&prefix=${S3_PREFIX}`
|
|
66
|
+
const xmlData = await fetchXml(listUrl)
|
|
67
|
+
|
|
68
|
+
// Regex to find all CommonPrefixes entries (the version folders)
|
|
69
|
+
const prefixRegex = /<Prefix>(releases\/v[^<]+)\/<\/Prefix>/g
|
|
70
|
+
const versionFolders = []
|
|
71
|
+
let match
|
|
72
|
+
|
|
73
|
+
while ((match = prefixRegex.exec(xmlData)) !== null) {
|
|
74
|
+
// Extract version part: "releases/v1.0.0/" -> "v1.0.0"
|
|
75
|
+
const fullPrefix = match[1]
|
|
76
|
+
const version = fullPrefix.substring(S3_PREFIX.length)
|
|
77
|
+
versionFolders.push(version)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (versionFolders.length === 0) {
|
|
81
|
+
throw new Error('No version folders found in S3 bucket.')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Sort versions to find the latest (using semantic versioning logic)
|
|
85
|
+
const sortedVersions = versionFolders.sort((a, b) => {
|
|
86
|
+
// Strips 'v', splits by '.', and converts to number arrays for comparison
|
|
87
|
+
const aParts = a.replace('v', '').split('.').map(Number)
|
|
88
|
+
const bParts = b.replace('v', '').split('.').map(Number)
|
|
89
|
+
|
|
90
|
+
// Compare major, minor, and patch numbers
|
|
91
|
+
for (let i = 0; i < 3; i++) {
|
|
92
|
+
if (aParts[i] > bParts[i]) return 1
|
|
93
|
+
if (aParts[i] < bParts[i]) return -1
|
|
94
|
+
}
|
|
95
|
+
return 0
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// The latest version is the last one after sorting
|
|
99
|
+
return sortedVersions[sortedVersions.length - 1]
|
|
100
|
+
}
|
|
101
|
+
|
|
35
102
|
async function downloadFile(url, destination) {
|
|
36
103
|
return new Promise((resolve, reject) => {
|
|
37
104
|
const file = createWriteStream(destination)
|
|
@@ -70,12 +137,9 @@ async function extractArchive(archivePath, extractPath, binaryName) {
|
|
|
70
137
|
|
|
71
138
|
// Make binary executable
|
|
72
139
|
const binaryPath = path.join(extractPath, binaryName)
|
|
73
|
-
fs.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
async function getLatestReleaseUrl(target, extension) {
|
|
77
|
-
const baseUrl = 'https://nulljs.s3.eu-north-1.amazonaws.com/releases'
|
|
78
|
-
return `${baseUrl}/nulljs-server-${target}/nulljs-server-${target}${extension}`
|
|
140
|
+
if (fs.existsSync(binaryPath)) {
|
|
141
|
+
fs.chmodSync(binaryPath, '755')
|
|
142
|
+
}
|
|
79
143
|
}
|
|
80
144
|
|
|
81
145
|
async function installServer() {
|
|
@@ -83,7 +147,6 @@ async function installServer() {
|
|
|
83
147
|
console.log('📦 Installing nulljs server binary...')
|
|
84
148
|
|
|
85
149
|
const { target, extension, binaryName } = getPlatformInfo()
|
|
86
|
-
|
|
87
150
|
const binDir = path.join(__dirname, '..', 'bin')
|
|
88
151
|
const archivePath = path.join(binDir, `server${extension}`)
|
|
89
152
|
const binaryPath = path.join(binDir, 'server')
|
|
@@ -93,33 +156,38 @@ async function installServer() {
|
|
|
93
156
|
fs.mkdirSync(binDir, { recursive: true })
|
|
94
157
|
}
|
|
95
158
|
|
|
96
|
-
// Check if binary already exists
|
|
159
|
+
// Check if binary already exists (optional optimization)
|
|
97
160
|
if (fs.existsSync(binaryPath)) {
|
|
98
161
|
console.log('✅ Server binary already installed')
|
|
99
162
|
return
|
|
100
163
|
}
|
|
101
164
|
|
|
102
|
-
|
|
165
|
+
// 1. Determine Version from S3
|
|
166
|
+
console.log('🔍 Checking for latest version on S3...')
|
|
167
|
+
const version = await getLatestVersionFromS3()
|
|
168
|
+
console.log(` Found version: ${version}`)
|
|
103
169
|
|
|
104
|
-
//
|
|
105
|
-
|
|
170
|
+
// 2. Construct URL
|
|
171
|
+
// URL format: https://.../releases/VERSION/nulljs-server-TARGET.tar.gz
|
|
172
|
+
const downloadUrl = `${DOWNLOAD_BASE_URL}${version}/nulljs-server-${target}${extension}`
|
|
173
|
+
console.log(` Downloading from: ${downloadUrl}`)
|
|
106
174
|
|
|
107
|
-
// Download
|
|
175
|
+
// 3. Download
|
|
108
176
|
await downloadFile(downloadUrl, archivePath)
|
|
109
177
|
console.log('✅ Download completed')
|
|
110
178
|
|
|
111
|
-
// Extract
|
|
179
|
+
// 4. Extract
|
|
112
180
|
console.log('📂 Extracting binary...')
|
|
113
181
|
await extractArchive(archivePath, binDir, binaryName)
|
|
114
182
|
|
|
115
|
-
// Clean up archive
|
|
183
|
+
// 5. Clean up archive
|
|
116
184
|
fs.unlinkSync(archivePath)
|
|
117
185
|
|
|
118
186
|
console.log('✅ nulljs server installed successfully')
|
|
119
187
|
} catch (error) {
|
|
120
188
|
console.error('❌ Failed to install server binary:', error.message)
|
|
121
189
|
console.error('⚠️ You may need to build the server manually')
|
|
122
|
-
process.exit(0)
|
|
190
|
+
process.exit(0)
|
|
123
191
|
}
|
|
124
192
|
}
|
|
125
193
|
|
|
@@ -129,4 +197,3 @@ if (require.main === module) {
|
|
|
129
197
|
}
|
|
130
198
|
|
|
131
199
|
module.exports = { installServer }
|
|
132
|
-
|
package/src/lib/update-server.ts
CHANGED
|
@@ -5,12 +5,25 @@ import https from 'node:https'
|
|
|
5
5
|
import * as tar from 'tar'
|
|
6
6
|
import chalk from 'chalk'
|
|
7
7
|
|
|
8
|
+
// --- Configuration ---
|
|
9
|
+
const S3_BASE_URL = 'https://nulljs.s3.eu-north-1.amazonaws.com'
|
|
10
|
+
const S3_PREFIX = 'releases/'
|
|
11
|
+
const DOWNLOAD_BASE_URL = `${S3_BASE_URL}/${S3_PREFIX}`
|
|
12
|
+
// ---------------------
|
|
13
|
+
|
|
8
14
|
interface PlatformInfo {
|
|
9
15
|
target: string
|
|
10
16
|
extension: string
|
|
11
17
|
binaryName: string
|
|
12
18
|
}
|
|
13
19
|
|
|
20
|
+
interface S3DownloadInfo {
|
|
21
|
+
downloadUrl: string
|
|
22
|
+
version: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// --- Core Helper Functions (Unchanged) ---
|
|
26
|
+
|
|
14
27
|
function getPlatformInfo(): PlatformInfo {
|
|
15
28
|
const platform = process.platform
|
|
16
29
|
const arch = process.arch
|
|
@@ -82,19 +95,87 @@ async function extractArchive(
|
|
|
82
95
|
chmodSync(binaryPath, '755')
|
|
83
96
|
}
|
|
84
97
|
|
|
85
|
-
async function getLatestReleaseUrl(target: string, extension: string): Promise<string> {
|
|
86
|
-
const baseUrl = 'https://nulljs.s3.eu-north-1.amazonaws.com/releases'
|
|
87
|
-
return `${baseUrl}/nulljs-server-${target}/nulljs-server-${target}${extension}`
|
|
88
|
-
}
|
|
89
|
-
|
|
90
98
|
function getServerBinPath(): string {
|
|
91
99
|
// Try to find the server binary relative to this module
|
|
92
|
-
// This works both in development and when installed globally
|
|
93
100
|
const currentFile = fileURLToPath(import.meta.url)
|
|
94
101
|
const moduleRoot = join(dirname(currentFile), '../..')
|
|
95
102
|
return join(moduleRoot, 'bin', 'server')
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
// --- S3 LISTING Functions (New) ---
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Fetches the raw XML listing from S3.
|
|
109
|
+
*/
|
|
110
|
+
async function fetchXml(url: string): Promise<string> {
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
https
|
|
113
|
+
.get(url, (res) => {
|
|
114
|
+
if (res.statusCode !== 200) {
|
|
115
|
+
// This likely indicates a 403 Access Denied error
|
|
116
|
+
reject(
|
|
117
|
+
new Error(`S3 Request Failed (${res.statusCode}): Check public 's3:ListBucket' policy.`)
|
|
118
|
+
)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
let data = ''
|
|
122
|
+
res.on('data', (chunk) => (data += chunk))
|
|
123
|
+
res.on('end', () => resolve(data))
|
|
124
|
+
})
|
|
125
|
+
.on('error', reject)
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Lists the S3 prefix, parses the XML, and determines the latest version tag.
|
|
131
|
+
*/
|
|
132
|
+
async function getLatestVersionFromS3(target: string, extension: string): Promise<S3DownloadInfo> {
|
|
133
|
+
// Query S3 for folders using delimiter and prefix
|
|
134
|
+
const listUrl = `${S3_BASE_URL}/?delimiter=/&prefix=${S3_PREFIX}`
|
|
135
|
+
const xmlData = await fetchXml(listUrl)
|
|
136
|
+
|
|
137
|
+
// Regex to find all CommonPrefixes entries (the version folders)
|
|
138
|
+
// Example: <Prefix>releases/v1.0.0/</Prefix>
|
|
139
|
+
const prefixRegex = /<Prefix>(releases\/v[^<]+)\/<\/Prefix>/g
|
|
140
|
+
const versionFolders: string[] = []
|
|
141
|
+
let match: RegExpExecArray | null
|
|
142
|
+
|
|
143
|
+
while ((match = prefixRegex.exec(xmlData)) !== null) {
|
|
144
|
+
// Extract version part: "releases/v1.0.0/" -> "v1.0.0"
|
|
145
|
+
const fullPrefix = match[1]
|
|
146
|
+
const version = fullPrefix.substring(S3_PREFIX.length)
|
|
147
|
+
versionFolders.push(version)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (versionFolders.length === 0) {
|
|
151
|
+
throw new Error('No version folders found in S3 bucket. Has the CI/CD run?')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Sort versions to find the latest (using semantic versioning logic)
|
|
155
|
+
const sortedVersions = versionFolders.sort((a, b) => {
|
|
156
|
+
// Strips 'v', splits by '.', and converts to number arrays for comparison
|
|
157
|
+
const aParts = a.replace('v', '').split('.').map(Number)
|
|
158
|
+
const bParts = b.replace('v', '').split('.').map(Number)
|
|
159
|
+
|
|
160
|
+
// Compare major, minor, and patch numbers (assuming vX.Y.Z)
|
|
161
|
+
for (let i = 0; i < 3; i++) {
|
|
162
|
+
if (aParts[i] > bParts[i]) return 1
|
|
163
|
+
if (aParts[i] < bParts[i]) return -1
|
|
164
|
+
}
|
|
165
|
+
return 0
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// The latest version is the last one after sorting
|
|
169
|
+
const latestVersion = sortedVersions[sortedVersions.length - 1]
|
|
170
|
+
|
|
171
|
+
// Construct the full download URL
|
|
172
|
+
const downloadUrl = `${DOWNLOAD_BASE_URL}${latestVersion}/nulljs-server-${target}${extension}`
|
|
173
|
+
|
|
174
|
+
return { downloadUrl, version: latestVersion }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// --- Main Update Function (Modified) ---
|
|
178
|
+
|
|
98
179
|
export async function updateServer(): Promise<void> {
|
|
99
180
|
try {
|
|
100
181
|
console.log(chalk.blue('🔄 Updating nulljs server binary...'))
|
|
@@ -115,21 +196,21 @@ export async function updateServer(): Promise<void> {
|
|
|
115
196
|
if (existsSync(serverBinPath)) {
|
|
116
197
|
console.log(chalk.blue('💾 Backing up existing binary...'))
|
|
117
198
|
|
|
118
|
-
// Remove old backup if it exists
|
|
119
199
|
if (existsSync(backupPath)) {
|
|
120
200
|
unlinkSync(backupPath)
|
|
121
201
|
}
|
|
122
202
|
|
|
123
|
-
// Create backup
|
|
124
203
|
const fs = await import('node:fs/promises')
|
|
125
204
|
await fs.copyFile(serverBinPath, backupPath)
|
|
126
205
|
console.log(chalk.green('✅ Backup created'))
|
|
127
206
|
}
|
|
128
207
|
|
|
129
|
-
console.log(chalk.blue(`🔍
|
|
208
|
+
console.log(chalk.blue(`🔍 Searching for latest server version...`))
|
|
130
209
|
|
|
131
|
-
// Get download URL from
|
|
132
|
-
const downloadUrl = await
|
|
210
|
+
// 1. Get download URL and version from S3 listing (NEW LOGIC)
|
|
211
|
+
const { downloadUrl, version } = await getLatestVersionFromS3(target, extension)
|
|
212
|
+
console.log(chalk.blue(` Found latest version: ${version} for ${target}`))
|
|
213
|
+
console.log(chalk.blue(` Downloading from: ${downloadUrl}`))
|
|
133
214
|
|
|
134
215
|
try {
|
|
135
216
|
// Download the archive
|
|
@@ -157,7 +238,7 @@ export async function updateServer(): Promise<void> {
|
|
|
157
238
|
} catch (error) {
|
|
158
239
|
// If update failed and we have a backup, restore it
|
|
159
240
|
if (existsSync(backupPath)) {
|
|
160
|
-
console.log(chalk.yellow('⚠️
|
|
241
|
+
console.log(chalk.yellow('⚠️ Update failed, restoring backup...'))
|
|
161
242
|
|
|
162
243
|
if (existsSync(serverBinPath)) {
|
|
163
244
|
unlinkSync(serverBinPath)
|
|
@@ -173,8 +254,9 @@ export async function updateServer(): Promise<void> {
|
|
|
173
254
|
throw error
|
|
174
255
|
}
|
|
175
256
|
} catch (error) {
|
|
176
|
-
|
|
257
|
+
// Ensure the error message is available if it's not a standard Error object
|
|
258
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
259
|
+
console.error(chalk.red('❌ Failed to update server binary:'), errorMessage)
|
|
177
260
|
throw error
|
|
178
261
|
}
|
|
179
262
|
}
|
|
180
|
-
|