artnet2usb-cli 0.2.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/LICENSE +21 -0
- package/README.de.md +149 -0
- package/README.es.md +149 -0
- package/README.fr.md +149 -0
- package/README.it.md +149 -0
- package/README.ja.md +149 -0
- package/README.md +149 -0
- package/README.pt-BR.md +149 -0
- package/README.ru.md +149 -0
- package/README.tr.md +149 -0
- package/README.zh-Hans.md +149 -0
- package/dist/main/main.js +518 -0
- package/dist-cli/app/artnet.js +158 -0
- package/dist-cli/app/dmxOutput.js +203 -0
- package/dist-cli/cli/autoRoute.js +43 -0
- package/dist-cli/cli/bridge.js +46 -0
- package/dist-cli/cli/configDir.js +49 -0
- package/dist-cli/cli/doctor.js +42 -0
- package/dist-cli/cli/index.js +422 -0
- package/dist-cli/cli/routeCommands.js +80 -0
- package/dist-cli/cli/serviceInstall.js +181 -0
- package/dist-cli/shared/portableConfig.js +52 -0
- package/dist-cli/shared/store.js +76 -0
- package/dist-cli/shared/types.js +3 -0
- package/package.json +122 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isWritableDir = isWritableDir;
|
|
7
|
+
exports.resolvePortableConfigDir = resolvePortableConfigDir;
|
|
8
|
+
exports.isCompiledBinary = isCompiledBinary;
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
/**
|
|
12
|
+
* Portable (standalone) config resolution — the shared rule for GUI and CLI:
|
|
13
|
+
* 1. If a config.json already EXISTS next to the exe, it always wins (USB drive scenario).
|
|
14
|
+
* 2. Otherwise try writing next to the exe — if writable, the config is created there and stays.
|
|
15
|
+
* 3. If it can't be written (e.g. macOS app translocation, read-only mount), fall back
|
|
16
|
+
* to the OS-standard user directory (the old behavior).
|
|
17
|
+
* If the GUI and CLI binaries are placed in the same folder, they keep sharing the same config.json.
|
|
18
|
+
*/
|
|
19
|
+
/** Is the directory actually writable — does a test write (accessSync is misleading on Windows). */
|
|
20
|
+
function isWritableDir(dir) {
|
|
21
|
+
const probe = node_path_1.default.join(dir, `.artnet2usb-write-probe-${process.pid}`);
|
|
22
|
+
try {
|
|
23
|
+
node_fs_1.default.writeFileSync(probe, '');
|
|
24
|
+
node_fs_1.default.unlinkSync(probe);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function resolvePortableConfigDir(exeSideDir, fallbackDir) {
|
|
32
|
+
if (!exeSideDir)
|
|
33
|
+
return fallbackDir;
|
|
34
|
+
try {
|
|
35
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(exeSideDir, 'config.json')))
|
|
36
|
+
return exeSideDir;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return fallbackDir;
|
|
40
|
+
}
|
|
41
|
+
return isWritableDir(exeSideDir) ? exeSideDir : fallbackDir;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Is the process running as a single-file compiled binary?
|
|
45
|
+
* (pkg → process.pkg; bun build --compile → globalThis.Bun)
|
|
46
|
+
* Returns false under a plain `node`/`tsx` run — in that case process.execPath
|
|
47
|
+
* is node itself, and writing a config next to it would be meaningless/wrong.
|
|
48
|
+
*/
|
|
49
|
+
function isCompiledBinary() {
|
|
50
|
+
const proc = process;
|
|
51
|
+
return Boolean(proc.pkg) || 'Bun' in globalThis;
|
|
52
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Store = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const DEFAULTS = {
|
|
10
|
+
deviceNames: {},
|
|
11
|
+
routes: [],
|
|
12
|
+
settings: { onSignalLoss: 'hold', signalLossMs: 3000 }
|
|
13
|
+
};
|
|
14
|
+
class Store {
|
|
15
|
+
file;
|
|
16
|
+
state;
|
|
17
|
+
constructor(configDir) {
|
|
18
|
+
this.file = node_path_1.default.join(configDir, 'config.json');
|
|
19
|
+
this.state = this.load();
|
|
20
|
+
}
|
|
21
|
+
load() {
|
|
22
|
+
try {
|
|
23
|
+
const raw = JSON.parse(node_fs_1.default.readFileSync(this.file, 'utf8'));
|
|
24
|
+
return {
|
|
25
|
+
deviceNames: raw.deviceNames ?? {},
|
|
26
|
+
routes: Array.isArray(raw.routes) ? raw.routes : [],
|
|
27
|
+
settings: { ...DEFAULTS.settings, ...(raw.settings ?? {}) }
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return structuredClone(DEFAULTS);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
save() {
|
|
35
|
+
try {
|
|
36
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(this.file), { recursive: true });
|
|
37
|
+
node_fs_1.default.writeFileSync(this.file, JSON.stringify(this.state, null, 2));
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error('[store] failed to save', err);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
get deviceNames() {
|
|
44
|
+
return this.state.deviceNames;
|
|
45
|
+
}
|
|
46
|
+
setDeviceName(uid, name) {
|
|
47
|
+
if (name.trim())
|
|
48
|
+
this.state.deviceNames[uid] = name.trim().slice(0, 60);
|
|
49
|
+
else
|
|
50
|
+
delete this.state.deviceNames[uid];
|
|
51
|
+
this.save();
|
|
52
|
+
}
|
|
53
|
+
get routes() {
|
|
54
|
+
return this.state.routes;
|
|
55
|
+
}
|
|
56
|
+
upsertRoute(route) {
|
|
57
|
+
const i = this.state.routes.findIndex((r) => r.id === route.id);
|
|
58
|
+
if (i >= 0)
|
|
59
|
+
this.state.routes[i] = route;
|
|
60
|
+
else
|
|
61
|
+
this.state.routes.push(route);
|
|
62
|
+
this.save();
|
|
63
|
+
}
|
|
64
|
+
removeRoute(id) {
|
|
65
|
+
this.state.routes = this.state.routes.filter((r) => r.id !== id);
|
|
66
|
+
this.save();
|
|
67
|
+
}
|
|
68
|
+
get settings() {
|
|
69
|
+
return this.state.settings;
|
|
70
|
+
}
|
|
71
|
+
setSettings(s) {
|
|
72
|
+
this.state.settings = s;
|
|
73
|
+
this.save();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.Store = Store;
|
package/package.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "artnet2usb-cli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Headless Art-Net to USB DMX bridge CLI for Enttec DMX USB Pro and Open DMX / FTDI interfaces — a desktop GUI is also available",
|
|
5
|
+
"main": "dist/main/main.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"artnet2usb-cli": "dist-cli/cli/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist-cli"
|
|
11
|
+
],
|
|
12
|
+
"author": "Remana <info@remana.tech> (https://relackout.com)",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/remanatech/artnet2usb.git"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://relackout.com/usb-dmx",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/remanatech/artnet2usb/issues"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"art-net",
|
|
24
|
+
"artnet",
|
|
25
|
+
"dmx",
|
|
26
|
+
"dmx512",
|
|
27
|
+
"usb-dmx",
|
|
28
|
+
"enttec",
|
|
29
|
+
"ftdi",
|
|
30
|
+
"open-dmx",
|
|
31
|
+
"lighting-control",
|
|
32
|
+
"stage-lighting"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"dev": "electron-vite dev",
|
|
36
|
+
"build": "electron-vite build",
|
|
37
|
+
"start": "electron .",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
40
|
+
"typecheck:cli": "tsc --noEmit -p tsconfig.cli.json",
|
|
41
|
+
"dist": "node scripts/build-standalone.mjs",
|
|
42
|
+
"dist:mac": "node scripts/build-standalone.mjs --mac",
|
|
43
|
+
"dist:win": "node scripts/build-standalone.mjs --win",
|
|
44
|
+
"dist:linux": "node scripts/build-standalone.mjs --linux",
|
|
45
|
+
"build:cli": "tsc -p tsconfig.cli.json",
|
|
46
|
+
"dev:cli": "tsx cli/index.ts",
|
|
47
|
+
"dist:standalone": "node scripts/build-standalone.mjs",
|
|
48
|
+
"prepack": "npm run build:cli"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@clack/prompts": "^1.7.0",
|
|
52
|
+
"commander": "^15.0.0",
|
|
53
|
+
"serialport": "^12.0.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@fontsource/space-grotesk": "^5.2.10",
|
|
57
|
+
"react": "^18.3.1",
|
|
58
|
+
"react-dom": "^18.3.1",
|
|
59
|
+
"@types/react": "^18.3.12",
|
|
60
|
+
"@types/react-dom": "^18.3.1",
|
|
61
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
62
|
+
"@yao-pkg/pkg": "^6.21.0",
|
|
63
|
+
"autoprefixer": "^10.4.20",
|
|
64
|
+
"electron": "^33.2.0",
|
|
65
|
+
"electron-builder": "^26.0.0",
|
|
66
|
+
"electron-vite": "^2.3.0",
|
|
67
|
+
"esbuild": "^0.28.1",
|
|
68
|
+
"postcss": "^8.4.49",
|
|
69
|
+
"resedit": "^1.7.2",
|
|
70
|
+
"tailwindcss": "^3.4.15",
|
|
71
|
+
"tsx": "^4.19.2",
|
|
72
|
+
"typescript": "^5.6.3",
|
|
73
|
+
"vite": "^5.4.11",
|
|
74
|
+
"vitest": "^2.1.8"
|
|
75
|
+
},
|
|
76
|
+
"allowScripts": {
|
|
77
|
+
"electron@33.4.11": true,
|
|
78
|
+
"esbuild@0.21.5": true,
|
|
79
|
+
"@serialport/bindings-cpp@12.0.1": true,
|
|
80
|
+
"electron-winstaller@5.4.0": true,
|
|
81
|
+
"esbuild@0.28.1": true
|
|
82
|
+
},
|
|
83
|
+
"build": {
|
|
84
|
+
"appId": "tech.remana.relackout.artnet2usb",
|
|
85
|
+
"productName": "Relackout ArtNet 2 USB",
|
|
86
|
+
"npmRebuild": false,
|
|
87
|
+
"files": [
|
|
88
|
+
"dist/**"
|
|
89
|
+
],
|
|
90
|
+
"directories": {
|
|
91
|
+
"output": "release"
|
|
92
|
+
},
|
|
93
|
+
"mac": {
|
|
94
|
+
"category": "public.app-category.utilities",
|
|
95
|
+
"target": [
|
|
96
|
+
"dmg"
|
|
97
|
+
],
|
|
98
|
+
"icon": "build/icon.icns"
|
|
99
|
+
},
|
|
100
|
+
"win": {
|
|
101
|
+
"target": [
|
|
102
|
+
"portable"
|
|
103
|
+
],
|
|
104
|
+
"icon": "build/icon.ico"
|
|
105
|
+
},
|
|
106
|
+
"linux": {
|
|
107
|
+
"target": [
|
|
108
|
+
"AppImage"
|
|
109
|
+
],
|
|
110
|
+
"icon": "build/icons"
|
|
111
|
+
},
|
|
112
|
+
"portable": {
|
|
113
|
+
"artifactName": "Relackout-ArtNet2USB-${version}-portable.${ext}"
|
|
114
|
+
},
|
|
115
|
+
"appImage": {
|
|
116
|
+
"artifactName": "Relackout-ArtNet2USB-${version}-${arch}.${ext}"
|
|
117
|
+
},
|
|
118
|
+
"dmg": {
|
|
119
|
+
"artifactName": "Relackout-ArtNet2USB-${version}-${arch}.${ext}"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|