@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tothalex/nulljs",
3
3
  "module": "index.ts",
4
- "version": "0.0.43",
4
+ "version": "0.0.46",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "nulljs": "./src/index.ts"
@@ -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.chmodSync(binaryPath, '755')
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
- console.log(`🔍 Downloading server for ${target}...`)
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
- // Get download URL from latest release
105
- const downloadUrl = await getLatestReleaseUrl(target, extension)
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 the archive
175
+ // 3. Download
108
176
  await downloadFile(downloadUrl, archivePath)
109
177
  console.log('✅ Download completed')
110
178
 
111
- // Extract the binary
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) // Don't fail the installation
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
-
@@ -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(`🔍 Downloading latest server for ${target}...`))
208
+ console.log(chalk.blue(`🔍 Searching for latest server version...`))
130
209
 
131
- // Get download URL from latest release
132
- const downloadUrl = await getLatestReleaseUrl(target, extension)
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('⚠️ Update failed, restoring backup...'))
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
- console.error(chalk.red('❌ Failed to update server binary:'), error.message)
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
-