@blimu/backend 0.7.0 → 1.1.0
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/README.md +129 -134
- package/dist/client.d.mts +24 -0
- package/dist/client.d.ts +15 -57
- package/dist/client.js +67 -157
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +48 -0
- package/dist/client.mjs.map +1 -0
- package/dist/index.d.mts +36 -0
- package/dist/index.d.ts +25 -26
- package/dist/index.js +1037 -83
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1005 -0
- package/dist/index.mjs.map +1 -0
- package/dist/main.d.mts +19 -0
- package/dist/main.d.ts +19 -0
- package/dist/main.js +1273 -0
- package/dist/main.js.map +1 -0
- package/dist/main.mjs +1264 -0
- package/dist/main.mjs.map +1 -0
- package/dist/schema-B1usIXCr.d.mts +424 -0
- package/dist/schema-B1usIXCr.d.ts +424 -0
- package/dist/schema.d.mts +2 -0
- package/dist/schema.d.ts +2 -381
- package/dist/schema.js +17 -2
- package/dist/schema.js.map +1 -1
- package/dist/schema.mjs +1 -0
- package/dist/schema.mjs.map +1 -0
- package/dist/schema.zod-CRNAHxbc.d.mts +444 -0
- package/dist/schema.zod-CRNAHxbc.d.ts +444 -0
- package/dist/schema.zod.d.mts +2 -0
- package/dist/schema.zod.d.ts +2 -0
- package/dist/schema.zod.js +562 -0
- package/dist/schema.zod.js.map +1 -0
- package/dist/schema.zod.mjs +496 -0
- package/dist/schema.zod.mjs.map +1 -0
- package/dist/services/bulk_resources.d.mts +12 -0
- package/dist/services/bulk_resources.d.ts +9 -4
- package/dist/services/bulk_resources.js +45 -18
- package/dist/services/bulk_resources.js.map +1 -1
- package/dist/services/bulk_resources.mjs +22 -0
- package/dist/services/bulk_resources.mjs.map +1 -0
- package/dist/services/bulk_roles.d.mts +12 -0
- package/dist/services/bulk_roles.d.ts +9 -4
- package/dist/services/bulk_roles.js +45 -18
- package/dist/services/bulk_roles.js.map +1 -1
- package/dist/services/bulk_roles.mjs +22 -0
- package/dist/services/bulk_roles.mjs.map +1 -0
- package/dist/services/entitlements.d.mts +14 -0
- package/dist/services/entitlements.d.ts +11 -6
- package/dist/services/entitlements.js +69 -34
- package/dist/services/entitlements.js.map +1 -1
- package/dist/services/entitlements.mjs +46 -0
- package/dist/services/entitlements.mjs.map +1 -0
- package/dist/services/plans.d.mts +14 -0
- package/dist/services/plans.d.ts +11 -6
- package/dist/services/plans.js +67 -32
- package/dist/services/plans.js.map +1 -1
- package/dist/services/plans.mjs +44 -0
- package/dist/services/plans.mjs.map +1 -0
- package/dist/services/resource_members.d.mts +12 -0
- package/dist/services/resource_members.d.ts +9 -4
- package/dist/services/resource_members.js +45 -17
- package/dist/services/resource_members.js.map +1 -1
- package/dist/services/resource_members.mjs +22 -0
- package/dist/services/resource_members.mjs.map +1 -0
- package/dist/services/resources.d.mts +16 -0
- package/dist/services/resources.d.ts +13 -8
- package/dist/services/resources.js +91 -49
- package/dist/services/resources.js.map +1 -1
- package/dist/services/resources.mjs +68 -0
- package/dist/services/resources.mjs.map +1 -0
- package/dist/services/roles.d.mts +14 -0
- package/dist/services/roles.d.ts +11 -6
- package/dist/services/roles.js +68 -33
- package/dist/services/roles.js.map +1 -1
- package/dist/services/roles.mjs +45 -0
- package/dist/services/roles.mjs.map +1 -0
- package/dist/services/usage.d.mts +16 -0
- package/dist/services/usage.d.ts +13 -8
- package/dist/services/usage.js +93 -52
- package/dist/services/usage.js.map +1 -1
- package/dist/services/usage.mjs +70 -0
- package/dist/services/usage.mjs.map +1 -0
- package/dist/services/users.d.mts +17 -0
- package/dist/services/users.d.ts +13 -8
- package/dist/services/users.js +102 -56
- package/dist/services/users.js.map +1 -1
- package/dist/services/users.mjs +79 -0
- package/dist/services/users.mjs.map +1 -0
- package/dist/token-verifier.d.mts +35 -0
- package/dist/token-verifier.d.ts +8 -6
- package/dist/token-verifier.js +225 -172
- package/dist/token-verifier.js.map +1 -1
- package/dist/token-verifier.mjs +201 -0
- package/dist/token-verifier.mjs.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils.d.mts +20 -0
- package/dist/utils.d.ts +7 -3
- package/dist/utils.js +50 -20
- package/dist/utils.js.map +1 -1
- package/dist/utils.mjs +29 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +37 -62
- package/src/client.ts +74 -0
- package/src/index.ts +55 -0
- package/src/main.ts +3 -0
- package/src/schema.ts +430 -0
- package/src/schema.zod.ts +558 -0
- package/src/services/bulk_resources.ts +24 -0
- package/src/services/bulk_roles.ts +22 -0
- package/src/services/entitlements.ts +58 -0
- package/src/services/plans.ts +57 -0
- package/src/services/resource_members.ts +25 -0
- package/src/services/resources.ts +91 -0
- package/src/services/roles.ts +58 -0
- package/src/services/usage.ts +93 -0
- package/src/services/users.ts +100 -0
- package/src/token-verifier.ts +280 -0
- package/src/utils.ts +56 -0
- package/bin/blimu +0 -0
- package/scripts/download-binary.js +0 -243
- package/scripts/postinstall.js +0 -24
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { FetchError } from 'client';
|
|
2
|
+
import * as crypto from 'crypto';
|
|
3
|
+
import * as jwt from 'jsonwebtoken';
|
|
4
|
+
|
|
5
|
+
export interface JWK {
|
|
6
|
+
kty: string;
|
|
7
|
+
use: string;
|
|
8
|
+
kid: string;
|
|
9
|
+
alg: string;
|
|
10
|
+
n: string;
|
|
11
|
+
e: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface JWKSet {
|
|
15
|
+
keys: JWK[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface CachedJWK {
|
|
19
|
+
key: crypto.KeyObject;
|
|
20
|
+
kid: string;
|
|
21
|
+
expiresAt: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface VerifyTokenOptions {
|
|
25
|
+
url?: string; // Direct URL to JWK endpoint (for custom scenarios)
|
|
26
|
+
secretKey?: string; // API key/secret key - uses runtimeApiUrl + JWK endpoint
|
|
27
|
+
token: string;
|
|
28
|
+
runtimeApiUrl?: string; // Optional override for runtime API URL
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TokenVerifierOptions {
|
|
32
|
+
runtimeApiUrl?: string; // Default from BLIMU_AUTH_API_URL env var
|
|
33
|
+
cacheTTL?: number; // Default: 1 hour
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class TokenVerifier {
|
|
37
|
+
private readonly cache = new Map<string, CachedJWK>();
|
|
38
|
+
private readonly cacheTTL: number;
|
|
39
|
+
private readonly runtimeApiUrl: string;
|
|
40
|
+
|
|
41
|
+
constructor(options?: TokenVerifierOptions) {
|
|
42
|
+
this.cacheTTL = options?.cacheTTL ?? 60 * 60 * 1000; // 1 hour
|
|
43
|
+
|
|
44
|
+
const blimuAuthApiUrl =
|
|
45
|
+
typeof process !== 'undefined' && process.env.BLIMU_AUTH_API_URL
|
|
46
|
+
? process.env.BLIMU_AUTH_API_URL
|
|
47
|
+
: undefined;
|
|
48
|
+
|
|
49
|
+
// if we have secretKey, we can call runtime-api directly, otherwise we need to use customer specific auth-api
|
|
50
|
+
this.runtimeApiUrl = blimuAuthApiUrl ?? 'https://api.blimu.dev';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Fetch JWK Set from runtime-api
|
|
55
|
+
*/
|
|
56
|
+
private async fetchJWKSet(
|
|
57
|
+
endpoint: string,
|
|
58
|
+
headers?: Record<string, string>
|
|
59
|
+
): Promise<JWKSet> {
|
|
60
|
+
console.log(`[TokenVerifier] 📡 Fetching JWK Set from: ${endpoint}`);
|
|
61
|
+
if (headers) {
|
|
62
|
+
console.log(
|
|
63
|
+
`[TokenVerifier] 📡 Request headers: ${JSON.stringify(Object.keys(headers).map((k) => `${k}: ${k === 'x-api-key' ? '***' : headers[k]}`))}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const response = await fetch(endpoint, {
|
|
68
|
+
method: 'GET',
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
...headers,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
console.log(
|
|
76
|
+
`[TokenVerifier] 📡 Response status: ${response.status} ${response.statusText}`
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const errorText = await response.text();
|
|
81
|
+
console.error(
|
|
82
|
+
`[TokenVerifier] ❌ Failed to fetch JWKs: ${response.status} ${errorText}`
|
|
83
|
+
);
|
|
84
|
+
throw new FetchError('Failed to fetch JWKs', response.status, errorText);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const jwkSet = (await response.json()) as JWKSet;
|
|
88
|
+
console.log(
|
|
89
|
+
`[TokenVerifier] ✅ Successfully fetched JWK Set with ${jwkSet.keys.length} keys`
|
|
90
|
+
);
|
|
91
|
+
return jwkSet;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Convert JWK to KeyObject
|
|
96
|
+
*/
|
|
97
|
+
private jwkToKeyObject(jwk: JWK): crypto.KeyObject {
|
|
98
|
+
return crypto.createPublicKey({
|
|
99
|
+
key: {
|
|
100
|
+
kty: jwk.kty,
|
|
101
|
+
n: jwk.n,
|
|
102
|
+
e: jwk.e,
|
|
103
|
+
alg: jwk.alg,
|
|
104
|
+
},
|
|
105
|
+
format: 'jwk',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get public key for a specific key ID
|
|
111
|
+
*/
|
|
112
|
+
private async getPublicKey(
|
|
113
|
+
kid: string,
|
|
114
|
+
cacheKey: string,
|
|
115
|
+
endpoint: string,
|
|
116
|
+
headers?: Record<string, string>
|
|
117
|
+
): Promise<crypto.KeyObject> {
|
|
118
|
+
// Check cache first
|
|
119
|
+
const cached = this.cache.get(cacheKey);
|
|
120
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
121
|
+
console.log(`[TokenVerifier] ✅ Using cached key for kid: ${kid}`);
|
|
122
|
+
return cached.key;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(
|
|
126
|
+
`[TokenVerifier] 🔍 Cache miss or expired. Fetching new key for kid: ${kid}`
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Fetch JWK Set
|
|
130
|
+
const jwkSet = await this.fetchJWKSet(endpoint, headers);
|
|
131
|
+
|
|
132
|
+
// Find the key with matching kid
|
|
133
|
+
const jwk = jwkSet.keys.find((k) => k.kid === kid);
|
|
134
|
+
if (!jwk) {
|
|
135
|
+
const availableKids = jwkSet.keys.map((k) => k.kid).join(', ');
|
|
136
|
+
console.error(
|
|
137
|
+
`[TokenVerifier] ❌ Key with kid '${kid}' not found in JWK Set. Available kids: ${availableKids}`
|
|
138
|
+
);
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Key with kid '${kid}' not found in JWK Set. Available kids: ${availableKids}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(`[TokenVerifier] ✅ Found key with kid: ${kid}`);
|
|
145
|
+
|
|
146
|
+
// Convert JWK to KeyObject
|
|
147
|
+
const keyObject = this.jwkToKeyObject(jwk);
|
|
148
|
+
|
|
149
|
+
// Cache the key
|
|
150
|
+
this.cache.set(cacheKey, {
|
|
151
|
+
key: keyObject,
|
|
152
|
+
kid,
|
|
153
|
+
expiresAt: Date.now() + this.cacheTTL,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return keyObject;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Verify JWT token using JWKs from runtime-api
|
|
161
|
+
*/
|
|
162
|
+
async verifyToken<T = any>(options: VerifyTokenOptions): Promise<T> {
|
|
163
|
+
const { url, secretKey, token, runtimeApiUrl } = options;
|
|
164
|
+
|
|
165
|
+
if (!url && !secretKey) {
|
|
166
|
+
throw new Error('Either url or secretKey must be provided');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (url && secretKey) {
|
|
170
|
+
throw new Error('Cannot provide both url and secretKey');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Decode token header to get kid (without verification)
|
|
174
|
+
const decoded = jwt.decode(token, { complete: true });
|
|
175
|
+
if (!decoded || typeof decoded === 'string') {
|
|
176
|
+
throw new Error('Invalid token format');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const header = decoded.header;
|
|
180
|
+
if (!header.kid) {
|
|
181
|
+
throw new Error('Token missing kid in header');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let endpoint: string;
|
|
185
|
+
let cacheKey: string;
|
|
186
|
+
let headers: Record<string, string> | undefined;
|
|
187
|
+
|
|
188
|
+
if (secretKey) {
|
|
189
|
+
// Use secretKey with runtimeApiUrl
|
|
190
|
+
const apiUrl = runtimeApiUrl ?? this.runtimeApiUrl;
|
|
191
|
+
endpoint = `${apiUrl}/v1/auth/.well-known/jwks.json`;
|
|
192
|
+
cacheKey = secretKey;
|
|
193
|
+
headers = {
|
|
194
|
+
'x-api-key': secretKey,
|
|
195
|
+
};
|
|
196
|
+
console.log(
|
|
197
|
+
`[TokenVerifier] 🔍 Verifying token with kid: ${header.kid}, endpoint: ${endpoint}`
|
|
198
|
+
);
|
|
199
|
+
} else {
|
|
200
|
+
// Use direct URL
|
|
201
|
+
endpoint = url!;
|
|
202
|
+
cacheKey = url!;
|
|
203
|
+
console.log(
|
|
204
|
+
`[TokenVerifier] 🔍 Verifying token with kid: ${header.kid}, endpoint: ${endpoint}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Get public key for this kid
|
|
209
|
+
let publicKey: crypto.KeyObject;
|
|
210
|
+
try {
|
|
211
|
+
publicKey = await this.getPublicKey(
|
|
212
|
+
header.kid,
|
|
213
|
+
cacheKey,
|
|
214
|
+
endpoint,
|
|
215
|
+
headers
|
|
216
|
+
);
|
|
217
|
+
console.log(
|
|
218
|
+
`[TokenVerifier] ✅ Successfully retrieved public key for kid: ${header.kid}`
|
|
219
|
+
);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.error(
|
|
222
|
+
`[TokenVerifier] ❌ Failed to get public key (first attempt): ${error instanceof Error ? error.message : String(error)}`
|
|
223
|
+
);
|
|
224
|
+
// If verification fails, clear cache and retry once (handles key rotation)
|
|
225
|
+
this.clearCache(cacheKey);
|
|
226
|
+
console.log(`[TokenVerifier] 🔄 Retrying after cache clear...`);
|
|
227
|
+
try {
|
|
228
|
+
publicKey = await this.getPublicKey(
|
|
229
|
+
header.kid,
|
|
230
|
+
cacheKey,
|
|
231
|
+
endpoint,
|
|
232
|
+
headers
|
|
233
|
+
);
|
|
234
|
+
console.log(
|
|
235
|
+
`[TokenVerifier] ✅ Successfully retrieved public key for kid: ${header.kid} (retry)`
|
|
236
|
+
);
|
|
237
|
+
} catch (retryError) {
|
|
238
|
+
console.error(
|
|
239
|
+
`[TokenVerifier] ❌ Failed to get public key (retry): ${retryError instanceof Error ? retryError.message : String(retryError)}`
|
|
240
|
+
);
|
|
241
|
+
throw retryError;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Verify token
|
|
246
|
+
try {
|
|
247
|
+
const payload = jwt.verify(token, publicKey, {
|
|
248
|
+
algorithms: ['RS256'],
|
|
249
|
+
}) as T;
|
|
250
|
+
console.log(`[TokenVerifier] ✅ Token verified successfully`);
|
|
251
|
+
return payload;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error(
|
|
254
|
+
`[TokenVerifier] ❌ JWT verification failed: ${error instanceof Error ? error.message : String(error)}`
|
|
255
|
+
);
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Clear cache (useful for testing or key rotation)
|
|
262
|
+
*/
|
|
263
|
+
clearCache(secretKeyOrUrl?: string): void {
|
|
264
|
+
if (secretKeyOrUrl) {
|
|
265
|
+
this.cache.delete(secretKeyOrUrl);
|
|
266
|
+
} else {
|
|
267
|
+
this.cache.clear();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Convenience function to verify a token
|
|
274
|
+
*/
|
|
275
|
+
export async function verifyToken<T = any>(
|
|
276
|
+
options: VerifyTokenOptions
|
|
277
|
+
): Promise<T> {
|
|
278
|
+
const verifier = new TokenVerifier();
|
|
279
|
+
return verifier.verifyToken<T>(options);
|
|
280
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { parseSSEStream, parseNDJSONStream } from '@blimu/fetch';
|
|
2
|
+
|
|
3
|
+
export type PaginableQuery = { limit?: number; offset?: number } & Record<
|
|
4
|
+
string,
|
|
5
|
+
unknown
|
|
6
|
+
>;
|
|
7
|
+
|
|
8
|
+
export async function* paginate<T>(
|
|
9
|
+
fetchPage: (
|
|
10
|
+
query?: any,
|
|
11
|
+
init?: Omit<RequestInit, 'method' | 'body'>
|
|
12
|
+
) => Promise<{
|
|
13
|
+
data?: T[];
|
|
14
|
+
hasMore?: boolean;
|
|
15
|
+
limit?: number;
|
|
16
|
+
offset?: number;
|
|
17
|
+
}>,
|
|
18
|
+
initialQuery: PaginableQuery = {},
|
|
19
|
+
pageSize = 100
|
|
20
|
+
): AsyncGenerator<T, void, unknown> {
|
|
21
|
+
let offset = Number(initialQuery.offset ?? 0);
|
|
22
|
+
const limit = Number(initialQuery.limit ?? pageSize);
|
|
23
|
+
// shallow copy to avoid mutating caller
|
|
24
|
+
const baseQuery: any = { ...initialQuery };
|
|
25
|
+
while (true) {
|
|
26
|
+
const page = await fetchPage({ ...baseQuery, limit, offset });
|
|
27
|
+
const items = page.data ?? [];
|
|
28
|
+
for (const item of items) {
|
|
29
|
+
yield item as T;
|
|
30
|
+
}
|
|
31
|
+
if (!page.hasMore || items.length < limit) break;
|
|
32
|
+
offset += limit;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function listAll<T>(
|
|
37
|
+
fetchPage: (
|
|
38
|
+
query?: any,
|
|
39
|
+
init?: Omit<RequestInit, 'method' | 'body'>
|
|
40
|
+
) => Promise<{
|
|
41
|
+
data?: T[];
|
|
42
|
+
hasMore?: boolean;
|
|
43
|
+
limit?: number;
|
|
44
|
+
offset?: number;
|
|
45
|
+
}>,
|
|
46
|
+
query: PaginableQuery = {},
|
|
47
|
+
pageSize = 100
|
|
48
|
+
): Promise<T[]> {
|
|
49
|
+
const out: T[] = [];
|
|
50
|
+
for await (const item of paginate<T>(fetchPage, query, pageSize))
|
|
51
|
+
out.push(item);
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Re-export streaming parsers from @blimu/fetch
|
|
56
|
+
export { parseSSEStream, parseNDJSONStream };
|
package/bin/blimu
DELETED
|
Binary file
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const https = require('https');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const { execSync } = require('child_process');
|
|
7
|
-
|
|
8
|
-
const BIN_DIR = path.join(__dirname, '..', 'bin');
|
|
9
|
-
const GITHUB_REPO = 'blimu-dev/blimu-cli';
|
|
10
|
-
|
|
11
|
-
// Platform mapping
|
|
12
|
-
const platformMap = {
|
|
13
|
-
darwin: 'darwin',
|
|
14
|
-
linux: 'linux',
|
|
15
|
-
win32: 'windows',
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// Architecture mapping
|
|
19
|
-
const archMap = {
|
|
20
|
-
x64: 'amd64',
|
|
21
|
-
arm64: 'arm64',
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
function getPlatformInfo() {
|
|
25
|
-
const platform = process.platform;
|
|
26
|
-
const arch = process.arch;
|
|
27
|
-
|
|
28
|
-
const goPlatform = platformMap[platform];
|
|
29
|
-
const goArch = archMap[arch];
|
|
30
|
-
|
|
31
|
-
if (!goPlatform || !goArch) {
|
|
32
|
-
throw new Error(
|
|
33
|
-
`Unsupported platform: ${platform}/${arch}. Supported platforms: darwin, linux, win32. Supported architectures: x64, arm64`,
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const binaryName = platform === 'win32' ? 'blimu.exe' : 'blimu';
|
|
38
|
-
const assetName = `blimu-${goPlatform}-${goArch}${platform === 'win32' ? '.exe' : ''}`;
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
platform: goPlatform,
|
|
42
|
-
arch: goArch,
|
|
43
|
-
binaryName,
|
|
44
|
-
assetName,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Verify that an existing binary matches the current platform/architecture
|
|
50
|
-
* Returns true if the binary is correct, false otherwise
|
|
51
|
-
*/
|
|
52
|
-
function verifyBinaryArchitecture(binaryPath) {
|
|
53
|
-
try {
|
|
54
|
-
// Use 'file' command to check binary type (available on macOS and Linux)
|
|
55
|
-
const output = execSync(`file "${binaryPath}"`, { encoding: 'utf8' });
|
|
56
|
-
|
|
57
|
-
const platform = process.platform;
|
|
58
|
-
const arch = process.arch;
|
|
59
|
-
|
|
60
|
-
if (platform === 'darwin') {
|
|
61
|
-
// macOS: should be "Mach-O 64-bit executable arm64" or "Mach-O 64-bit executable x86_64"
|
|
62
|
-
const expectedArch = arch === 'arm64' ? 'arm64' : 'x86_64';
|
|
63
|
-
if (!output.includes('Mach-O') || !output.includes(expectedArch)) {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
} else if (platform === 'linux') {
|
|
67
|
-
// Linux: should be "ELF 64-bit LSB executable, x86-64" or "ELF 64-bit LSB executable, ARM aarch64"
|
|
68
|
-
const expectedArch = arch === 'arm64' ? 'ARM aarch64' : 'x86-64';
|
|
69
|
-
if (!output.includes('ELF') || !output.includes(expectedArch)) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
} else if (platform === 'win32') {
|
|
73
|
-
// Windows: should be "PE32+ executable"
|
|
74
|
-
if (!output.includes('PE32+')) {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return true;
|
|
80
|
-
} catch (err) {
|
|
81
|
-
// If 'file' command fails or is not available, assume binary is incorrect to be safe
|
|
82
|
-
// This will trigger a re-download
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function downloadFile(url, dest) {
|
|
88
|
-
return new Promise((resolve, reject) => {
|
|
89
|
-
const file = fs.createWriteStream(dest);
|
|
90
|
-
https
|
|
91
|
-
.get(url, (response) => {
|
|
92
|
-
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
93
|
-
// Follow redirect
|
|
94
|
-
return downloadFile(response.headers.location, dest).then(resolve).catch(reject);
|
|
95
|
-
}
|
|
96
|
-
if (response.statusCode !== 200) {
|
|
97
|
-
file.close();
|
|
98
|
-
fs.unlinkSync(dest);
|
|
99
|
-
reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
response.pipe(file);
|
|
103
|
-
file.on('finish', () => {
|
|
104
|
-
file.close();
|
|
105
|
-
resolve();
|
|
106
|
-
});
|
|
107
|
-
})
|
|
108
|
-
.on('error', (err) => {
|
|
109
|
-
file.close();
|
|
110
|
-
if (fs.existsSync(dest)) {
|
|
111
|
-
fs.unlinkSync(dest);
|
|
112
|
-
}
|
|
113
|
-
reject(err);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function getReleaseUrl(version, assetName) {
|
|
119
|
-
return new Promise((resolve, reject) => {
|
|
120
|
-
// Always use latest release - version parameter is ignored
|
|
121
|
-
const tag = 'latest';
|
|
122
|
-
const url = `https://api.github.com/repos/${GITHUB_REPO}/releases/${tag}`;
|
|
123
|
-
|
|
124
|
-
https
|
|
125
|
-
.get(
|
|
126
|
-
url,
|
|
127
|
-
{
|
|
128
|
-
headers: {
|
|
129
|
-
'User-Agent': 'blimu-ts-installer',
|
|
130
|
-
Accept: 'application/vnd.github.v3+json',
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
(res) => {
|
|
134
|
-
let data = '';
|
|
135
|
-
res.on('data', (chunk) => {
|
|
136
|
-
data += chunk;
|
|
137
|
-
});
|
|
138
|
-
res.on('end', () => {
|
|
139
|
-
if (res.statusCode === 404) {
|
|
140
|
-
reject(
|
|
141
|
-
new Error(
|
|
142
|
-
`Latest CLI release not found. Please check the blimu-cli repository for available releases.`,
|
|
143
|
-
),
|
|
144
|
-
);
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
if (res.statusCode !== 200) {
|
|
148
|
-
reject(
|
|
149
|
-
new Error(`Failed to fetch latest release: ${res.statusCode} ${res.statusMessage}`),
|
|
150
|
-
);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
try {
|
|
154
|
-
const release = JSON.parse(data);
|
|
155
|
-
const asset = release.assets.find((a) => a.name === assetName);
|
|
156
|
-
if (!asset) {
|
|
157
|
-
reject(new Error(`Asset ${assetName} not found in latest release`));
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
resolve(asset.browser_download_url);
|
|
161
|
-
} catch (err) {
|
|
162
|
-
reject(err);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
},
|
|
166
|
-
)
|
|
167
|
-
.on('error', reject);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async function main(version) {
|
|
172
|
-
try {
|
|
173
|
-
// Always use latest version - version parameter is ignored for consistency
|
|
174
|
-
// This ensures users always get the latest CLI features and fixes
|
|
175
|
-
version = null;
|
|
176
|
-
|
|
177
|
-
const { binaryName, assetName } = getPlatformInfo();
|
|
178
|
-
|
|
179
|
-
// Create bin directory if it doesn't exist
|
|
180
|
-
if (!fs.existsSync(BIN_DIR)) {
|
|
181
|
-
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const binaryPath = path.join(BIN_DIR, binaryName);
|
|
185
|
-
|
|
186
|
-
// Check if binary already exists and is executable
|
|
187
|
-
if (fs.existsSync(binaryPath)) {
|
|
188
|
-
// Verify the binary matches the current platform/architecture
|
|
189
|
-
const isCorrectArchitecture = verifyBinaryArchitecture(binaryPath);
|
|
190
|
-
|
|
191
|
-
if (isCorrectArchitecture) {
|
|
192
|
-
try {
|
|
193
|
-
// Try to make it executable (Unix only)
|
|
194
|
-
if (process.platform !== 'win32') {
|
|
195
|
-
fs.chmodSync(binaryPath, 0o755);
|
|
196
|
-
}
|
|
197
|
-
console.log(`✅ Blimu CLI binary already exists at ${binaryPath}`);
|
|
198
|
-
return;
|
|
199
|
-
} catch (err) {
|
|
200
|
-
// If we can't check/update permissions, continue to download
|
|
201
|
-
}
|
|
202
|
-
} else {
|
|
203
|
-
// Binary exists but is for wrong platform/architecture - delete it
|
|
204
|
-
console.log(`⚠️ Existing binary is for wrong platform/architecture, will re-download...`);
|
|
205
|
-
try {
|
|
206
|
-
fs.unlinkSync(binaryPath);
|
|
207
|
-
} catch (err) {
|
|
208
|
-
console.warn(`Warning: Could not delete incorrect binary: ${err.message}`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
console.log(
|
|
214
|
-
`📥 Downloading Blimu CLI binary (latest) for ${process.platform}/${process.arch}...`,
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
// Get release download URL
|
|
218
|
-
const downloadUrl = await getReleaseUrl(version, assetName);
|
|
219
|
-
console.log(`🔗 Download URL: ${downloadUrl}`);
|
|
220
|
-
|
|
221
|
-
// Download the binary
|
|
222
|
-
await downloadFile(downloadUrl, binaryPath);
|
|
223
|
-
|
|
224
|
-
// Make binary executable (Unix only)
|
|
225
|
-
if (process.platform !== 'win32') {
|
|
226
|
-
fs.chmodSync(binaryPath, 0o755);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
console.log(`✅ Blimu CLI binary downloaded successfully to ${binaryPath}`);
|
|
230
|
-
} catch (error) {
|
|
231
|
-
console.error(`❌ Failed to download Blimu CLI binary: ${error.message}`);
|
|
232
|
-
console.error(`\nYou can manually install it by running:`);
|
|
233
|
-
console.error(` go install github.com/blimu-dev/blimu-cli/cmd/blimucli@latest`);
|
|
234
|
-
console.error(`\nOr download from: https://github.com/${GITHUB_REPO}/releases/latest`);
|
|
235
|
-
process.exit(1);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (require.main === module) {
|
|
240
|
-
main();
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
module.exports = { main, getPlatformInfo };
|
package/scripts/postinstall.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const { main } = require('./download-binary');
|
|
6
|
-
|
|
7
|
-
// Only run download if we're in a production install (not in development)
|
|
8
|
-
// Check if we're in node_modules (production install) or in the repo (development)
|
|
9
|
-
const isProductionInstall = __dirname.includes('node_modules');
|
|
10
|
-
|
|
11
|
-
if (isProductionInstall || process.env.BLIMU_DOWNLOAD_CLI !== 'false') {
|
|
12
|
-
// Always download the latest CLI version
|
|
13
|
-
main(null).catch((error) => {
|
|
14
|
-
// Fail the install if binary download fails
|
|
15
|
-
console.error('Error: Failed to download Blimu CLI binary during install.');
|
|
16
|
-
console.error(error.message);
|
|
17
|
-
console.error('\nThe package will download the latest available CLI version.');
|
|
18
|
-
console.error('\nYou can manually install it by running:');
|
|
19
|
-
console.error(' go install github.com/blimu-dev/blimu-cli/cmd/blimucli@latest');
|
|
20
|
-
process.exit(1);
|
|
21
|
-
});
|
|
22
|
-
} else {
|
|
23
|
-
console.log('Skipping Blimu CLI binary download (development mode)');
|
|
24
|
-
}
|