feat(smartdeno): Run small scripts in-memory via stdin, fall back to temp files for large scripts; remove internal HTTP script server and simplify plugin dependencies; update API and docs.
This commit is contained in:
11
changelog.md
11
changelog.md
@@ -1,5 +1,16 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-12-02 - 1.2.0 - feat(smartdeno)
|
||||||
|
Run small scripts in-memory via stdin, fall back to temp files for large scripts; remove internal HTTP script server and simplify plugin dependencies; update API and docs.
|
||||||
|
|
||||||
|
- Execute scripts < 2MB via stdin (deno run -) to support fully in-memory execution with no disk I/O.
|
||||||
|
- Automatically write scripts >= 2MB to a temp file under .nogit and clean up after execution.
|
||||||
|
- Removed internal HTTP script server implementation and related types; start() no longer starts a script server.
|
||||||
|
- Dropped plugin dependencies and exports related to @api.global/typedserver and @push.rocks/lik; plugins.ts simplified to only include necessary push.rocks modules and node path.
|
||||||
|
- Updated package.json to remove unused dependencies and adjust keywords to reflect in-memory execution.
|
||||||
|
- Updated README to document new start() behavior, in-memory execution, temp-file fallback, and simplified API signatures (start/stop/isRunning/executeScript).
|
||||||
|
- ts index now only exports SmartDeno (removed direct type export of TDenoPermission from separate file).
|
||||||
|
|
||||||
## 2025-12-02 - 1.1.0 - feat(core)
|
## 2025-12-02 - 1.1.0 - feat(core)
|
||||||
Add permission-controlled Deno execution, configurable script server port, improved downloader, dependency bumps and test updates
|
Add permission-controlled Deno execution, configurable script server port, improved downloader, dependency bumps and test updates
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,6 @@
|
|||||||
"@types/node": "^24.10.1"
|
"@types/node": "^24.10.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@api.global/typedserver": "^4.0.0",
|
|
||||||
"@push.rocks/lik": "^6.2.2",
|
|
||||||
"@push.rocks/smartarchive": "^5.0.1",
|
"@push.rocks/smartarchive": "^5.0.1",
|
||||||
"@push.rocks/smartfs": "^1.2.0",
|
"@push.rocks/smartfs": "^1.2.0",
|
||||||
"@push.rocks/smartpath": "^6.0.0",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
@@ -59,9 +57,9 @@
|
|||||||
"Development Tools",
|
"Development Tools",
|
||||||
"Deno Download",
|
"Deno Download",
|
||||||
"Code Execution",
|
"Code Execution",
|
||||||
"Scripting Server",
|
|
||||||
"Ephemeral Execution",
|
"Ephemeral Execution",
|
||||||
"Cross-platform"
|
"Cross-platform",
|
||||||
|
"In-memory"
|
||||||
],
|
],
|
||||||
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34"
|
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34"
|
||||||
}
|
}
|
||||||
|
|||||||
69
pnpm-lock.yaml
generated
69
pnpm-lock.yaml
generated
@@ -8,12 +8,6 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedserver':
|
|
||||||
specifier: ^4.0.0
|
|
||||||
version: 4.0.0
|
|
||||||
'@push.rocks/lik':
|
|
||||||
specifier: ^6.2.2
|
|
||||||
version: 6.2.2
|
|
||||||
'@push.rocks/smartarchive':
|
'@push.rocks/smartarchive':
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.0.1
|
version: 5.0.1
|
||||||
@@ -57,9 +51,6 @@ packages:
|
|||||||
'@api.global/typedserver@3.0.80':
|
'@api.global/typedserver@3.0.80':
|
||||||
resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==}
|
resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==}
|
||||||
|
|
||||||
'@api.global/typedserver@4.0.0':
|
|
||||||
resolution: {integrity: sha512-FfAeVcGnVJdjDae9ryVZZZqtegk/N4Mbq+IIGVGJ/lCfNiwtXqBHXhsiLhVvz0Buja0swLnO6pF1DOS8cxzBzw==}
|
|
||||||
|
|
||||||
'@api.global/typedsocket@3.0.1':
|
'@api.global/typedsocket@3.0.1':
|
||||||
resolution: {integrity: sha512-xojiAVNXtHoxkpBo8U2HHJG8FrVXXuLvDNndSHXwx4C9VslUwDn5zSCI+PdBl8iAg+ZuBmKjqkpZZ9sL6DC5yQ==}
|
resolution: {integrity: sha512-xojiAVNXtHoxkpBo8U2HHJG8FrVXXuLvDNndSHXwx4C9VslUwDn5zSCI+PdBl8iAg+ZuBmKjqkpZZ9sL6DC5yQ==}
|
||||||
|
|
||||||
@@ -769,10 +760,6 @@ packages:
|
|||||||
'@push.rocks/smartversion@3.0.5':
|
'@push.rocks/smartversion@3.0.5':
|
||||||
resolution: {integrity: sha512-8MZSo1yqyaKxKq0Q5N188l4un++9GFWVbhCAX5mXJwewZHn97ujffTeL+eOQYpWFTEpUhaq1QhL4NhqObBCt1Q==}
|
resolution: {integrity: sha512-8MZSo1yqyaKxKq0Q5N188l4un++9GFWVbhCAX5mXJwewZHn97ujffTeL+eOQYpWFTEpUhaq1QhL4NhqObBCt1Q==}
|
||||||
|
|
||||||
'@push.rocks/smartwatch@5.0.0':
|
|
||||||
resolution: {integrity: sha512-uuWUlTo0l5LWOWoOuTMG7zzxpUNKBcyqoB+zyQ24NHTtSYNcaUJtaQzTO2gxMXr5sqiZDkohlThS0KvsBc3g7w==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
|
|
||||||
'@push.rocks/smartxml@2.0.0':
|
'@push.rocks/smartxml@2.0.0':
|
||||||
resolution: {integrity: sha512-1d06zYJX4Zt8s5w5qFOUg2LAEz9ykrh9d6CQPK4WAgOBIefb1xzVEWHc7yoxicc2OkzNgC3IBCEg3s6BncZKWw==}
|
resolution: {integrity: sha512-1d06zYJX4Zt8s5w5qFOUg2LAEz9ykrh9d6CQPK4WAgOBIefb1xzVEWHc7yoxicc2OkzNgC3IBCEg3s6BncZKWw==}
|
||||||
|
|
||||||
@@ -3385,54 +3372,6 @@ snapshots:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@api.global/typedserver@4.0.0':
|
|
||||||
dependencies:
|
|
||||||
'@api.global/typedrequest': 3.1.10
|
|
||||||
'@api.global/typedrequest-interfaces': 3.0.19
|
|
||||||
'@api.global/typedsocket': 3.0.1
|
|
||||||
'@cloudflare/workers-types': 4.20251202.0
|
|
||||||
'@design.estate/dees-comms': 1.0.27
|
|
||||||
'@push.rocks/lik': 6.2.2
|
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
|
||||||
'@push.rocks/smartenv': 6.0.0
|
|
||||||
'@push.rocks/smartfeed': 1.4.0
|
|
||||||
'@push.rocks/smartfile': 13.1.0
|
|
||||||
'@push.rocks/smartfs': 1.2.0
|
|
||||||
'@push.rocks/smartjson': 5.2.0
|
|
||||||
'@push.rocks/smartlog': 3.1.10
|
|
||||||
'@push.rocks/smartlog-destination-devtools': 1.0.12
|
|
||||||
'@push.rocks/smartlog-interfaces': 3.0.2
|
|
||||||
'@push.rocks/smartmanifest': 2.0.2
|
|
||||||
'@push.rocks/smartmatch': 2.0.0
|
|
||||||
'@push.rocks/smartmime': 2.0.4
|
|
||||||
'@push.rocks/smartntml': 2.0.8
|
|
||||||
'@push.rocks/smartopen': 2.0.0
|
|
||||||
'@push.rocks/smartpath': 6.0.0
|
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
|
||||||
'@push.rocks/smartrequest': 5.0.1
|
|
||||||
'@push.rocks/smartrx': 3.0.10
|
|
||||||
'@push.rocks/smartsitemap': 2.0.4
|
|
||||||
'@push.rocks/smartstream': 3.2.5
|
|
||||||
'@push.rocks/smarttime': 4.1.1
|
|
||||||
'@push.rocks/smartwatch': 5.0.0
|
|
||||||
'@push.rocks/taskbuffer': 3.4.0
|
|
||||||
'@push.rocks/webrequest': 4.0.1
|
|
||||||
'@push.rocks/webstore': 2.0.20
|
|
||||||
'@tsclass/tsclass': 9.3.0
|
|
||||||
'@types/express': 5.0.6
|
|
||||||
body-parser: 2.2.1
|
|
||||||
cors: 2.8.5
|
|
||||||
express: 5.2.1
|
|
||||||
express-force-ssl: 0.3.2
|
|
||||||
lit: 3.3.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@nuxt/kit'
|
|
||||||
- bufferutil
|
|
||||||
- react
|
|
||||||
- supports-color
|
|
||||||
- utf-8-validate
|
|
||||||
- vue
|
|
||||||
|
|
||||||
'@api.global/typedsocket@3.0.1':
|
'@api.global/typedsocket@3.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedrequest': 3.1.10
|
'@api.global/typedrequest': 3.1.10
|
||||||
@@ -5067,14 +5006,6 @@ snapshots:
|
|||||||
'@types/semver': 7.7.1
|
'@types/semver': 7.7.1
|
||||||
semver: 7.7.3
|
semver: 7.7.3
|
||||||
|
|
||||||
'@push.rocks/smartwatch@5.0.0':
|
|
||||||
dependencies:
|
|
||||||
'@push.rocks/lik': 6.2.2
|
|
||||||
'@push.rocks/smartenv': 6.0.0
|
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
|
||||||
'@push.rocks/smartrx': 3.0.10
|
|
||||||
picomatch: 4.0.3
|
|
||||||
|
|
||||||
'@push.rocks/smartxml@2.0.0':
|
'@push.rocks/smartxml@2.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-xml-parser: 5.3.2
|
fast-xml-parser: 5.3.2
|
||||||
|
|||||||
14
readme.md
14
readme.md
@@ -43,9 +43,6 @@ await smartDeno.stop();
|
|||||||
await smartDeno.start({
|
await smartDeno.start({
|
||||||
// Force download a local copy of Deno even if it's in PATH
|
// Force download a local copy of Deno even if it's in PATH
|
||||||
forceLocalDeno: true,
|
forceLocalDeno: true,
|
||||||
|
|
||||||
// Custom port for the internal script server (default: 3210)
|
|
||||||
port: 4000,
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -153,7 +150,7 @@ const smartDeno = new SmartDeno();
|
|||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
// Initialize on startup
|
// Initialize on startup
|
||||||
await smartDeno.start({ port: 3211 });
|
await smartDeno.start();
|
||||||
|
|
||||||
app.post('/execute', async (req, res) => {
|
app.post('/execute', async (req, res) => {
|
||||||
const { script, permissions } = req.body;
|
const { script, permissions } = req.body;
|
||||||
@@ -180,8 +177,8 @@ app.listen(3000);
|
|||||||
SmartDeno works by:
|
SmartDeno works by:
|
||||||
|
|
||||||
1. **🦕 Deno Management** — Automatically downloads the latest Deno binary for your platform if not available or if `forceLocalDeno` is set
|
1. **🦕 Deno Management** — Automatically downloads the latest Deno binary for your platform if not available or if `forceLocalDeno` is set
|
||||||
2. **🖥️ Script Server** — Runs an internal HTTP server that serves scripts to Deno
|
2. **💾 In-Memory Execution** — Scripts < 2MB are executed via stdin (`deno run -`), staying fully in-memory with no disk I/O
|
||||||
3. **⚡ Ephemeral Execution** — Each script execution is isolated and cleaned up after completion
|
3. **📁 Large Script Support** — Scripts >= 2MB automatically use a temp file (cleaned up after execution)
|
||||||
4. **🔒 Permission Control** — Translates permission options to Deno's security flags
|
4. **🔒 Permission Control** — Translates permission options to Deno's security flags
|
||||||
|
|
||||||
## 📚 API Reference
|
## 📚 API Reference
|
||||||
@@ -198,8 +195,8 @@ const smartDeno = new SmartDeno();
|
|||||||
|
|
||||||
| Method | Description |
|
| Method | Description |
|
||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| `start(options?)` | Initialize and start SmartDeno |
|
| `start(options?)` | Initialize SmartDeno (downloads Deno if needed) |
|
||||||
| `stop()` | Stop SmartDeno and clean up resources |
|
| `stop()` | Stop SmartDeno instance |
|
||||||
| `isRunning()` | Check if SmartDeno is currently running |
|
| `isRunning()` | Check if SmartDeno is currently running |
|
||||||
| `executeScript(script, options?)` | Execute a Deno script |
|
| `executeScript(script, options?)` | Execute a Deno script |
|
||||||
|
|
||||||
@@ -208,7 +205,6 @@ const smartDeno = new SmartDeno();
|
|||||||
```typescript
|
```typescript
|
||||||
interface ISmartDenoOptions {
|
interface ISmartDenoOptions {
|
||||||
forceLocalDeno?: boolean;
|
forceLocalDeno?: boolean;
|
||||||
port?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IExecuteScriptOptions {
|
interface IExecuteScriptOptions {
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartdeno',
|
name: '@push.rocks/smartdeno',
|
||||||
version: '1.1.0',
|
version: '1.2.0',
|
||||||
description: 'A module to run Deno scripts from Node.js, including functionalities for downloading Deno and executing Deno scripts.'
|
description: 'A module to run Deno scripts from Node.js, including functionalities for downloading Deno and executing Deno scripts.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
import type { ScriptServer } from './classes.scriptserver.js';
|
|
||||||
import * as plugins from './plugins.js';
|
|
||||||
|
|
||||||
export type TDenoPermission =
|
|
||||||
| 'all'
|
|
||||||
| 'env'
|
|
||||||
| 'ffi'
|
|
||||||
| 'hrtime'
|
|
||||||
| 'net'
|
|
||||||
| 'read'
|
|
||||||
| 'run'
|
|
||||||
| 'sys'
|
|
||||||
| 'write';
|
|
||||||
|
|
||||||
export interface IDenoExecutionOptions {
|
|
||||||
permissions?: TDenoPermission[];
|
|
||||||
denoBinaryPath?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class contains logic to execute deno commands in an ephemeral way
|
|
||||||
*/
|
|
||||||
export class DenoExecution {
|
|
||||||
public id: string;
|
|
||||||
public scriptserverRef: ScriptServer;
|
|
||||||
public script: string;
|
|
||||||
public options: IDenoExecutionOptions;
|
|
||||||
|
|
||||||
constructor(scriptserverRef: ScriptServer, scriptArg: string, options: IDenoExecutionOptions = {}) {
|
|
||||||
this.scriptserverRef = scriptserverRef;
|
|
||||||
this.script = scriptArg;
|
|
||||||
this.options = options;
|
|
||||||
this.id = plugins.smartunique.shortId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildPermissionFlags(): string {
|
|
||||||
const permissions = this.options.permissions || [];
|
|
||||||
if (permissions.length === 0) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (permissions.includes('all')) {
|
|
||||||
return '-A';
|
|
||||||
}
|
|
||||||
return permissions.map(p => `--allow-${p}`).join(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
public async execute(): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
|
||||||
this.scriptserverRef.executionMap.add(this);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const denoBinary = this.options.denoBinaryPath || 'deno';
|
|
||||||
const permissionFlags = this.buildPermissionFlags();
|
|
||||||
const port = this.scriptserverRef.getPort();
|
|
||||||
const scriptUrl = `http://localhost:${port}/getscript/${this.id}`;
|
|
||||||
|
|
||||||
const command = `${denoBinary} run ${permissionFlags} ${scriptUrl}`.replace(/\s+/g, ' ').trim();
|
|
||||||
const result = await this.scriptserverRef.smartshellInstance.exec(command);
|
|
||||||
|
|
||||||
return {
|
|
||||||
exitCode: result.exitCode,
|
|
||||||
stdout: result.stdout,
|
|
||||||
stderr: result.stderr,
|
|
||||||
};
|
|
||||||
} finally {
|
|
||||||
// Clean up: remove from execution map after execution completes
|
|
||||||
this.scriptserverRef.executionMap.remove(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import type { DenoExecution } from './classes.denoexecution.js';
|
|
||||||
import * as plugins from './plugins.js';
|
|
||||||
|
|
||||||
export interface IScriptServerOptions {
|
|
||||||
port?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ScriptServer {
|
|
||||||
private server: plugins.typedserver.servertools.Server;
|
|
||||||
private port: number;
|
|
||||||
public smartshellInstance = new plugins.smartshell.Smartshell({
|
|
||||||
executor: 'bash'
|
|
||||||
});
|
|
||||||
|
|
||||||
public executionMap = new plugins.lik.ObjectMap<DenoExecution>();
|
|
||||||
|
|
||||||
constructor(options: IScriptServerOptions = {}) {
|
|
||||||
this.port = options.port ?? 3210;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getPort(): number {
|
|
||||||
return this.port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async start() {
|
|
||||||
this.server = new plugins.typedserver.servertools.Server({
|
|
||||||
port: this.port,
|
|
||||||
cors: true,
|
|
||||||
});
|
|
||||||
this.server.addRoute(
|
|
||||||
'/getscript/:executionId',
|
|
||||||
new plugins.typedserver.servertools.Handler('GET', async (req, res) => {
|
|
||||||
const executionId = req.params.executionId;
|
|
||||||
const denoExecution = await this.executionMap.find(async denoExecutionArg => {
|
|
||||||
return denoExecutionArg.id === executionId;
|
|
||||||
});
|
|
||||||
if (!denoExecution) {
|
|
||||||
res.statusCode = 404;
|
|
||||||
res.write('Execution not found');
|
|
||||||
res.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.write(denoExecution.script);
|
|
||||||
res.end();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
await this.server.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async stop() {
|
|
||||||
if (this.server) {
|
|
||||||
await this.server.stop();
|
|
||||||
}
|
|
||||||
this.executionMap.wipe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,25 @@
|
|||||||
import { DenoDownloader } from './classes.denodownloader.js';
|
import { DenoDownloader } from './classes.denodownloader.js';
|
||||||
import * as plugins from './plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from './paths.js';
|
||||||
import { ScriptServer } from './classes.scriptserver.js';
|
|
||||||
import { DenoExecution, type TDenoPermission } from './classes.denoexecution.js';
|
const MAX_STDIN_SIZE = 2 * 1024 * 1024; // 2MB threshold for stdin execution
|
||||||
|
|
||||||
|
export type TDenoPermission =
|
||||||
|
| 'all'
|
||||||
|
| 'env'
|
||||||
|
| 'ffi'
|
||||||
|
| 'hrtime'
|
||||||
|
| 'net'
|
||||||
|
| 'read'
|
||||||
|
| 'run'
|
||||||
|
| 'sys'
|
||||||
|
| 'write';
|
||||||
|
|
||||||
export interface ISmartDenoOptions {
|
export interface ISmartDenoOptions {
|
||||||
/**
|
/**
|
||||||
* Force downloading a local copy of Deno even if it's available in PATH
|
* Force downloading a local copy of Deno even if it's available in PATH
|
||||||
*/
|
*/
|
||||||
forceLocalDeno?: boolean;
|
forceLocalDeno?: boolean;
|
||||||
/**
|
|
||||||
* Port for the internal script server (default: 3210)
|
|
||||||
*/
|
|
||||||
port?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IExecuteScriptOptions {
|
export interface IExecuteScriptOptions {
|
||||||
@@ -24,16 +31,14 @@ export interface IExecuteScriptOptions {
|
|||||||
|
|
||||||
export class SmartDeno {
|
export class SmartDeno {
|
||||||
private denoDownloader = new DenoDownloader();
|
private denoDownloader = new DenoDownloader();
|
||||||
private scriptServer: ScriptServer;
|
|
||||||
private denoBinaryPath: string | null = null;
|
private denoBinaryPath: string | null = null;
|
||||||
private isStarted = false;
|
private isStarted = false;
|
||||||
|
private smartshellInstance = new plugins.smartshell.Smartshell({
|
||||||
constructor() {
|
executor: 'bash',
|
||||||
this.scriptServer = new ScriptServer();
|
});
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the SmartDeno instance
|
* Starts the SmartDeno instance (downloads Deno if needed)
|
||||||
* @param optionsArg Configuration options
|
* @param optionsArg Configuration options
|
||||||
*/
|
*/
|
||||||
public async start(optionsArg: ISmartDenoOptions = {}): Promise<void> {
|
public async start(optionsArg: ISmartDenoOptions = {}): Promise<void> {
|
||||||
@@ -41,11 +46,8 @@ export class SmartDeno {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create script server with configured port
|
|
||||||
this.scriptServer = new ScriptServer({ port: optionsArg.port });
|
|
||||||
|
|
||||||
const denoAlreadyInPath = await plugins.smartshell.which('deno', {
|
const denoAlreadyInPath = await plugins.smartshell.which('deno', {
|
||||||
nothrow: true
|
nothrow: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!denoAlreadyInPath || optionsArg.forceLocalDeno) {
|
if (!denoAlreadyInPath || optionsArg.forceLocalDeno) {
|
||||||
@@ -56,19 +58,13 @@ export class SmartDeno {
|
|||||||
this.denoBinaryPath = 'deno';
|
this.denoBinaryPath = 'deno';
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.scriptServer.start();
|
|
||||||
this.isStarted = true;
|
this.isStarted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the SmartDeno instance and cleans up resources
|
* Stops the SmartDeno instance
|
||||||
*/
|
*/
|
||||||
public async stop(): Promise<void> {
|
public async stop(): Promise<void> {
|
||||||
if (!this.isStarted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.scriptServer.stop();
|
|
||||||
this.isStarted = false;
|
this.isStarted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +75,19 @@ export class SmartDeno {
|
|||||||
return this.isStarted;
|
return this.isStarted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build permission flags for Deno
|
||||||
|
*/
|
||||||
|
private buildPermissionFlags(permissions?: TDenoPermission[]): string {
|
||||||
|
if (!permissions || permissions.length === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (permissions.includes('all')) {
|
||||||
|
return '-A';
|
||||||
|
}
|
||||||
|
return permissions.map((p) => `--allow-${p}`).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a Deno script
|
* Execute a Deno script
|
||||||
* @param scriptArg The script content to execute
|
* @param scriptArg The script content to execute
|
||||||
@@ -93,11 +102,75 @@ export class SmartDeno {
|
|||||||
throw new Error('SmartDeno is not started. Call start() first.');
|
throw new Error('SmartDeno is not started. Call start() first.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const denoExecution = new DenoExecution(this.scriptServer, scriptArg, {
|
const denoBinary = this.denoBinaryPath || 'deno';
|
||||||
permissions: options.permissions,
|
const permissionFlags = this.buildPermissionFlags(options.permissions);
|
||||||
denoBinaryPath: this.denoBinaryPath || undefined,
|
const scriptSize = Buffer.byteLength(scriptArg, 'utf8');
|
||||||
});
|
|
||||||
|
|
||||||
return denoExecution.execute();
|
if (scriptSize < MAX_STDIN_SIZE) {
|
||||||
|
// Use stdin for small scripts (in-memory)
|
||||||
|
return this.executeViaStdin(denoBinary, permissionFlags, scriptArg);
|
||||||
|
} else {
|
||||||
|
// Use temp file for large scripts
|
||||||
|
return this.executeViaTempFile(denoBinary, permissionFlags, scriptArg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute script via stdin (in-memory, for scripts < 2MB)
|
||||||
|
* Uses `deno run -` which reads from stdin and supports all permission flags
|
||||||
|
*/
|
||||||
|
private async executeViaStdin(
|
||||||
|
denoBinary: string,
|
||||||
|
permissionFlags: string,
|
||||||
|
script: string
|
||||||
|
): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
||||||
|
// Use base64 encoding to safely pass script through shell
|
||||||
|
const base64Script = Buffer.from(script).toString('base64');
|
||||||
|
const command = `echo '${base64Script}' | base64 -d | ${denoBinary} run ${permissionFlags} -`.trim().replace(/\s+/g, ' ');
|
||||||
|
|
||||||
|
const result = await this.smartshellInstance.exec(command);
|
||||||
|
return {
|
||||||
|
exitCode: result.exitCode,
|
||||||
|
stdout: result.stdout,
|
||||||
|
stderr: result.stderr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute script via temp file (for scripts >= 2MB)
|
||||||
|
*/
|
||||||
|
private async executeViaTempFile(
|
||||||
|
denoBinary: string,
|
||||||
|
permissionFlags: string,
|
||||||
|
script: string
|
||||||
|
): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
||||||
|
const tempFileName = `deno_script_${plugins.smartunique.shortId()}.ts`;
|
||||||
|
const tempFilePath = plugins.path.join(paths.nogitDir, tempFileName);
|
||||||
|
const fsInstance = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode());
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ensure .nogit directory exists
|
||||||
|
await fsInstance.directory(paths.nogitDir).create();
|
||||||
|
|
||||||
|
// Write script to temp file
|
||||||
|
await fsInstance.file(tempFilePath).write(script);
|
||||||
|
|
||||||
|
// Execute the script
|
||||||
|
const command = `${denoBinary} run ${permissionFlags} "${tempFilePath}"`.trim().replace(/\s+/g, ' ');
|
||||||
|
const result = await this.smartshellInstance.exec(command);
|
||||||
|
|
||||||
|
return {
|
||||||
|
exitCode: result.exitCode,
|
||||||
|
stdout: result.stdout,
|
||||||
|
stderr: result.stderr,
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
// Clean up temp file
|
||||||
|
try {
|
||||||
|
await fsInstance.file(tempFilePath).delete();
|
||||||
|
} catch {
|
||||||
|
// Ignore cleanup errors
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
export * from './classes.smartdeno.js';
|
export * from './classes.smartdeno.js';
|
||||||
export type { TDenoPermission } from './classes.denoexecution.js';
|
|
||||||
|
|||||||
@@ -5,15 +5,7 @@ export {
|
|||||||
path,
|
path,
|
||||||
}
|
}
|
||||||
|
|
||||||
// @api.global scope
|
|
||||||
import * as typedserver from '@api.global/typedserver';
|
|
||||||
|
|
||||||
export {
|
|
||||||
typedserver,
|
|
||||||
}
|
|
||||||
|
|
||||||
// @push.rocks scope
|
// @push.rocks scope
|
||||||
import * as lik from '@push.rocks/lik';
|
|
||||||
import * as smartarchive from '@push.rocks/smartarchive';
|
import * as smartarchive from '@push.rocks/smartarchive';
|
||||||
import * as smartfs from '@push.rocks/smartfs';
|
import * as smartfs from '@push.rocks/smartfs';
|
||||||
import * as smartpath from '@push.rocks/smartpath';
|
import * as smartpath from '@push.rocks/smartpath';
|
||||||
@@ -21,10 +13,9 @@ import * as smartshell from '@push.rocks/smartshell';
|
|||||||
import * as smartunique from '@push.rocks/smartunique';
|
import * as smartunique from '@push.rocks/smartunique';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
lik,
|
|
||||||
smartarchive,
|
smartarchive,
|
||||||
smartfs,
|
smartfs,
|
||||||
smartpath,
|
smartpath,
|
||||||
smartshell,
|
smartshell,
|
||||||
smartunique,
|
smartunique,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user