@portel/photon-core 2.17.6 → 2.18.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/dist/audit.d.ts +4 -0
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +107 -36
- package/dist/audit.js.map +1 -1
- package/dist/base.d.ts +81 -12
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +80 -7
- package/dist/base.js.map +1 -1
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +9 -1
- package/dist/compiler.js.map +1 -1
- package/dist/config.d.ts +14 -28
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +48 -46
- package/dist/config.js.map +1 -1
- package/dist/data-paths.d.ts +115 -0
- package/dist/data-paths.d.ts.map +1 -0
- package/dist/data-paths.js +243 -0
- package/dist/data-paths.js.map +1 -0
- package/dist/dependency-manager.d.ts +1 -1
- package/dist/dependency-manager.d.ts.map +1 -1
- package/dist/dependency-manager.js +13 -5
- package/dist/dependency-manager.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/instance-store.d.ts +22 -11
- package/dist/instance-store.d.ts.map +1 -1
- package/dist/instance-store.js +63 -28
- package/dist/instance-store.js.map +1 -1
- package/dist/memory.d.ts +8 -6
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +49 -35
- package/dist/memory.js.map +1 -1
- package/dist/mixins.d.ts.map +1 -1
- package/dist/mixins.js +22 -3
- package/dist/mixins.js.map +1 -1
- package/dist/path-resolver.d.ts +3 -1
- package/dist/path-resolver.d.ts.map +1 -1
- package/dist/path-resolver.js +49 -2
- package/dist/path-resolver.js.map +1 -1
- package/dist/photon-loader-lite.d.ts +2 -0
- package/dist/photon-loader-lite.d.ts.map +1 -1
- package/dist/photon-loader-lite.js +3 -3
- package/dist/photon-loader-lite.js.map +1 -1
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +11 -7
- package/dist/schedule.js.map +1 -1
- package/dist/schema-extractor.js +1 -1
- package/dist/schema-extractor.js.map +1 -1
- package/dist/stateful.d.ts +2 -1
- package/dist/stateful.d.ts.map +1 -1
- package/dist/stateful.js +4 -3
- package/dist/stateful.js.map +1 -1
- package/package.json +1 -1
- package/src/audit.ts +111 -38
- package/src/base.ts +117 -19
- package/src/compiler.ts +10 -1
- package/src/config.ts +59 -46
- package/src/data-paths.ts +289 -0
- package/src/dependency-manager.ts +13 -5
- package/src/index.ts +4 -0
- package/src/instance-store.ts +70 -30
- package/src/memory.ts +60 -38
- package/src/mixins.ts +24 -3
- package/src/path-resolver.ts +52 -2
- package/src/photon-loader-lite.ts +5 -3
- package/src/schedule.ts +11 -7
- package/src/schema-extractor.ts +1 -1
- package/src/stateful.ts +5 -2
package/src/audit.ts
CHANGED
|
@@ -19,9 +19,14 @@
|
|
|
19
19
|
|
|
20
20
|
import * as fs from 'fs';
|
|
21
21
|
import * as path from 'path';
|
|
22
|
-
import * as os from 'os';
|
|
23
22
|
import * as crypto from 'crypto';
|
|
24
23
|
|
|
24
|
+
import {
|
|
25
|
+
getPhotonLogsDir,
|
|
26
|
+
getLegacyLogsDir,
|
|
27
|
+
getDataRoot,
|
|
28
|
+
} from './data-paths.js';
|
|
29
|
+
|
|
25
30
|
/**
|
|
26
31
|
* A single execution record
|
|
27
32
|
*/
|
|
@@ -73,19 +78,25 @@ export function generateExecutionId(): string {
|
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
/**
|
|
76
|
-
* Get the logs directory for a photon
|
|
81
|
+
* Get the logs directory for a photon.
|
|
82
|
+
* New path: .data/{namespace}/{photonName}/logs/
|
|
83
|
+
* Falls back to legacy ~/.photon/logs/{photonId}/ for reads.
|
|
77
84
|
*/
|
|
78
|
-
function getLogDir(photonId: string): string {
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
85
|
+
function getLogDir(photonId: string, namespace?: string): string {
|
|
86
|
+
const ns = namespace || 'local';
|
|
87
|
+
const newDir = getPhotonLogsDir(ns, photonId);
|
|
88
|
+
if (!fs.existsSync(newDir)) {
|
|
89
|
+
const legacyDir = getLegacyLogsDir(photonId);
|
|
90
|
+
if (fs.existsSync(legacyDir)) return legacyDir;
|
|
91
|
+
}
|
|
92
|
+
return newDir;
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
/**
|
|
85
96
|
* Get the executions log file path
|
|
86
97
|
*/
|
|
87
|
-
function getLogPath(photonId: string): string {
|
|
88
|
-
return path.join(getLogDir(photonId), 'executions.jsonl');
|
|
98
|
+
function getLogPath(photonId: string, namespace?: string): string {
|
|
99
|
+
return path.join(getLogDir(photonId, namespace), 'executions.jsonl');
|
|
89
100
|
}
|
|
90
101
|
|
|
91
102
|
/**
|
|
@@ -234,16 +245,9 @@ export class AuditTrail {
|
|
|
234
245
|
return this.findInLog(getLogPath(photonId), executionId);
|
|
235
246
|
}
|
|
236
247
|
|
|
237
|
-
//
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const dirs = fs.readdirSync(baseDir, { withFileTypes: true })
|
|
242
|
-
.filter(d => d.isDirectory())
|
|
243
|
-
.map(d => d.name);
|
|
244
|
-
|
|
245
|
-
for (const dir of dirs) {
|
|
246
|
-
const found = this.findInLog(path.join(baseDir, dir, 'executions.jsonl'), executionId);
|
|
248
|
+
// Search all photon logs across namespaces in .data/
|
|
249
|
+
for (const logPath of this.allLogPaths()) {
|
|
250
|
+
const found = this.findInLog(logPath, executionId);
|
|
247
251
|
if (found) return found;
|
|
248
252
|
}
|
|
249
253
|
|
|
@@ -260,17 +264,8 @@ export class AuditTrail {
|
|
|
260
264
|
const result = [root];
|
|
261
265
|
|
|
262
266
|
// Find all children across all photon logs
|
|
263
|
-
const
|
|
264
|
-
if (!fs.existsSync(baseDir)) return result;
|
|
265
|
-
|
|
266
|
-
const dirs = fs.readdirSync(baseDir, { withFileTypes: true })
|
|
267
|
-
.filter(d => d.isDirectory())
|
|
268
|
-
.map(d => d.name);
|
|
269
|
-
|
|
270
|
-
for (const dir of dirs) {
|
|
271
|
-
const logPath = path.join(baseDir, dir, 'executions.jsonl');
|
|
267
|
+
for (const logPath of this.allLogPaths()) {
|
|
272
268
|
if (!fs.existsSync(logPath)) continue;
|
|
273
|
-
|
|
274
269
|
const content = fs.readFileSync(logPath, 'utf-8');
|
|
275
270
|
const lines = content.trim().split('\n').filter(Boolean);
|
|
276
271
|
|
|
@@ -294,13 +289,47 @@ export class AuditTrail {
|
|
|
294
289
|
* List all photons that have execution logs
|
|
295
290
|
*/
|
|
296
291
|
listPhotons(): string[] {
|
|
297
|
-
const
|
|
298
|
-
|
|
292
|
+
const results: string[] = [];
|
|
293
|
+
const dataRoot = getDataRoot();
|
|
294
|
+
if (!fs.existsSync(dataRoot)) return results;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
// Scan .data/{ns}/{photon}/logs/executions.jsonl
|
|
298
|
+
const nsDirs = fs.readdirSync(dataRoot, { withFileTypes: true })
|
|
299
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('_') && !e.name.startsWith('.'));
|
|
300
|
+
|
|
301
|
+
for (const nsDir of nsDirs) {
|
|
302
|
+
const nsPath = path.join(dataRoot, nsDir.name);
|
|
303
|
+
const photonDirs = fs.readdirSync(nsPath, { withFileTypes: true })
|
|
304
|
+
.filter(e => e.isDirectory());
|
|
305
|
+
|
|
306
|
+
for (const pDir of photonDirs) {
|
|
307
|
+
if (fs.existsSync(path.join(nsPath, pDir.name, 'logs', 'executions.jsonl'))) {
|
|
308
|
+
results.push(pDir.name);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
// Unreadable
|
|
314
|
+
}
|
|
299
315
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
.
|
|
316
|
+
// Also check legacy path
|
|
317
|
+
try {
|
|
318
|
+
const legacyDir = path.join(path.dirname(dataRoot), 'logs');
|
|
319
|
+
if (fs.existsSync(legacyDir)) {
|
|
320
|
+
const dirs = fs.readdirSync(legacyDir, { withFileTypes: true })
|
|
321
|
+
.filter(d => d.isDirectory())
|
|
322
|
+
.filter(d => fs.existsSync(path.join(legacyDir, d.name, 'executions.jsonl')))
|
|
323
|
+
.map(d => d.name);
|
|
324
|
+
for (const d of dirs) {
|
|
325
|
+
if (!results.includes(d)) results.push(d);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
// Legacy dir doesn't exist
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return results;
|
|
304
333
|
}
|
|
305
334
|
|
|
306
335
|
/**
|
|
@@ -366,10 +395,12 @@ export class AuditTrail {
|
|
|
366
395
|
return;
|
|
367
396
|
}
|
|
368
397
|
|
|
369
|
-
// Clear all
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
fs.
|
|
398
|
+
// Clear all — remove log dirs inside .data/{ns}/{photon}/logs/
|
|
399
|
+
for (const logPath of this.allLogPaths()) {
|
|
400
|
+
const logDir = path.dirname(logPath);
|
|
401
|
+
if (fs.existsSync(logDir)) {
|
|
402
|
+
fs.rmSync(logDir, { recursive: true, force: true });
|
|
403
|
+
}
|
|
373
404
|
}
|
|
374
405
|
}
|
|
375
406
|
|
|
@@ -402,6 +433,48 @@ export class AuditTrail {
|
|
|
402
433
|
return output;
|
|
403
434
|
}
|
|
404
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Collect all executions.jsonl paths across namespaces
|
|
438
|
+
*/
|
|
439
|
+
private allLogPaths(): string[] {
|
|
440
|
+
const paths: string[] = [];
|
|
441
|
+
const dataRoot = getDataRoot();
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
if (fs.existsSync(dataRoot)) {
|
|
445
|
+
const nsDirs = fs.readdirSync(dataRoot, { withFileTypes: true })
|
|
446
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('_') && !e.name.startsWith('.'));
|
|
447
|
+
|
|
448
|
+
for (const nsDir of nsDirs) {
|
|
449
|
+
const nsPath = path.join(dataRoot, nsDir.name);
|
|
450
|
+
try {
|
|
451
|
+
const photonDirs = fs.readdirSync(nsPath, { withFileTypes: true })
|
|
452
|
+
.filter(e => e.isDirectory());
|
|
453
|
+
for (const pDir of photonDirs) {
|
|
454
|
+
const logPath = path.join(nsPath, pDir.name, 'logs', 'executions.jsonl');
|
|
455
|
+
if (fs.existsSync(logPath)) paths.push(logPath);
|
|
456
|
+
}
|
|
457
|
+
} catch { /* skip unreadable ns dir */ }
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} catch { /* data root doesn't exist */ }
|
|
461
|
+
|
|
462
|
+
// Also check legacy
|
|
463
|
+
try {
|
|
464
|
+
const legacyDir = path.join(path.dirname(dataRoot), 'logs');
|
|
465
|
+
if (fs.existsSync(legacyDir)) {
|
|
466
|
+
const dirs = fs.readdirSync(legacyDir, { withFileTypes: true })
|
|
467
|
+
.filter(d => d.isDirectory());
|
|
468
|
+
for (const d of dirs) {
|
|
469
|
+
const logPath = path.join(legacyDir, d.name, 'executions.jsonl');
|
|
470
|
+
if (fs.existsSync(logPath)) paths.push(logPath);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
} catch { /* legacy doesn't exist */ }
|
|
474
|
+
|
|
475
|
+
return paths;
|
|
476
|
+
}
|
|
477
|
+
|
|
405
478
|
/**
|
|
406
479
|
* Find a record by ID in a specific log file
|
|
407
480
|
*/
|
package/src/base.ts
CHANGED
|
@@ -47,6 +47,7 @@ import { MemoryProvider } from './memory.js';
|
|
|
47
47
|
import { ScheduleProvider } from './schedule.js';
|
|
48
48
|
import * as path from 'path';
|
|
49
49
|
import * as fs from 'fs';
|
|
50
|
+
import { getPhotonDataDir } from './data-paths.js';
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
53
|
* Simple base class for creating Photons
|
|
@@ -63,6 +64,13 @@ export class Photon {
|
|
|
63
64
|
*/
|
|
64
65
|
_photonName?: string;
|
|
65
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Photon namespace (marketplace owner) - set by runtime loader
|
|
69
|
+
* Used for data path resolution: .data/{namespace}/{name}/
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
_photonNamespace?: string;
|
|
73
|
+
|
|
66
74
|
/**
|
|
67
75
|
* Absolute path to the .photon.ts/.photon.js source file - set by runtime loader
|
|
68
76
|
* Used for storage() and assets() path resolution
|
|
@@ -145,7 +153,7 @@ export class Photon {
|
|
|
145
153
|
.replace(/([A-Z])/g, '-$1')
|
|
146
154
|
.toLowerCase()
|
|
147
155
|
.replace(/^-/, '');
|
|
148
|
-
this._memory = new MemoryProvider(name, this._sessionId);
|
|
156
|
+
this._memory = new MemoryProvider(name, this._sessionId, this._photonNamespace);
|
|
149
157
|
}
|
|
150
158
|
return this._memory;
|
|
151
159
|
}
|
|
@@ -219,9 +227,9 @@ export class Photon {
|
|
|
219
227
|
'Ensure this photon is loaded through the standard runtime.'
|
|
220
228
|
);
|
|
221
229
|
}
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const target = path.join(
|
|
230
|
+
const name = this._photonName || path.basename(this._photonFilePath).replace(/\.photon\.(ts|js)$/, '');
|
|
231
|
+
const ns = this._photonNamespace || 'local';
|
|
232
|
+
const target = path.join(getPhotonDataDir(ns, name), subpath);
|
|
225
233
|
fs.mkdirSync(target, { recursive: true });
|
|
226
234
|
return target;
|
|
227
235
|
}
|
|
@@ -386,35 +394,125 @@ export class Photon {
|
|
|
386
394
|
* format — the same formats available via `@format` docblock tags. Each call
|
|
387
395
|
* replaces the previous render in the result panel.
|
|
388
396
|
*
|
|
397
|
+
* Also supports UI feedback formats: status, progress, toast.
|
|
389
398
|
* For custom formats, place an HTML renderer at `assets/formats/<name>.html`.
|
|
390
399
|
*
|
|
391
|
-
* @param format The format type (table, qr,
|
|
392
|
-
* @param value The data to render —
|
|
400
|
+
* @param format The format type (table, qr, status, progress, toast, guide, or custom)
|
|
401
|
+
* @param value The data to render — shape depends on format
|
|
393
402
|
*
|
|
394
403
|
* @example
|
|
395
404
|
* ```typescript
|
|
396
|
-
* //
|
|
397
|
-
* this.render('
|
|
398
|
-
*
|
|
399
|
-
*
|
|
405
|
+
* // Status message
|
|
406
|
+
* this.render('status', 'Connecting...');
|
|
407
|
+
* this.render('status', { message: 'Error!', type: 'error' });
|
|
408
|
+
*
|
|
409
|
+
* // Progress bar (0–1)
|
|
410
|
+
* this.render('progress', 0.5);
|
|
411
|
+
* this.render('progress', { value: 0.75, message: 'Almost done' });
|
|
412
|
+
*
|
|
413
|
+
* // Toast notification
|
|
414
|
+
* this.render('toast', 'Saved!');
|
|
415
|
+
* this.render('toast', { message: 'Done!', type: 'success' });
|
|
416
|
+
*
|
|
417
|
+
* // Multi-step guide
|
|
418
|
+
* this.render('guide', [
|
|
419
|
+
* { label: 'Create bot', status: 'done' },
|
|
420
|
+
* { label: 'Enter token', status: 'active' },
|
|
421
|
+
* { label: 'Connect', status: 'pending' },
|
|
422
|
+
* ]);
|
|
423
|
+
*
|
|
424
|
+
* // Formatted data
|
|
400
425
|
* this.render('table', [['Step', 'Status'], ['Auth', 'Done']]);
|
|
401
|
-
*
|
|
402
|
-
* // Composite dashboard
|
|
403
|
-
* this.render('dashboard', {
|
|
404
|
-
* qr: { format: 'qr', data: 'https://wa.link/...' },
|
|
405
|
-
* status: { format: 'text', data: 'Scan the QR code above' }
|
|
406
|
-
* });
|
|
426
|
+
* this.render('qr', { value: 'https://wa.link/...' });
|
|
407
427
|
* ```
|
|
408
428
|
*/
|
|
409
429
|
protected render(format: string, value: any): void;
|
|
410
430
|
protected render(): void;
|
|
411
431
|
protected render(format?: string, value?: any): void {
|
|
412
432
|
if (format === undefined) {
|
|
413
|
-
// Clear the render zone without rendering new content
|
|
414
433
|
this.emit({ emit: 'render:clear' });
|
|
415
|
-
|
|
416
|
-
this.emit({ emit: 'render', format, value });
|
|
434
|
+
return;
|
|
417
435
|
}
|
|
436
|
+
|
|
437
|
+
// UI feedback formats — emit native shapes the frontend already handles
|
|
438
|
+
switch (format) {
|
|
439
|
+
case 'status':
|
|
440
|
+
this.emit(typeof value === 'string'
|
|
441
|
+
? { emit: 'status', message: value }
|
|
442
|
+
: { emit: 'status', ...value });
|
|
443
|
+
return;
|
|
444
|
+
case 'progress':
|
|
445
|
+
this.emit(typeof value === 'number'
|
|
446
|
+
? { emit: 'progress', value }
|
|
447
|
+
: { emit: 'progress', ...value });
|
|
448
|
+
return;
|
|
449
|
+
case 'toast':
|
|
450
|
+
this.emit(typeof value === 'string'
|
|
451
|
+
? { emit: 'toast', message: value }
|
|
452
|
+
: { emit: 'toast', ...value });
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// All other formats — generic render
|
|
457
|
+
this.emit({ emit: 'render', format, value });
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Channel interface for communicating with connected clients (e.g. Claude Code).
|
|
462
|
+
* No-ops silently when the photon is not marked with @channel.
|
|
463
|
+
*
|
|
464
|
+
* Call directly to send a message:
|
|
465
|
+
* this.channel('Hello', { chat_id: '123' })
|
|
466
|
+
*
|
|
467
|
+
* Use .respond() to answer permission requests:
|
|
468
|
+
* this.channel.respond(request_id, 'allow')
|
|
469
|
+
*
|
|
470
|
+
* Use .onPermission() to handle incoming permission requests:
|
|
471
|
+
* this.channel.onPermission((req) => { ... })
|
|
472
|
+
*/
|
|
473
|
+
protected channel: {
|
|
474
|
+
(content: string, meta?: Record<string, string>): void;
|
|
475
|
+
respond(requestId: string, behavior: 'allow' | 'deny'): void;
|
|
476
|
+
onPermission(handler: (request: { request_id: string; tool_name: string; description: string; input_preview: string }) => void): void;
|
|
477
|
+
} = Object.assign(
|
|
478
|
+
(_content: string, _meta?: Record<string, string>) => {
|
|
479
|
+
// Injected by the loader — no-op by default
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
respond: (_requestId: string, _behavior: 'allow' | 'deny') => {
|
|
483
|
+
// Injected by the loader — no-op by default
|
|
484
|
+
},
|
|
485
|
+
onPermission: (_handler: (request: any) => void) => {
|
|
486
|
+
// Injected by the loader — no-op by default
|
|
487
|
+
},
|
|
488
|
+
}
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Create a blocking input request for use in generator methods.
|
|
493
|
+
*
|
|
494
|
+
* Returns a yield object — use with `yield` in async generators:
|
|
495
|
+
* ```typescript
|
|
496
|
+
* const name = yield this.ask('text', 'What is your name?');
|
|
497
|
+
* ```
|
|
498
|
+
*
|
|
499
|
+
* @param type Input type: text, password, confirm, select, number, file, date, form, url
|
|
500
|
+
* @param message The prompt message shown to the user
|
|
501
|
+
* @param options Type-specific options (placeholder, pattern, min/max, etc.)
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* ```typescript
|
|
505
|
+
* async *setup() {
|
|
506
|
+
* const token = yield this.ask('password', 'Enter API key:');
|
|
507
|
+
* const env = yield this.ask('select', 'Environment:', {
|
|
508
|
+
* options: ['dev', 'staging', 'prod']
|
|
509
|
+
* });
|
|
510
|
+
* const confirmed = yield this.ask('confirm', `Deploy to ${env}?`);
|
|
511
|
+
* }
|
|
512
|
+
* ```
|
|
513
|
+
*/
|
|
514
|
+
protected ask(type: string, message: string, options?: Record<string, any>): { ask: string; message: string; [key: string]: any } {
|
|
515
|
+
return { ask: type, message, ...options };
|
|
418
516
|
}
|
|
419
517
|
|
|
420
518
|
/**
|
package/src/compiler.ts
CHANGED
|
@@ -174,8 +174,17 @@ export async function compilePhotonTS(
|
|
|
174
174
|
// Ensure cache directory exists
|
|
175
175
|
await fs.mkdir(options.cacheDir, { recursive: true });
|
|
176
176
|
|
|
177
|
+
// Inject createRequire shim so CJS dependencies work in ESM context.
|
|
178
|
+
// This is needed for Node.js — Bun handles CJS/ESM interop natively.
|
|
179
|
+
const requireShim = `import { createRequire as __createRequire } from 'module';
|
|
180
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
181
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
182
|
+
const require = __createRequire(import.meta.url);
|
|
183
|
+
`;
|
|
184
|
+
const code = requireShim + result.code;
|
|
185
|
+
|
|
177
186
|
// Write compiled JavaScript
|
|
178
|
-
await fs.writeFile(cachedJsPath,
|
|
187
|
+
await fs.writeFile(cachedJsPath, code, 'utf-8');
|
|
179
188
|
|
|
180
189
|
return cachedJsPath;
|
|
181
190
|
}
|
package/src/config.ts
CHANGED
|
@@ -1,69 +1,73 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Photon Configuration Utilities
|
|
3
3
|
*
|
|
4
|
-
* Provides standard config storage for photons
|
|
5
|
-
* Config is stored at
|
|
4
|
+
* Provides standard config storage for photons.
|
|
5
|
+
* Config is stored at .data/{namespace}/{photonName}/config.json
|
|
6
6
|
*
|
|
7
7
|
* Usage in a Photon:
|
|
8
8
|
* ```typescript
|
|
9
|
-
* import { loadPhotonConfig, savePhotonConfig
|
|
9
|
+
* import { loadPhotonConfig, savePhotonConfig } from '@portel/photon-core';
|
|
10
10
|
*
|
|
11
11
|
* export default class MyPhoton extends Photon {
|
|
12
12
|
* async configure(params: { apiKey: string }) {
|
|
13
13
|
* savePhotonConfig('my-photon', params);
|
|
14
14
|
* return { success: true, config: params };
|
|
15
15
|
* }
|
|
16
|
-
*
|
|
17
|
-
* async getConfig() {
|
|
18
|
-
* return loadPhotonConfig('my-photon');
|
|
19
|
-
* }
|
|
20
16
|
* }
|
|
21
17
|
* ```
|
|
22
18
|
*/
|
|
23
19
|
|
|
24
20
|
import * as fs from 'fs';
|
|
25
21
|
import * as path from 'path';
|
|
26
|
-
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
getPhotonConfigPath as getNewConfigPath,
|
|
25
|
+
getLegacyPhotonConfigPath,
|
|
26
|
+
getDataRoot,
|
|
27
|
+
} from './data-paths.js';
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
|
-
* Get the config
|
|
30
|
-
*
|
|
30
|
+
* Get the config file path for a specific photon.
|
|
31
|
+
* Uses new .data/ layout, falls back to legacy path for reads.
|
|
31
32
|
*/
|
|
32
|
-
export function
|
|
33
|
-
|
|
33
|
+
export function getPhotonConfigPath(photonName: string, namespace?: string): string {
|
|
34
|
+
const ns = namespace || 'local';
|
|
35
|
+
const newPath = getNewConfigPath(ns, photonName);
|
|
36
|
+
|
|
37
|
+
// Fallback: check legacy path for existing config
|
|
38
|
+
if (!fs.existsSync(newPath)) {
|
|
39
|
+
const legacyPath = getLegacyPhotonConfigPath(photonName);
|
|
40
|
+
if (fs.existsSync(legacyPath)) return legacyPath;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return newPath;
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
/**
|
|
37
|
-
* Get the config
|
|
38
|
-
* @
|
|
39
|
-
* @returns Path to config.json for this photon
|
|
47
|
+
* Get the config directory for photons (legacy compat)
|
|
48
|
+
* @deprecated Use getPhotonConfigPath with namespace instead
|
|
40
49
|
*/
|
|
41
|
-
export function
|
|
42
|
-
|
|
43
|
-
return path.join(getPhotonConfigDir(), safeName, 'config.json');
|
|
50
|
+
export function getPhotonConfigDir(): string {
|
|
51
|
+
return process.env.PHOTON_CONFIG_DIR || getDataRoot();
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
/**
|
|
47
55
|
* Load configuration for a photon
|
|
48
|
-
* @param photonName The photon name (kebab-case)
|
|
49
|
-
* @param defaults Default values if config doesn't exist
|
|
50
|
-
* @returns The config object or defaults
|
|
51
56
|
*/
|
|
52
57
|
export function loadPhotonConfig<T extends Record<string, any>>(
|
|
53
58
|
photonName: string,
|
|
54
|
-
defaults?: T
|
|
59
|
+
defaults?: T,
|
|
60
|
+
namespace?: string
|
|
55
61
|
): T {
|
|
56
|
-
const configPath = getPhotonConfigPath(photonName);
|
|
62
|
+
const configPath = getPhotonConfigPath(photonName, namespace);
|
|
57
63
|
|
|
58
64
|
try {
|
|
59
65
|
if (fs.existsSync(configPath)) {
|
|
60
66
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
61
67
|
const config = JSON.parse(content);
|
|
62
|
-
// Merge with defaults
|
|
63
68
|
return defaults ? { ...defaults, ...config } : config;
|
|
64
69
|
}
|
|
65
70
|
} catch (error) {
|
|
66
|
-
// Log but don't throw - return defaults
|
|
67
71
|
if (process.env.PHOTON_DEBUG) {
|
|
68
72
|
console.error(`Failed to load config for ${photonName}:`, error);
|
|
69
73
|
}
|
|
@@ -73,18 +77,17 @@ export function loadPhotonConfig<T extends Record<string, any>>(
|
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
/**
|
|
76
|
-
* Save configuration for a photon
|
|
77
|
-
* @param photonName The photon name (kebab-case)
|
|
78
|
-
* @param config The configuration object to save
|
|
80
|
+
* Save configuration for a photon (always writes to new .data/ path)
|
|
79
81
|
*/
|
|
80
82
|
export function savePhotonConfig<T extends Record<string, any>>(
|
|
81
83
|
photonName: string,
|
|
82
|
-
config: T
|
|
84
|
+
config: T,
|
|
85
|
+
namespace?: string
|
|
83
86
|
): void {
|
|
84
|
-
const
|
|
87
|
+
const ns = namespace || 'local';
|
|
88
|
+
const configPath = getNewConfigPath(ns, photonName);
|
|
85
89
|
const configDir = path.dirname(configPath);
|
|
86
90
|
|
|
87
|
-
// Ensure directory exists
|
|
88
91
|
if (!fs.existsSync(configDir)) {
|
|
89
92
|
fs.mkdirSync(configDir, { recursive: true });
|
|
90
93
|
}
|
|
@@ -94,19 +97,16 @@ export function savePhotonConfig<T extends Record<string, any>>(
|
|
|
94
97
|
|
|
95
98
|
/**
|
|
96
99
|
* Check if a photon has been configured
|
|
97
|
-
* @param photonName The photon name (kebab-case)
|
|
98
|
-
* @returns true if config file exists
|
|
99
100
|
*/
|
|
100
|
-
export function hasPhotonConfig(photonName: string): boolean {
|
|
101
|
-
return fs.existsSync(getPhotonConfigPath(photonName));
|
|
101
|
+
export function hasPhotonConfig(photonName: string, namespace?: string): boolean {
|
|
102
|
+
return fs.existsSync(getPhotonConfigPath(photonName, namespace));
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
/**
|
|
105
106
|
* Delete configuration for a photon
|
|
106
|
-
* @param photonName The photon name (kebab-case)
|
|
107
107
|
*/
|
|
108
|
-
export function deletePhotonConfig(photonName: string): void {
|
|
109
|
-
const configPath = getPhotonConfigPath(photonName);
|
|
108
|
+
export function deletePhotonConfig(photonName: string, namespace?: string): void {
|
|
109
|
+
const configPath = getPhotonConfigPath(photonName, namespace);
|
|
110
110
|
if (fs.existsSync(configPath)) {
|
|
111
111
|
fs.unlinkSync(configPath);
|
|
112
112
|
}
|
|
@@ -114,21 +114,34 @@ export function deletePhotonConfig(photonName: string): void {
|
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
116
|
* List all configured photons
|
|
117
|
-
* @returns Array of photon names that have config
|
|
118
117
|
*/
|
|
119
118
|
export function listConfiguredPhotons(): string[] {
|
|
120
|
-
const
|
|
119
|
+
const dataRoot = getDataRoot();
|
|
121
120
|
|
|
122
|
-
if (!fs.existsSync(
|
|
121
|
+
if (!fs.existsSync(dataRoot)) {
|
|
123
122
|
return [];
|
|
124
123
|
}
|
|
125
124
|
|
|
125
|
+
const results: string[] = [];
|
|
126
126
|
try {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
.filter(
|
|
130
|
-
|
|
127
|
+
// Scan namespace directories inside .data/
|
|
128
|
+
const nsDirs = fs.readdirSync(dataRoot, { withFileTypes: true })
|
|
129
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('_') && !e.name.startsWith('.'));
|
|
130
|
+
|
|
131
|
+
for (const nsDir of nsDirs) {
|
|
132
|
+
const nsPath = path.join(dataRoot, nsDir.name);
|
|
133
|
+
const photonDirs = fs.readdirSync(nsPath, { withFileTypes: true })
|
|
134
|
+
.filter(e => e.isDirectory());
|
|
135
|
+
|
|
136
|
+
for (const pDir of photonDirs) {
|
|
137
|
+
if (fs.existsSync(path.join(nsPath, pDir.name, 'config.json'))) {
|
|
138
|
+
results.push(pDir.name);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
131
142
|
} catch {
|
|
132
|
-
|
|
143
|
+
// Data root doesn't exist or is unreadable
|
|
133
144
|
}
|
|
145
|
+
|
|
146
|
+
return results;
|
|
134
147
|
}
|