Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1241e4a333 | |||
| f93d3e2cfe | |||
| 78a5615bb5 | |||
| d4a100ff32 |
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"@git.zone/cli": {
|
||||||
|
"projectType": "npm",
|
||||||
|
"module": {
|
||||||
|
"githost": "code.foss.global",
|
||||||
|
"gitscope": "push.rocks",
|
||||||
|
"gitrepo": "smartdeno",
|
||||||
|
"description": "A module to run Deno scripts from Node.js, including functionalities for downloading Deno and executing Deno scripts.",
|
||||||
|
"npmPackagename": "@push.rocks/smartdeno",
|
||||||
|
"license": "MIT",
|
||||||
|
"projectDomain": "push.rocks"
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"registries": [
|
||||||
|
"https://verdaccio.lossless.digital",
|
||||||
|
"https://registry.npmjs.org"
|
||||||
|
],
|
||||||
|
"accessLevel": "public"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@git.zone/tsdoc": {
|
||||||
|
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||||
|
},
|
||||||
|
"@ship.zone/szci": {
|
||||||
|
"npmGlobalTools": [],
|
||||||
|
"npmRegistryUrl": "registry.npmjs.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,23 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-04-30 - 1.2.1 - fix(smartdeno)
|
||||||
|
normalize stderr output to an empty string and update build configuration
|
||||||
|
|
||||||
|
- Ensure executeScript always returns a string for stderr in both stdin and temp-file execution paths.
|
||||||
|
- Adjust tests and TypeScript/build settings for stricter typing and updated toolchain compatibility.
|
||||||
|
- Refresh package metadata and project configuration files, including registry and issue tracker settings.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2026 Task Venture Capital GmbH
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
+13
-6
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"gitzone": {
|
"@git.zone/cli": {
|
||||||
"projectType": "npm",
|
"projectType": "npm",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "code.foss.global",
|
"githost": "code.foss.global",
|
||||||
@@ -21,13 +21,20 @@
|
|||||||
"Ephemeral Execution",
|
"Ephemeral Execution",
|
||||||
"Cross-platform"
|
"Cross-platform"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"registries": [
|
||||||
|
"https://verdaccio.lossless.digital",
|
||||||
|
"https://registry.npmjs.org"
|
||||||
|
],
|
||||||
|
"accessLevel": "public"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmci": {
|
"@git.zone/tsdoc": {
|
||||||
"npmGlobalTools": [],
|
|
||||||
"npmAccessLevel": "public"
|
|
||||||
},
|
|
||||||
"tsdoc": {
|
|
||||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||||
|
},
|
||||||
|
"@ship.zone/szci": {
|
||||||
|
"npmGlobalTools": [],
|
||||||
|
"npmRegistryUrl": "registry.npmjs.org"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+15
-15
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartdeno",
|
"name": "@push.rocks/smartdeno",
|
||||||
"version": "1.1.0",
|
"version": "1.2.1",
|
||||||
"private": false,
|
"private": false,
|
||||||
"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.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
@@ -10,22 +10,20 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(tstest test/ --verbose --timeout 60)",
|
"test": "(tstest test/ --verbose --timeout 60)",
|
||||||
"build": "(tsbuild --web --allowimplicitany)",
|
"build": "tsbuild --web",
|
||||||
"buildDocs": "(tsdoc)"
|
"buildDocs": "(tsdoc)"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^3.1.2",
|
"@git.zone/tsbuild": "^4.4.0",
|
||||||
"@git.zone/tsrun": "^2.0.0",
|
"@git.zone/tsrun": "^2.0.3",
|
||||||
"@git.zone/tstest": "^3.1.3",
|
"@git.zone/tstest": "^3.6.3",
|
||||||
"@types/node": "^24.10.1"
|
"@types/node": "^25.6.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@api.global/typedserver": "^4.0.0",
|
"@push.rocks/smartarchive": "^5.2.2",
|
||||||
"@push.rocks/lik": "^6.2.2",
|
"@push.rocks/smartfs": "^1.5.1",
|
||||||
"@push.rocks/smartarchive": "^5.0.1",
|
|
||||||
"@push.rocks/smartfs": "^1.2.0",
|
|
||||||
"@push.rocks/smartpath": "^6.0.0",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@push.rocks/smartshell": "^3.3.0",
|
"@push.rocks/smartshell": "^3.3.8",
|
||||||
"@push.rocks/smartunique": "^3.0.9"
|
"@push.rocks/smartunique": "^3.0.9"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -33,7 +31,7 @@
|
|||||||
"url": "https://code.foss.global/push.rocks/smartdeno.git"
|
"url": "https://code.foss.global/push.rocks/smartdeno.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://gitlab.com/push.rocks/smartdeno/issues"
|
"url": "https://code.foss.global/push.rocks/smartdeno/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://code.foss.global/push.rocks/smartdeno",
|
"homepage": "https://code.foss.global/push.rocks/smartdeno",
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
@@ -48,6 +46,8 @@
|
|||||||
"dist_ts_web/**/*",
|
"dist_ts_web/**/*",
|
||||||
"assets/**/*",
|
"assets/**/*",
|
||||||
"cli.js",
|
"cli.js",
|
||||||
|
".smartconfig.json",
|
||||||
|
"license",
|
||||||
"npmextra.json",
|
"npmextra.json",
|
||||||
"readme.md"
|
"readme.md"
|
||||||
],
|
],
|
||||||
@@ -59,9 +59,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.28.2"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2281
-1793
File diff suppressed because it is too large
Load Diff
@@ -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 {
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ tap.test('should throw when executing before start', async () => {
|
|||||||
await testSmartdeno.executeScript('console.log("test")');
|
await testSmartdeno.executeScript('console.log("test")');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
threw = true;
|
threw = true;
|
||||||
|
if (!(e instanceof Error)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
expect(e.message).toInclude('not started');
|
expect(e.message).toInclude('not started');
|
||||||
}
|
}
|
||||||
expect(threw).toBeTrue();
|
expect(threw).toBeTrue();
|
||||||
|
|||||||
@@ -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.1',
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+101
-28
@@ -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,7 +13,6 @@ 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,
|
||||||
|
|||||||
+4
-4
@@ -5,10 +5,10 @@
|
|||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
|
"noImplicitAny": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"verbatimModuleSyntax": true
|
"verbatimModuleSyntax": true,
|
||||||
|
"types": ["node"]
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": ["dist_*/**/*.d.ts"]
|
||||||
"dist_*/**/*.d.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user