@jxrstudios/jxr 1.0.3 → 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.
Files changed (41) hide show
  1. package/README.md +6 -2
  2. package/bin/jxr.js +60 -0
  3. package/dist/deployer.d.ts +8 -12
  4. package/dist/deployer.d.ts.map +1 -1
  5. package/dist/deployer.js +69 -106
  6. package/dist/deployer.js.map +1 -1
  7. package/dist/enhanced-transpiler.d.ts +36 -0
  8. package/dist/enhanced-transpiler.d.ts.map +1 -0
  9. package/dist/enhanced-transpiler.js +272 -0
  10. package/dist/enhanced-transpiler.js.map +1 -0
  11. package/dist/entry-point-detection.d.ts +22 -0
  12. package/dist/entry-point-detection.d.ts.map +1 -0
  13. package/dist/entry-point-detection.js +415 -0
  14. package/dist/entry-point-detection.js.map +1 -0
  15. package/dist/index.d.ts +19 -12
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +2119 -16
  18. package/dist/index.js.map +1 -1
  19. package/dist/jxr-server-manager.d.ts +32 -0
  20. package/dist/jxr-server-manager.d.ts.map +1 -0
  21. package/dist/jxr-server-manager.js +353 -0
  22. package/dist/jxr-server-manager.js.map +1 -0
  23. package/dist/runtime.d.ts +9 -9
  24. package/dist/runtime.d.ts.map +1 -1
  25. package/dist/runtime.js +3 -3
  26. package/package.json +15 -4
  27. package/src/deployer.ts +231 -0
  28. package/src/enhanced-transpiler.ts +331 -0
  29. package/src/entry-point-detection.ts +470 -0
  30. package/src/index.ts +63 -0
  31. package/src/jxr-server-manager.ts +410 -0
  32. package/src/module-resolver.ts +520 -0
  33. package/src/moq-transport.ts +267 -0
  34. package/src/runtime.ts +188 -0
  35. package/src/web-crypto.ts +279 -0
  36. package/src/worker-pool.ts +321 -0
  37. package/zzz_react_template/App.tsx +160 -0
  38. package/zzz_react_template/index.css +16 -0
  39. package/zzz_react_template/index.html +12 -0
  40. package/zzz_react_template/main.tsx +10 -0
  41. package/zzz_react_template/package.json +25 -0
@@ -0,0 +1,231 @@
1
+ /**
2
+ * JXR.js — Wranglerless Deployer
3
+ * Deploy projects to JXR Studios' Cloudflare infrastructure without wrangler.
4
+ * Users only need a JXR API key - no Cloudflare account required.
5
+ */
6
+
7
+ import { readFile, readdir, stat } from "fs/promises";
8
+ import path from "path";
9
+ import { createWriteStream } from "fs";
10
+ import { spawn } from "child_process";
11
+
12
+ export interface DeployConfig {
13
+ projectId?: string;
14
+ environment?: 'production' | 'staging' | 'preview';
15
+ branch?: string;
16
+ }
17
+
18
+ export interface DeployResult {
19
+ success: boolean;
20
+ url: string;
21
+ deploymentId: string;
22
+ timestamp: string;
23
+ logs: string[];
24
+ }
25
+
26
+ export interface DeploymentStatus {
27
+ status: 'pending' | 'building' | 'deployed' | 'failed';
28
+ progress: number;
29
+ url?: string;
30
+ error?: string;
31
+ }
32
+
33
+ /**
34
+ * JXRDeployer — Deploy to JXR Cloudflare infrastructure
35
+ * No wrangler config needed. Just your JXR API key.
36
+ */
37
+ export class JXRDeployer {
38
+ private apiKey: string;
39
+ private projectId: string;
40
+
41
+ constructor(apiKey: string, projectId?: string) {
42
+ this.apiKey = apiKey;
43
+ this.projectId = projectId || this.generateProjectId();
44
+ }
45
+
46
+ /**
47
+ * Deploy the current project to JXR infrastructure
48
+ */
49
+ async deploy(projectPath: string, config: DeployConfig = {}): Promise<DeployResult> {
50
+ const env = config.environment || 'production';
51
+ const branch = config.branch || 'main';
52
+ const pid = config.projectId || this.projectId;
53
+
54
+ console.log(`🚀 Deploying to JXR ${env}...`);
55
+
56
+ const logs: string[] = [];
57
+
58
+ try {
59
+ // Verify project path exists
60
+ await stat(projectPath);
61
+ logs.push(`📁 Deploying from: ${projectPath}`);
62
+
63
+ // Create tarball of build files
64
+ const tarballPath = await this.createTarball(projectPath);
65
+ logs.push(`📦 Created tarball: ${tarballPath}`);
66
+
67
+ // Upload tarball
68
+ const uploadResult = await this.uploadTarball(tarballPath, pid, env, branch);
69
+ logs.push(`☁️ Uploaded to JXR`);
70
+
71
+ // Get deployment URL
72
+ const url = `https://${pid}.jxr.dev`;
73
+ logs.push(`🌐 Live at: ${url}`);
74
+
75
+ return {
76
+ success: true,
77
+ url,
78
+ deploymentId: uploadResult.deploymentId,
79
+ timestamp: new Date().toISOString(),
80
+ logs,
81
+ };
82
+
83
+ } catch (error) {
84
+ const errorMsg = error instanceof Error ? error.message : 'Unknown error';
85
+ logs.push(`❌ Error: ${errorMsg}`);
86
+ return {
87
+ success: false,
88
+ url: '',
89
+ deploymentId: '',
90
+ timestamp: new Date().toISOString(),
91
+ logs,
92
+ };
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Create a tarball of the build directory
98
+ */
99
+ private async createTarball(projectPath: string): Promise<string> {
100
+ const tarballPath = path.join(process.cwd(), '.jxr-deploy.tar.gz');
101
+
102
+ return new Promise((resolve, reject) => {
103
+ const tar = spawn('tar', ['-czf', tarballPath, '-C', projectPath, '.']);
104
+
105
+ tar.on('close', (code) => {
106
+ if (code === 0) {
107
+ resolve(tarballPath);
108
+ } else {
109
+ reject(new Error(`tar exited with code ${code}`));
110
+ }
111
+ });
112
+
113
+ tar.on('error', reject);
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Upload tarball to JXR API
119
+ */
120
+ private async uploadTarball(
121
+ tarballPath: string,
122
+ projectId: string,
123
+ environment: string,
124
+ branch: string
125
+ ): Promise<{ deploymentId: string }> {
126
+ const formData = new FormData();
127
+
128
+ const fileContent = await readFile(tarballPath);
129
+ const blob = new Blob([fileContent]);
130
+
131
+ formData.append('build', blob, 'build.tar.gz');
132
+ formData.append('projectId', projectId);
133
+ formData.append('environment', environment);
134
+ formData.append('branch', branch);
135
+
136
+ const response = await fetch('https://jxrstudios.workers.dev/v1/deployments', {
137
+ method: 'POST',
138
+ headers: {
139
+ 'Authorization': `Bearer ${this.apiKey}`,
140
+ },
141
+ body: formData,
142
+ });
143
+
144
+ if (!response.ok) {
145
+ throw new Error(`Upload failed: ${response.statusText}`);
146
+ }
147
+
148
+ const result = await response.json();
149
+ return { deploymentId: result.deploymentId };
150
+ }
151
+
152
+ /**
153
+ * Get deployment status
154
+ */
155
+ async getStatus(deploymentId: string): Promise<DeploymentStatus> {
156
+ const response = await fetch(`https://jxrstudios.workers.dev/v1/deployments/${deploymentId}`, {
157
+ headers: {
158
+ 'Authorization': `Bearer ${this.apiKey}`,
159
+ },
160
+ });
161
+
162
+ if (!response.ok) {
163
+ throw new Error(`Failed to get status: ${response.statusText}`);
164
+ }
165
+
166
+ return response.json();
167
+ }
168
+
169
+ /**
170
+ * List all deployments for a project
171
+ */
172
+ async listDeployments(projectId?: string): Promise<Array<{
173
+ id: string;
174
+ url: string;
175
+ environment: string;
176
+ createdAt: string;
177
+ status: string;
178
+ }>> {
179
+ const pid = projectId || this.projectId;
180
+
181
+ const response = await fetch(`https://jxrstudios.workers.dev/v1/projects/${pid}/deployments`, {
182
+ headers: {
183
+ 'Authorization': `Bearer ${this.apiKey}`,
184
+ },
185
+ });
186
+
187
+ if (!response.ok) {
188
+ throw new Error(`Failed to list deployments: ${response.statusText}`);
189
+ }
190
+
191
+ return response.json();
192
+ }
193
+
194
+ /**
195
+ * Rollback to a previous deployment
196
+ */
197
+ async rollback(deploymentId: string): Promise<DeployResult> {
198
+ const response = await fetch(`https://jxrstudios.workers.dev/v1/deployments/${deploymentId}/rollback`, {
199
+ method: 'POST',
200
+ headers: {
201
+ 'Authorization': `Bearer ${this.apiKey}`,
202
+ },
203
+ });
204
+
205
+ if (!response.ok) {
206
+ throw new Error(`Rollback failed: ${response.statusText}`);
207
+ }
208
+
209
+ const result = await response.json();
210
+
211
+ return {
212
+ success: true,
213
+ url: result.url,
214
+ deploymentId: result.deploymentId,
215
+ timestamp: new Date().toISOString(),
216
+ logs: ['Rollback successful'],
217
+ };
218
+ }
219
+
220
+ private generateProjectId(): string {
221
+ const timestamp = Date.now().toString(36);
222
+ const random = Math.random().toString(36).substring(2, 8);
223
+ return `jxr-${timestamp}-${random}`;
224
+ }
225
+ }
226
+
227
+ /** Global deployer singleton */
228
+ export const jxrDeployer = new JXRDeployer(
229
+ process.env.JXR_API_KEY || '',
230
+ process.env.JXR_PROJECT_ID
231
+ );
@@ -0,0 +1,331 @@
1
+ import * as Babel from "@babel/standalone"
2
+
3
+ export interface TranspilerOptions {
4
+ filename: string
5
+ presets?: string[]
6
+ plugins?: any[]
7
+ cache?: boolean
8
+ }
9
+
10
+ export interface TranspilationResult {
11
+ code: string
12
+ map?: any
13
+ error?: Error
14
+ cached?: boolean
15
+ }
16
+
17
+ export class EnhancedTranspiler {
18
+ private transformCache = new Map<string, TranspilationResult>()
19
+ private dependencyGraph = new Map<string, Set<string>>()
20
+ private options: Partial<TranspilerOptions>
21
+
22
+ constructor(options: Partial<TranspilerOptions> = {}) {
23
+ this.options = options
24
+ }
25
+
26
+ transpileTypeScript(code: string, filename: string, options: Partial<TranspilerOptions> = {}): TranspilationResult {
27
+ const mergedOptions = { ...this.options, ...options, filename }
28
+ const cacheKey = `${filename}:${code}:${JSON.stringify(mergedOptions)}`
29
+
30
+ if (mergedOptions.cache !== false && this.transformCache.has(cacheKey)) {
31
+ return { ...this.transformCache.get(cacheKey)!, cached: true }
32
+ }
33
+
34
+ try {
35
+ const result = Babel.transform(code, {
36
+ filename,
37
+ presets: [
38
+ ["react", { runtime: "automatic" }] as any,
39
+ "typescript",
40
+ ...(mergedOptions.presets || [])
41
+ ],
42
+ plugins: [
43
+ // Handle import/export transformations - pass filename for context
44
+ this.createImportTransformer(filename),
45
+ ...(mergedOptions.plugins || [])
46
+ ],
47
+ sourceMaps: true,
48
+ sourceFileName: filename
49
+ })
50
+
51
+ const transformed: TranspilationResult = {
52
+ code: result.code || code,
53
+ map: result.map,
54
+ cached: false
55
+ }
56
+
57
+ if (mergedOptions.cache !== false) {
58
+ this.transformCache.set(cacheKey, transformed)
59
+ }
60
+
61
+ return transformed
62
+ } catch (error) {
63
+ console.error("[EnhancedTranspiler] Transpilation error:", error)
64
+ return {
65
+ code,
66
+ error: error as Error,
67
+ cached: false
68
+ }
69
+ }
70
+ }
71
+
72
+ transpileJSX(code: string, filename: string, options: Partial<TranspilerOptions> = {}): TranspilationResult {
73
+ const mergedOptions = { ...this.options, ...options, filename }
74
+ const cacheKey = `jsx:${filename}:${code}:${JSON.stringify(mergedOptions)}`
75
+
76
+ if (mergedOptions.cache !== false && this.transformCache.has(cacheKey)) {
77
+ return { ...this.transformCache.get(cacheKey)!, cached: true }
78
+ }
79
+
80
+ try {
81
+ const result = Babel.transform(code, {
82
+ filename,
83
+ presets: [
84
+ ["react", { runtime: "automatic" }] as any,
85
+ ...(mergedOptions.presets || [])
86
+ ],
87
+ plugins: mergedOptions.plugins || [],
88
+ sourceMaps: true,
89
+ sourceFileName: filename
90
+ })
91
+
92
+ console.log('[v0] Transpiled JSX for', filename)
93
+ console.log('[v0] Output (first 500 chars):', result.code?.substring(0, 500))
94
+
95
+ const transformed: TranspilationResult = {
96
+ code: result.code || code,
97
+ map: result.map,
98
+ cached: false
99
+ }
100
+
101
+ if (mergedOptions.cache !== false) {
102
+ this.transformCache.set(cacheKey, transformed)
103
+ }
104
+
105
+ return transformed
106
+ } catch (error) {
107
+ console.error("[EnhancedTranspiler] JSX transformation error:", error)
108
+ return {
109
+ code,
110
+ error: error as Error,
111
+ cached: false
112
+ }
113
+ }
114
+ }
115
+
116
+ private createImportTransformer(filename: string) {
117
+ const self = this
118
+ return function importTransformer() {
119
+ return {
120
+ visitor: {
121
+ ImportDeclaration(path: any) {
122
+ const source = path.node.source.value
123
+
124
+ // Skip external packages
125
+ if (!source.startsWith('./') && !source.startsWith('../') && !source.startsWith('@/')) {
126
+ return
127
+ }
128
+
129
+ // Rewrite to absolute path that matches import map
130
+ const resolvedPath = self.resolveImportPath(filename, source)
131
+ if (resolvedPath) {
132
+ console.log(`[Transpiler] Rewriting: ${filename} imports "${source}" → "${resolvedPath}"`)
133
+ path.node.source.value = resolvedPath
134
+ }
135
+ },
136
+ ExportNamedDeclaration(path: any) {
137
+ if (path.node.source) {
138
+ const source = path.node.source.value
139
+
140
+ // Skip external packages
141
+ if (!source.startsWith('./') && !source.startsWith('../') && !source.startsWith('@/')) {
142
+ return
143
+ }
144
+
145
+ const resolvedPath = self.resolveImportPath(filename, source)
146
+ if (resolvedPath) {
147
+ console.log(`[Transpiler] Rewriting export: ${filename} exports from "${source}" → "${resolvedPath}"`)
148
+ path.node.source.value = resolvedPath
149
+ }
150
+ }
151
+ },
152
+ ExportAllDeclaration(path: any) {
153
+ if (path.node.source) {
154
+ const source = path.node.source.value
155
+
156
+ // Skip external packages
157
+ if (!source.startsWith('./') && !source.startsWith('../') && !source.startsWith('@/')) {
158
+ return
159
+ }
160
+
161
+ const resolvedPath = self.resolveImportPath(filename, source)
162
+ if (resolvedPath) {
163
+ console.log(`[Transpiler] Rewriting export all: ${filename} exports from "${source}" → "${resolvedPath}"`)
164
+ path.node.source.value = resolvedPath
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ private resolveImportPath(fromFile: string, importPath: string): string | null {
174
+ // Normalize fromFile - remove leading slash for consistent resolution
175
+ const normalizedFromFile = fromFile.startsWith('/') ? fromFile.substring(1) : fromFile
176
+
177
+ // Handle @/ alias
178
+ if (importPath.startsWith('@/')) {
179
+ // Convert @/components/Button → /src/components/Button (with leading slash)
180
+ return ('/src/' + importPath.substring(2)).replace(/\.(tsx|ts|jsx|js)$/, '')
181
+ }
182
+
183
+ // Handle relative imports
184
+ if (importPath.startsWith('./') || importPath.startsWith('../')) {
185
+ // Calculate absolute path from relative import
186
+ const fromDir = normalizedFromFile.split('/').slice(0, -1)
187
+ const importParts = importPath.split('/')
188
+
189
+ let currentPath = [...fromDir]
190
+ for (const part of importParts) {
191
+ if (part === '..') {
192
+ currentPath.pop()
193
+ } else if (part !== '.') {
194
+ currentPath.push(part)
195
+ }
196
+ }
197
+
198
+ // Return absolute path with leading slash and without extension
199
+ return '/' + currentPath.join('/').replace(/\.(tsx|ts|jsx|js)$/, '')
200
+ }
201
+
202
+ return null
203
+ }
204
+
205
+ // Track dependencies for cache invalidation
206
+ trackDependencies(filename: string, dependencies: string[]): void {
207
+ this.dependencyGraph.set(filename, new Set(dependencies))
208
+ }
209
+
210
+ // Invalidate cache entries that depend on changed files
211
+ invalidateCache(changedFiles: string[]): void {
212
+ const toInvalidate = new Set<string>()
213
+
214
+ // Find all entries that depend on changed files
215
+ for (const [file, deps] of this.dependencyGraph) {
216
+ for (const changedFile of changedFiles) {
217
+ if (deps.has(changedFile)) {
218
+ toInvalidate.add(file)
219
+ }
220
+ }
221
+ }
222
+
223
+ // Invalidate cache entries
224
+ for (const file of toInvalidate) {
225
+ for (const [key] of this.transformCache) {
226
+ if (key.startsWith(`${file}:`)) {
227
+ this.transformCache.delete(key)
228
+ }
229
+ }
230
+ }
231
+ }
232
+
233
+ // Clear all cache
234
+ clearCache(): void {
235
+ this.transformCache.clear()
236
+ this.dependencyGraph.clear()
237
+ }
238
+
239
+ invalidateFile(filename: string): void {
240
+ // Invalidate cache entries for this file
241
+ for (const key of this.transformCache.keys()) {
242
+ if (key.startsWith(filename)) {
243
+ this.transformCache.delete(key)
244
+ console.log('[Transpiler] Invalidated cache for', filename)
245
+ }
246
+ }
247
+
248
+ // Invalidate dependents
249
+ const dependents = this.dependencyGraph.get(filename)
250
+ if (dependents) {
251
+ for (const dependent of dependents) {
252
+ this.invalidateFile(dependent)
253
+ }
254
+ }
255
+ }
256
+
257
+ // Get cache statistics
258
+ getCacheStats(): { size: number; hitRate: number } {
259
+ return {
260
+ size: this.transformCache.size,
261
+ hitRate: 0 // Could be implemented with usage tracking
262
+ }
263
+ }
264
+
265
+ // Extract dependencies from code for tracking
266
+ extractDependencies(code: string): string[] {
267
+ const dependencies: string[] = []
268
+
269
+ try {
270
+ // Simple regex-based dependency extraction for browser compatibility
271
+ // Import statements: import ... from 'module'
272
+ const importRegex = /import\s+(?:[\w\s{},*]*\s+from\s+)?['"]([^'"]+)['"]/g
273
+ let match
274
+
275
+ while ((match = importRegex.exec(code)) !== null) {
276
+ dependencies.push(match[1])
277
+ }
278
+
279
+ // Export statements: export ... from 'module'
280
+ const exportRegex = /export\s+(?:[\w\s{},*]*\s+from\s+)?['"]([^'"]+)['"]/g
281
+
282
+ while ((match = exportRegex.exec(code)) !== null) {
283
+ dependencies.push(match[1])
284
+ }
285
+
286
+ // Dynamic imports: import('module')
287
+ const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g
288
+
289
+ while ((match = dynamicImportRegex.exec(code)) !== null) {
290
+ dependencies.push(match[1])
291
+ }
292
+
293
+ // Require statements: require('module')
294
+ const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
295
+
296
+ while ((match = requireRegex.exec(code)) !== null) {
297
+ dependencies.push(match[1])
298
+ }
299
+ } catch (error) {
300
+ console.warn('[EnhancedTranspiler] Failed to extract dependencies:', error)
301
+ }
302
+
303
+ return [...new Set(dependencies)] // Remove duplicates
304
+ }
305
+
306
+ // Validate transpiled code
307
+ validateTranspiledCode(code: string): { valid: boolean; errors: string[] } {
308
+ const errors: string[] = []
309
+
310
+ try {
311
+ // Basic syntax check
312
+ new Function(code)
313
+ } catch (error: any) {
314
+ errors.push(`Syntax error: ${error.message}`)
315
+ }
316
+
317
+ // Check for common issues
318
+ if (code.includes('undefined') && code.includes('import')) {
319
+ errors.push('Possible import resolution issue detected')
320
+ }
321
+
322
+ if (code.includes('require(') && !code.includes('module.exports')) {
323
+ errors.push('CommonJS require() detected in ES module')
324
+ }
325
+
326
+ return {
327
+ valid: errors.length === 0,
328
+ errors
329
+ }
330
+ }
331
+ }