@devscholar/node-ps1-dotnet 0.0.3 → 0.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.
- package/README.md +1 -1
- package/package.json +14 -8
- package/scripts/PsBridge/PsHost.cs +3 -2
- package/scripts/PsBridge/PsHostEntry.cs +7 -0
- package/scripts/PsBridge/Reflection.cs +19 -0
- package/scripts/PsHost.ps1 +3 -0
- package/src/index.ts +28 -8
- package/src/ipc.ts +1 -1
- package/src/namespace.ts +2 -2
- package/src/proxy.ts +1 -1
- package/src/state.ts +1 -1
- package/start.js +57 -19
- package/tsconfig.json +1 -1
- package/AGENTS.md +0 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
⚠️ This project is still in pre-alpha stage, and API is subject to change.
|
|
4
4
|
|
|
5
|
-
This is a project that mimics the [Node API for .NET](https://github.com/microsoft/node-api-dotnet), aiming to utilize the built-in PowerShell 5.1 in Windows to replace the full high-version .NET runtime, thereby reducing the program's size. Since this project uses IPC instead of C++ Addon, it is compatible not only with Node but also with Deno and Bun.
|
|
5
|
+
This is a project that mimics the [Node API for .NET](https://github.com/microsoft/node-api-dotnet), aiming to utilize the built-in PowerShell 5.1 in Windows to replace the full high-version .NET runtime, thereby reducing the program's size. Since this project uses IPC instead of C++ Addon, it is compatible not only with Node but also with Deno and Bun.
|
|
6
6
|
|
|
7
7
|
# Requirements
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devscholar/node-ps1-dotnet",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Node.js to .NET interop via IPC",
|
|
5
|
-
"main": "./
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
|
-
".": "./
|
|
8
|
+
".": "./dist/index.js"
|
|
9
9
|
},
|
|
10
|
+
"types": "./types/index.d.ts",
|
|
10
11
|
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
11
13
|
"test": "vitest run",
|
|
12
14
|
"test:watch": "vitest",
|
|
13
15
|
"test:coverage": "vitest run --coverage",
|
|
14
|
-
"
|
|
16
|
+
"start": "node start.js"
|
|
15
17
|
},
|
|
16
18
|
"keywords": [
|
|
17
19
|
"dotnet",
|
|
18
20
|
"winforms",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
21
|
+
"wpf",
|
|
22
|
+
"webview2",
|
|
23
|
+
"netfx",
|
|
24
|
+
"deno",
|
|
25
|
+
"bun",
|
|
26
|
+
"gui"
|
|
22
27
|
],
|
|
23
28
|
"author": "",
|
|
24
|
-
"license": "
|
|
29
|
+
"license": "MIT",
|
|
25
30
|
"devDependencies": {
|
|
26
31
|
"@types/node": "^25.0.10",
|
|
32
|
+
"tsx": "^4.21.0",
|
|
27
33
|
"typescript": "^5.9.3",
|
|
28
34
|
"vitest": "^3.0.0"
|
|
29
35
|
}
|
|
@@ -101,8 +101,9 @@ public static class PsHost
|
|
|
101
101
|
BridgeState.PipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
|
|
102
102
|
|
|
103
103
|
BridgeState.PipeServer.WaitForConnection();
|
|
104
|
-
|
|
105
|
-
BridgeState.
|
|
104
|
+
var utf8Encoding = new System.Text.UTF8Encoding(false);
|
|
105
|
+
BridgeState.Reader = new StreamReader(BridgeState.PipeServer, utf8Encoding);
|
|
106
|
+
BridgeState.Writer = new StreamWriter(BridgeState.PipeServer, utf8Encoding);
|
|
106
107
|
BridgeState.Writer.AutoFlush = true;
|
|
107
108
|
|
|
108
109
|
var readerThread = new Thread(() =>
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
// scripts/PsBridge/PsHostEntry.cs
|
|
2
2
|
using System;
|
|
3
|
+
using System.Globalization;
|
|
4
|
+
using System.Text;
|
|
3
5
|
|
|
4
6
|
public static class PsHostEntry
|
|
5
7
|
{
|
|
6
8
|
public static void Run(string pipeName)
|
|
7
9
|
{
|
|
10
|
+
Console.OutputEncoding = Encoding.UTF8;
|
|
11
|
+
Console.InputEncoding = Encoding.UTF8;
|
|
12
|
+
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
|
13
|
+
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;
|
|
14
|
+
|
|
8
15
|
BridgeState.PipeName = pipeName;
|
|
9
16
|
PsHost.ProcessNestedCommands = PsHost.RunProcessNestedCommands;
|
|
10
17
|
|
|
@@ -647,6 +647,25 @@ public static class Reflection
|
|
|
647
647
|
return Protocol.ConvertToProtocol(asm);
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
+
if (action == "LoadFrom")
|
|
651
|
+
{
|
|
652
|
+
var filePath = cmd["filePath"].ToString();
|
|
653
|
+
if (!File.Exists(filePath))
|
|
654
|
+
{
|
|
655
|
+
throw new Exception("File not found: " + filePath);
|
|
656
|
+
}
|
|
657
|
+
Assembly asm = null;
|
|
658
|
+
try
|
|
659
|
+
{
|
|
660
|
+
asm = Assembly.LoadFrom(filePath);
|
|
661
|
+
}
|
|
662
|
+
catch (Exception ex)
|
|
663
|
+
{
|
|
664
|
+
throw new Exception("Failed to load assembly from file: " + ex.Message);
|
|
665
|
+
}
|
|
666
|
+
return Protocol.ConvertToProtocol(asm);
|
|
667
|
+
}
|
|
668
|
+
|
|
650
669
|
if (action == "Release")
|
|
651
670
|
{
|
|
652
671
|
Protocol.RemoveBridgeObject(cmd["targetId"].ToString());
|
package/scripts/PsHost.ps1
CHANGED
|
@@ -6,6 +6,9 @@ $ScriptDir = Split-Path $MyInvocation.MyCommand.Path
|
|
|
6
6
|
Import-Module "$ScriptDir\PsBridge" -Force
|
|
7
7
|
|
|
8
8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
9
|
+
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
|
10
|
+
[System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo]::InvariantCulture
|
|
11
|
+
[System.Threading.Thread]::CurrentThread.CurrentUICulture = [System.Globalization.CultureInfo]::InvariantCulture
|
|
9
12
|
$ErrorActionPreference = "Stop"
|
|
10
13
|
|
|
11
14
|
[PsHostEntry]::Run($PipeName)
|
package/src/index.ts
CHANGED
|
@@ -2,11 +2,11 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import * as cp from 'node:child_process';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { getPowerShellPath } from './utils.
|
|
6
|
-
import { IpcSync } from './ipc.
|
|
7
|
-
import { getIpc, setIpc, getProc, setProc, getInitialized, setInitialized, getCachedRuntimeInfo, setCachedRuntimeInfo } from './state.
|
|
8
|
-
import { callbackRegistry, createProxyWithInlineProps, createProxy, setNodePs1Dotnet } from './proxy.
|
|
9
|
-
import { createNamespaceProxy, createExportNamespaceProxy } from './namespace.
|
|
5
|
+
import { getPowerShellPath } from './utils.js';
|
|
6
|
+
import { IpcSync } from './ipc.js';
|
|
7
|
+
import { getIpc, setIpc, getProc, setProc, getInitialized, setInitialized, getCachedRuntimeInfo, setCachedRuntimeInfo } from './state.js';
|
|
8
|
+
import { callbackRegistry, createProxyWithInlineProps, createProxy, setNodePs1Dotnet } from './proxy.js';
|
|
9
|
+
import { createNamespaceProxy, createExportNamespaceProxy } from './namespace.js';
|
|
10
10
|
|
|
11
11
|
export const __filename = fileURLToPath(import.meta.url);
|
|
12
12
|
export const __dirname = path.dirname(__filename);
|
|
@@ -49,9 +49,19 @@ function doInitialize() {
|
|
|
49
49
|
|
|
50
50
|
const powerShellPath = getPowerShellPath();
|
|
51
51
|
const proc = cp.spawn(powerShellPath, [
|
|
52
|
-
'-NoProfile', '-ExecutionPolicy', 'Bypass',
|
|
53
|
-
'-Command',
|
|
54
|
-
], {
|
|
52
|
+
'-NoProfile', '-ExecutionPolicy', 'Bypass', '-OutputFormat', 'Text',
|
|
53
|
+
'-Command', `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'; chcp.com 65001 > $null; & '${scriptPath}' -PipeName '${pipeName}'`
|
|
54
|
+
], {
|
|
55
|
+
stdio: 'inherit',
|
|
56
|
+
windowsHide: false,
|
|
57
|
+
env: {
|
|
58
|
+
...process.env,
|
|
59
|
+
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: '0',
|
|
60
|
+
DOTNET_SYSTEM_GLOBALIZATION_CULTURE: 'en-US',
|
|
61
|
+
DOTNET_UTF8_GLOBALIZATION: '1'
|
|
62
|
+
},
|
|
63
|
+
shell: false
|
|
64
|
+
});
|
|
55
65
|
|
|
56
66
|
setProc(proc);
|
|
57
67
|
proc.unref();
|
|
@@ -136,6 +146,13 @@ export const node_ps1_dotnet = {
|
|
|
136
146
|
return createProxy(res);
|
|
137
147
|
},
|
|
138
148
|
|
|
149
|
+
_loadFrom(filePath: string): any {
|
|
150
|
+
doInitialize();
|
|
151
|
+
const ipc = getIpc();
|
|
152
|
+
const res = ipc!.send({ action: 'LoadFrom', filePath });
|
|
153
|
+
return createProxy(res);
|
|
154
|
+
},
|
|
155
|
+
|
|
139
156
|
_getRuntimeInfo(): { frameworkMoniker: string; runtimeVersion: string } {
|
|
140
157
|
if (getCachedRuntimeInfo()) return getCachedRuntimeInfo()!;
|
|
141
158
|
doInitialize();
|
|
@@ -159,6 +176,9 @@ const dotnetProxy = new Proxy(function() {} as any, {
|
|
|
159
176
|
if (prop === 'load') return (assemblyNameOrFilePath: string) => {
|
|
160
177
|
node_ps1_dotnet._loadAssembly(assemblyNameOrFilePath);
|
|
161
178
|
};
|
|
179
|
+
if (prop === 'loadFrom') return (filePath: string) => {
|
|
180
|
+
node_ps1_dotnet._loadFrom(filePath);
|
|
181
|
+
};
|
|
162
182
|
if (prop === 'frameworkMoniker') {
|
|
163
183
|
return node_ps1_dotnet._getRuntimeInfo().frameworkMoniker;
|
|
164
184
|
}
|
package/src/ipc.ts
CHANGED
package/src/namespace.ts
CHANGED
package/src/proxy.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getIpc, setIpc, getProc, setProc, getInitialized, setInitialized, getCachedRuntimeInfo, setCachedRuntimeInfo } from './state.
|
|
1
|
+
import { getIpc, setIpc, getProc, setProc, getInitialized, setInitialized, getCachedRuntimeInfo, setCachedRuntimeInfo } from './state.js';
|
|
2
2
|
|
|
3
3
|
export let node_ps1_dotnetGetter: (() => any) | null = null;
|
|
4
4
|
|
package/src/state.ts
CHANGED
package/start.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// start.js - Build and run TypeScript files
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import fs from 'fs';
|
|
@@ -12,7 +12,6 @@ let runtime = 'node';
|
|
|
12
12
|
let tsFile = null;
|
|
13
13
|
let extraArgs = [];
|
|
14
14
|
|
|
15
|
-
// Parse arguments
|
|
16
15
|
for (let i = 0; i < args.length; i++) {
|
|
17
16
|
const arg = args[i];
|
|
18
17
|
if (arg.startsWith('--runtime=')) {
|
|
@@ -41,27 +40,66 @@ if (!fs.existsSync(filePath)) {
|
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
const runtimeFlags = {
|
|
44
|
-
node: [
|
|
43
|
+
node: [],
|
|
45
44
|
bun: [],
|
|
46
45
|
deno: ['run', '--allow-all', '--unstable-node-globals']
|
|
47
46
|
};
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
console.log(
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
48
|
+
async function buildAndRun() {
|
|
49
|
+
const tsFileDir = path.dirname(filePath);
|
|
50
|
+
|
|
51
|
+
console.log('Building TypeScript in:', tsFileDir);
|
|
52
|
+
|
|
53
|
+
const tscProc = spawn('npx', ['tsc', '-p', path.join(__dirname, 'tsconfig.json')], {
|
|
54
|
+
stdio: 'inherit',
|
|
55
|
+
cwd: __dirname,
|
|
56
|
+
shell: true
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await new Promise((resolve, reject) => {
|
|
60
|
+
tscProc.on('exit', (code) => {
|
|
61
|
+
if (code !== 0) {
|
|
62
|
+
console.error('Build failed with code:', code);
|
|
63
|
+
reject(new Error(`tsc exited with code ${code}`));
|
|
64
|
+
} else {
|
|
65
|
+
resolve();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
tscProc.on('error', reject);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
console.log('Build complete. Running with', runtime, ':', tsFile);
|
|
72
|
+
|
|
73
|
+
const ext = path.extname(tsFile);
|
|
74
|
+
const baseName = path.basename(tsFile, ext);
|
|
75
|
+
const relativePath = path.relative(path.join(__dirname, 'src'), filePath);
|
|
76
|
+
const jsFile = path.join(__dirname, 'dist', relativePath.replace(/\.ts$/, '.js'));
|
|
77
|
+
|
|
78
|
+
const runtimeArgs = runtimeFlags[runtime] || [];
|
|
79
|
+
const runtimeCmd = runtime;
|
|
80
|
+
|
|
81
|
+
const finalArgs = runtime === 'deno'
|
|
82
|
+
? [...runtimeArgs, jsFile, ...extraArgs]
|
|
83
|
+
: [...runtimeArgs, jsFile, ...extraArgs];
|
|
84
|
+
|
|
85
|
+
const proc = spawn(runtimeCmd, finalArgs, {
|
|
86
|
+
stdio: 'inherit',
|
|
87
|
+
cwd: process.cwd(),
|
|
88
|
+
env: { ...process.env, DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: '0', CHARSET: 'UTF-8' },
|
|
89
|
+
shell: true
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
proc.on('exit', (code) => {
|
|
93
|
+
process.exit(code);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
proc.on('error', (err) => {
|
|
97
|
+
console.error(`Failed to start ${runtime}:`, err.message);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
63
101
|
|
|
64
|
-
|
|
65
|
-
console.error(
|
|
102
|
+
buildAndRun().catch((err) => {
|
|
103
|
+
console.error('Error:', err.message);
|
|
66
104
|
process.exit(1);
|
|
67
105
|
});
|
package/tsconfig.json
CHANGED
package/AGENTS.md
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
When using TypeScript in Node, you must run TS with the `--experimental-transform-types` flag; other methods like `tsx` are not allowed, and building and transpilation are not allowed, except Vite. The test framework should be Vitest, not Jest. ESM must be used instead of require.
|
|
2
|
-
allowImportingTsExtensions should be true in tsconfig.json. (If this file exists)
|