@node-3d/addon-tools 10.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Luis Blanco
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,252 @@
1
+ # Addon Tools
2
+
3
+ This is a part of [Node3D](https://github.com/node-3d) project.
4
+
5
+ [![NPM](https://badge.fury.io/js/%40node-3d%2Faddon-tools.svg)](https://badge.fury.io/js/@node-3d/addon-tools)
6
+ [![Lint](https://github.com/node-3d/addon-tools/actions/workflows/lint.yml/badge.svg)](https://github.com/node-3d/addon-tools/actions/workflows/lint.yml)
7
+ [![Test](https://github.com/node-3d/addon-tools/actions/workflows/test.yml/badge.svg)](https://github.com/node-3d/addon-tools/actions/workflows/test.yml)
8
+ [![Cpplint](https://github.com/node-3d/addon-tools/actions/workflows/cpplint.yml/badge.svg)](https://github.com/node-3d/addon-tools/actions/workflows/cpplint.yml)
9
+
10
+ ```console
11
+ npm install @node-3d/addon-tools
12
+ ```
13
+
14
+ Addon Tools provide build-time and run-time helpers for Node.js C++ addons.
15
+ - C++ shortcuts to replace repetitive code in method/class
16
+ declaration and commonly used calls (such as `console.log`).
17
+ - JS helpers to deliver the precompiled addons to end-users during npm install.
18
+ - Common Logger for both C++ and JS sides with additional control,
19
+ compared to native (`printf/cout`) and console logging.
20
+
21
+ ## include/addon-tools.hpp
22
+
23
+ Macros and helpers for C++ addons using **NAPI**.
24
+ See more detailed [docs here](doc).
25
+
26
+ Example of a C++ method definition using Addon Tools:
27
+
28
+ ```c++
29
+ // hpp:
30
+ #include <addon-tools.hpp>
31
+ DBG_EXPORT JS_METHOD(doSomething);
32
+ // cpp:
33
+ DBG_EXPORT JS_METHOD(doSomething) { NAPI_ENV;
34
+ LET_INT32_ARG(0, param0);
35
+ Napi::Value args[2] = { JS_STR("param0:"), JS_NUM(param0) };
36
+ consoleLog(env, 2, &args[0]);
37
+ RET_UNDEFINED;
38
+ }
39
+ ```
40
+
41
+ Also, ES5 class helpers allow exporting a JS class directly from C++:
42
+ generated wrappers validate the JS receiver before dispatching to the native
43
+ instance, while exposing `unwrap()` for lower-level manual probing.
44
+
45
+ ```cpp
46
+ // hpp:
47
+ #include <addon-tools.hpp>
48
+ class MyClass {
49
+ DECLARE_ES5_CLASS(MyClass, MyClass);
50
+ public:
51
+ static void init(Napi::Env env, Napi::Object exports);
52
+ explicit MyClass(const Napi::CallbackInfo& info);
53
+ ~MyClass();
54
+ private:
55
+ JS_DECLARE_GETTER(MyClass, a);
56
+ JS_DECLARE_GETTER(MyClass, b);
57
+ JS_DECLARE_METHOD(MyClass, test);
58
+ };
59
+
60
+ // cpp:
61
+ IMPLEMENT_ES5_CLASS(MyClass);
62
+
63
+ void MyClass::init(Napi::Env env, Napi::Object exports) {
64
+ Napi::Function ctor = wrap(env);
65
+ JS_ASSIGN_GETTER(a);
66
+ JS_ASSIGN_GETTER(b);
67
+ JS_ASSIGN_METHOD(test);
68
+ exports.Set("MyClass", ctor);
69
+ }
70
+
71
+ MyClass::MyClass(const Napi::CallbackInfo &info) {
72
+ super(info);
73
+ }
74
+
75
+ MyClass::~MyClass() {}
76
+
77
+ JS_IMPLEMENT_GETTER(MyClass, a) { NAPI_ENV;
78
+ RET_NUM(10);
79
+ }
80
+
81
+ JS_IMPLEMENT_GETTER(MyClass, b) { NAPI_ENV;
82
+ RET_NUM(20);
83
+ }
84
+
85
+ JS_IMPLEMENT_METHOD(MyClass, test) { NAPI_ENV;
86
+ consoleLog("test");
87
+ RET_STR("test");
88
+ }
89
+
90
+ Napi::Object init(Napi::Env env, Napi::Object exports) {
91
+ MyClass::init(env, exports);
92
+ return exports;
93
+ }
94
+
95
+ NODE_API_MODULE(myaddon, init)
96
+ ```
97
+
98
+ ## JS Addon Helpers
99
+
100
+ ### Example for an ADDON's **index.js**:
101
+
102
+ Get the platform-specific directory name to import the `ADDON.node` file.
103
+
104
+ ```js
105
+ import { createRequire } from 'node:module';
106
+ import { getBin } from '@node-3d/addon-tools';
107
+
108
+ const require = createRequire(import.meta.url);
109
+ const core = require(`./${getBin()}/ADDON`);
110
+ ```
111
+
112
+
113
+ ### Example for **binding.gyp**:
114
+
115
+ Using the include directories for both Addon Tools header
116
+ and Addon API header:
117
+
118
+ ```gyp
119
+ 'include_dirs': [
120
+ '<!@(node -e "import(\'@node-3d/addon-tools\').then((m) => m.printInclude())")',
121
+ ],
122
+ ```
123
+
124
+ > NOTE: the optional `node-addon-api` dependency is used by the `getInclude()`
125
+ helper. If not found,
126
+ the **napi.h** include path won't be a part of the returned string.
127
+
128
+ Using helpers for paths to dependency libs and own binaries:
129
+
130
+ ```gyp
131
+ 'variables': {
132
+ 'bin': '<!(node -e "import(\'@node-3d/addon-tools\').then((m) => m.printBin())")',
133
+ 'gl_include': '<!(node -p "require(\'@node-3d/deps-opengl\').include")',
134
+ 'gl_bin': '<!(node -p "require(\'@node-3d/deps-opengl\').bin")',
135
+ },
136
+ ```
137
+
138
+
139
+ ### Example of `cpbin` in **package.json :: scripts**:
140
+
141
+ Copy the addon file, for example, from `./src/build/Release/glfw.node`
142
+ to `./bin-windows/glfw.node`, but each platform uses a different folder.
143
+
144
+ ```json
145
+ "build": "cd src && node-gyp rebuild -j max --silent && node -e \"import('@node-3d/addon-tools').then((m) => m.cpbin('glfw'))\"",
146
+ "build-only": "cd src && node-gyp build -j max --silent && node -e \"import('@node-3d/addon-tools').then((m) => m.cpbin('glfw'))\"",
147
+ ```
148
+
149
+ ### Example of `cpcpplint` in **cpplint.yml**:
150
+
151
+ Since all my addons use the same codestyle, I don't keep
152
+ copies of the [CPPLINT config](utils/CPPLINT.cfg) in
153
+ every addon. If that same config fits for you,
154
+ here's how it can be used:
155
+
156
+ ```yml
157
+ - name: Run Cpplint
158
+ run: |
159
+ node -e "import('@node-3d/addon-tools').then((m) => m.cpcpplint())"
160
+ cpplint --recursive ./src/cpp
161
+ ```
162
+
163
+ ### Example of `cpclangformat` in **package.json :: scripts**:
164
+
165
+ Since all my addons use the same C++ formatting style, I don't keep
166
+ copies of the [.clang-format config](utils/.clang-format) in every addon.
167
+ If that same config fits for you, here's how it can be used:
168
+
169
+ ```json
170
+ "format:src": "node -e \"import('@node-3d/addon-tools').then((m) => m.cpclangformat())\" && clang-format -i \"src/cpp/**/*.{cpp,hpp}\"",
171
+ "format:src:ci": "node -e \"import('@node-3d/addon-tools').then((m) => m.cpclangformat())\" && clang-format --dry-run --Werror \"src/cpp/**/*.{cpp,hpp}\""
172
+ ```
173
+
174
+ ### Example of `install` in **install.js**:
175
+
176
+ Downloads the addon (for example, from GitHub releases) and places
177
+ it into a platform-specific folder.
178
+
179
+ ```js
180
+ import { install } from '@node-3d/addon-tools';
181
+
182
+ const prefix = 'https://github.com/node-3d/glfw/releases/download';
183
+ const tag = '5.5.0';
184
+ install(`${prefix}/${tag}`);
185
+ ```
186
+
187
+ ## JS Utils
188
+
189
+ JavaScript helpers for Node.js addon development. The short list of helpers:
190
+
191
+ ```js
192
+ 'getBin', 'getPlatform', 'getInclude', 'getPaths',
193
+ 'install', 'cpbin', 'download', 'copy', 'exists',
194
+ 'ensuredir', 'subdirs', 'subfiles', 'traverse',
195
+ 'rmdir', 'rm', 'actionPack', 'checkGypi',
196
+ 'createLogger', 'setLevel', 'getLevel', 'getLoggers',
197
+ ```
198
+
199
+ The public JS helpers are:
200
+
201
+ * `getPaths(dir)` - return `{ bin, include }` for a dependency package and prepend `bin`
202
+ to `PATH` on Windows.
203
+ * `getBin()`, `printBin()` - current platform binary directory, such as `bin-windows`.
204
+ * `getPlatform()`, `printPlatform()` - current platform key.
205
+ * `getInclude()`, `printInclude()` - include flags for addon-tools and optional
206
+ `node-addon-api`.
207
+ * `cpbin(name)` - copy `src/build/Release/<name>.node` into the platform `bin-*` directory.
208
+ * `cpcpplint()` - copy the shared `CPPLINT.cfg` into the current directory.
209
+ * `cpclangformat()` - copy the shared `.clang-format` into the current directory.
210
+ * `checkGypi(path?)` - verify a local `common.gypi` matches the shared one.
211
+ * `install(folderUrl)` - download and unpack `<platform>.gz` into the current platform bin dir.
212
+ * `download(url)` - fetch a URL into a `Buffer`.
213
+ * `copy`, `exists`, `ensuredir`, `subdirs`, `subfiles`, `traverse`, `rmdir`, `rm`.
214
+ * `actionPack()` - pack the current platform bin directory as `<platform>.gz`.
215
+
216
+ ### Logger:
217
+
218
+ This helper provides simple logging interface, for both JS and C++, that may be used
219
+ locally or globally.
220
+
221
+ ```js
222
+ // to `console` by default
223
+ const logger = utils.createLogger({ name: 'my-logger' });
224
+ ```
225
+
226
+ Now the following JS calls are equal:
227
+
228
+ ```js
229
+ logger.warn(1, 2, '3');
230
+ global.AddonTools.log('my-logger', 'warn', 1, 2, '3');
231
+ const { getLogger } = await import('@node-3d/addon-tools');
232
+ getLogger('my-logger').warn(1, 2, '3');
233
+ ```
234
+
235
+ And the C++ calls are:
236
+
237
+ ```cpp
238
+ globalLog(env, "my-logger", "warn", "string log message");
239
+ // or
240
+ Napi::Value args[3] = { JS_NUM(1), JS_NUM(2), JS_STR("3") };
241
+ globalLog(env, "cpp", "warn", 3, &args[0]);
242
+ ```
243
+
244
+ Logger helpers:
245
+
246
+ * `createLogger({ name, ...methods })` - create or update a named logger.
247
+ * `getLogger(name)` - get an existing logger or create one with console methods.
248
+ * `setLevel(level | null)` and `getLevel()` - control global log filtering.
249
+ * `getLoggers()` - return the logger registry snapshot.
250
+
251
+ `global.AddonTools.log(name, level, ...args)` is installed for C++ helpers that need to
252
+ route log messages back into JavaScript.
@@ -0,0 +1,12 @@
1
+ type TAddonPaths = {
2
+ bin: string;
3
+ include: string;
4
+ };
5
+ export declare const getPaths: (dir: string) => TAddonPaths;
6
+ export declare const getBin: () => string;
7
+ export declare const printBin: () => void;
8
+ export declare const getPlatform: () => string;
9
+ export declare const printPlatform: () => void;
10
+ export declare const getInclude: () => string;
11
+ export declare const printInclude: () => void;
12
+ export {};
@@ -0,0 +1,43 @@
1
+ import node_path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ const nameWindows = 'windows';
4
+ const platformAndArch = `${process.platform}-${process.arch}`;
5
+ const platformNames = {
6
+ 'win32-x64': nameWindows,
7
+ 'linux-x64': 'linux',
8
+ 'darwin-x64': 'osx',
9
+ 'linux-arm64': 'aarch64'
10
+ };
11
+ const platformName = platformNames[platformAndArch] || platformAndArch;
12
+ const isWindows = platformName === nameWindows;
13
+ const getPaths = (dir)=>{
14
+ const dirFws = dir.replaceAll('\\', '/');
15
+ const bin = `${dirFws}/bin-${platformName}`;
16
+ const include = `${dirFws}/include`;
17
+ if (isWindows) process.env.path = `${bin};${process.env.path ? `${process.env.path}` : ''}`;
18
+ return {
19
+ bin,
20
+ include
21
+ };
22
+ };
23
+ const getBin = ()=>`bin-${platformName}`;
24
+ const printBin = ()=>console.log(getBin());
25
+ const getPlatform = ()=>platformName;
26
+ const printPlatform = ()=>console.log(getPlatform());
27
+ const getNodeAddonApiIncludeDir = ()=>{
28
+ try {
29
+ const packageUrl = import.meta.resolve('node-addon-api/package.json');
30
+ const packageDir = node_path.dirname(fileURLToPath(packageUrl));
31
+ return node_path.relative('.', packageDir).replaceAll('\\', '/');
32
+ } catch {
33
+ return '';
34
+ }
35
+ };
36
+ const getInclude = ()=>{
37
+ const rootPath = node_path.resolve(`${import.meta.dirname}/..`).replaceAll('\\', '/');
38
+ const napiInclude = getNodeAddonApiIncludeDir();
39
+ const thisInclude = `${rootPath}/include`;
40
+ return `${napiInclude} ${thisInclude}`.trim();
41
+ };
42
+ const printInclude = ()=>console.log(getInclude());
43
+ export { getBin, getInclude, getPaths, getPlatform, printBin, printInclude, printPlatform };
@@ -0,0 +1,2 @@
1
+ export * from './include.ts';
2
+ export * from './utils/index.ts';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./include.js";
2
+ export * from "./utils/index.js";
3
+ globalThis.AddonTools ??= {};
@@ -0,0 +1 @@
1
+ export declare const actionPack: () => Promise<void>;
@@ -0,0 +1,16 @@
1
+ import { promisify } from "node:util";
2
+ import { exec } from "node:child_process";
3
+ import { getBin, getPlatform } from "../include.js";
4
+ import { getLogger } from "./logger.js";
5
+ const action_pack_exec = promisify(exec);
6
+ const logger = getLogger('addon-tools');
7
+ const actionPack = async ()=>{
8
+ try {
9
+ await action_pack_exec(`cd ${getBin()} && tar -czf ../${getPlatform()}.gz *`);
10
+ logger.log(`pack=${getPlatform()}.gz`);
11
+ } catch (error) {
12
+ logger.error(error);
13
+ process.exitCode = 1;
14
+ }
15
+ };
16
+ export { actionPack };
@@ -0,0 +1 @@
1
+ export declare const checkGypi: (localPath?: string) => void;
@@ -0,0 +1,10 @@
1
+ import { readFileSync } from "node:fs";
2
+ import node_path from "node:path";
3
+ const normalize = (text)=>text.replaceAll('\r\n', '\n');
4
+ const checkGypi = (localPath = 'src/common.gypi')=>{
5
+ const canonicalPath = node_path.join(import.meta.dirname, '../../utils/common.gypi');
6
+ const local = normalize(readFileSync(localPath, 'utf8'));
7
+ const canonical = normalize(readFileSync(canonicalPath, 'utf8'));
8
+ if (local !== canonical) throw new Error(`${localPath} does not match ${canonicalPath}`);
9
+ };
10
+ export { checkGypi };
@@ -0,0 +1 @@
1
+ export declare const cpbin: (name: string) => Promise<void>;
@@ -0,0 +1,18 @@
1
+ import promises from "node:fs/promises";
2
+ import { getBin } from "../include.js";
3
+ import { copy, exists, rm } from "./files.js";
4
+ import { getLogger } from "./logger.js";
5
+ const logger = getLogger('addon-tools');
6
+ const cpbin = async (name)=>{
7
+ const srcDir = process.cwd().replaceAll('\\', '/');
8
+ if (!await exists(`${srcDir}/build/Release/${name}.node`)) logger.error(`Error. File "${srcDir}/build/Release/${name}.node" not found.`);
9
+ const binAbs = `${srcDir}/../${getBin()}`;
10
+ if (!await exists(binAbs)) await promises.mkdir(binAbs, {
11
+ recursive: true
12
+ });
13
+ const destAbs = `${binAbs}/${name}.node`;
14
+ if (await exists(destAbs)) await rm(destAbs);
15
+ await copy(`${srcDir}/build/Release/${name}.node`, destAbs);
16
+ logger.log(`The binary "${name}.node" was copied to "${getBin()}".`);
17
+ };
18
+ export { cpbin };
@@ -0,0 +1 @@
1
+ export declare const cpclangformat: () => Promise<void>;
@@ -0,0 +1,11 @@
1
+ import { copy, exists } from "./files.js";
2
+ import { getLogger } from "./logger.js";
3
+ const logger = getLogger('addon-tools');
4
+ const cpclangformat = async ()=>{
5
+ const configDest = `${process.cwd()}/.clang-format`.replaceAll('\\', '/');
6
+ const configSrc = `${import.meta.dirname}/../../utils/.clang-format`.replaceAll('\\', '/');
7
+ if (!await exists(configSrc)) return void logger.error('Error. File ".clang-format" not found.');
8
+ await copy(configSrc, configDest);
9
+ logger.log(`".clang-format" was copied to "${configDest}".`);
10
+ };
11
+ export { cpclangformat };
@@ -0,0 +1 @@
1
+ export declare const cpcpplint: () => Promise<void>;
@@ -0,0 +1,12 @@
1
+ import { copy, exists } from "./files.js";
2
+ import { getLogger } from "./logger.js";
3
+ const logger = getLogger('addon-tools');
4
+ const cpcpplint = async ()=>{
5
+ const cpplintDest = `${process.cwd()}/CPPLINT.cfg`.replaceAll('\\', '/');
6
+ const cpplintSrc = `${import.meta.dirname}/../../utils/CPPLINT.cfg`.replaceAll('\\', '/');
7
+ if (!await exists(cpplintSrc)) return void logger.error('Error. File "CPPLINT.cfg" not found.');
8
+ if (await exists(cpplintDest)) logger.warn('Warning. Dest "CPPLINT.cfg" exists and will be overwritten.');
9
+ await copy(cpplintSrc, cpplintDest);
10
+ logger.log(`"CPPLINT.cfg" was copied to "${cpplintDest}".`);
11
+ };
12
+ export { cpcpplint };
@@ -0,0 +1 @@
1
+ export declare const download: (url: string) => Promise<Buffer>;
@@ -0,0 +1,6 @@
1
+ const download = async (url)=>{
2
+ const response = await fetch(url);
3
+ if (!response.ok) throw new Error(`Response status was ${response.status}`);
4
+ return Buffer.from(await response.arrayBuffer());
5
+ };
6
+ export { download };
@@ -0,0 +1,8 @@
1
+ export declare const copy: (src: string, dest: string) => Promise<void>;
2
+ export declare const exists: (name: string) => Promise<boolean>;
3
+ export declare const ensuredir: (dir: string) => Promise<void>;
4
+ export declare const subdirs: (name: string) => Promise<string[]>;
5
+ export declare const subfiles: (name: string) => Promise<string[]>;
6
+ export declare const traverse: (name: string, showDirs?: boolean) => Promise<string[]>;
7
+ export declare const rmdir: (name: string) => Promise<void>;
8
+ export declare const rm: (name: string) => Promise<void>;
@@ -0,0 +1,57 @@
1
+ import promises from "node:fs/promises";
2
+ import { getLogger } from "./logger.js";
3
+ const logger = getLogger('addon-tools');
4
+ const copy = async (src, dest)=>{
5
+ try {
6
+ await promises.copyFile(src, dest);
7
+ } catch (error) {
8
+ if (!(error instanceof Error && 'code' in error && 'EBUSY' === error.code)) logger.warn('WARNING\n', error);
9
+ }
10
+ };
11
+ const exists = async (name)=>{
12
+ try {
13
+ await promises.access(name);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ };
19
+ const ensuredir = async (dir)=>{
20
+ if (!dir) return;
21
+ await promises.mkdir(dir, {
22
+ recursive: true
23
+ });
24
+ };
25
+ const subdirs = async (name)=>{
26
+ const all = await promises.readdir(name, {
27
+ withFileTypes: true
28
+ });
29
+ return all.filter((dirent)=>dirent.isDirectory()).map((dirent)=>dirent.name);
30
+ };
31
+ const subfiles = async (name)=>{
32
+ const all = await promises.readdir(name, {
33
+ withFileTypes: true
34
+ });
35
+ return all.filter((dirent)=>dirent.isFile()).map((dirent)=>`${name}/${dirent.name}`);
36
+ };
37
+ const traverse = async (name, showDirs = false)=>{
38
+ const subdirNames = await subdirs(name);
39
+ const dirs = subdirNames.map((dir)=>`${name}/${dir}`);
40
+ const nestedItems = await Promise.all(dirs.map((dir)=>traverse(dir, showDirs)));
41
+ const nested = nestedItems.flat();
42
+ const files = await subfiles(name);
43
+ return [
44
+ ...showDirs ? dirs : [],
45
+ ...nested,
46
+ ...files
47
+ ];
48
+ };
49
+ const rmdir = async (name)=>{
50
+ if (await exists(name)) await promises.rm(name, {
51
+ recursive: true
52
+ });
53
+ };
54
+ const rm = async (name)=>{
55
+ if (await exists(name)) await promises.rm(name);
56
+ };
57
+ export { copy, ensuredir, exists, rm, rmdir, subdirs, subfiles, traverse };
@@ -0,0 +1,9 @@
1
+ export * from './action-pack.ts';
2
+ export * from './check-gypi.ts';
3
+ export * from './cpbin.ts';
4
+ export * from './cpclangformat.ts';
5
+ export * from './cpcpplint.ts';
6
+ export * from './download.ts';
7
+ export * from './files.ts';
8
+ export * from './install.ts';
9
+ export * from './logger.ts';
@@ -0,0 +1,9 @@
1
+ export * from "./action-pack.js";
2
+ export * from "./check-gypi.js";
3
+ export * from "./cpbin.js";
4
+ export * from "./cpclangformat.js";
5
+ export * from "./cpcpplint.js";
6
+ export * from "./download.js";
7
+ export * from "./files.js";
8
+ export * from "./install.js";
9
+ export * from "./logger.js";
@@ -0,0 +1 @@
1
+ export declare const install: (folderUrl: string) => Promise<boolean>;
@@ -0,0 +1,43 @@
1
+ import { exec } from "node:child_process";
2
+ import promises from "node:fs/promises";
3
+ import { promisify } from "node:util";
4
+ import { getBin, getPlatform } from "../include.js";
5
+ import { exists, rm, rmdir } from "./files.js";
6
+ import { getLogger } from "./logger.js";
7
+ const install_exec = promisify(exec);
8
+ const logger = getLogger('addon-tools');
9
+ const download = async (url)=>{
10
+ const { stderr } = await install_exec([
11
+ 'curl -sL -o',
12
+ `${getBin()}/${getPlatform()}.gz`,
13
+ url
14
+ ].join(' '));
15
+ if (stderr) logger.warn(stderr);
16
+ };
17
+ const unpack = async (gzPath, binPath)=>{
18
+ const { stderr } = await install_exec(`tar -xzf ${gzPath} --directory ${binPath}`);
19
+ if (stderr) logger.warn(stderr);
20
+ };
21
+ const install = async (folderUrl)=>{
22
+ const binPath = getBin();
23
+ const urlPath = `${folderUrl}/${getPlatform()}.gz`;
24
+ const gzPath = `${binPath}/${getPlatform()}.gz`;
25
+ await rmdir(binPath);
26
+ await promises.mkdir(binPath, {
27
+ recursive: true
28
+ });
29
+ try {
30
+ await download(urlPath);
31
+ if (!await exists(gzPath)) {
32
+ logger.warn(`Could not download "${urlPath}" to "${gzPath}"`);
33
+ return false;
34
+ }
35
+ await unpack(gzPath, binPath);
36
+ } catch (error) {
37
+ logger.warn(error);
38
+ return false;
39
+ }
40
+ await rm(gzPath);
41
+ return true;
42
+ };
43
+ export { install };
@@ -0,0 +1,21 @@
1
+ type LoggerFn = (...args: unknown[]) => void;
2
+ type LoggerLevel = 'error' | 'warn' | 'info' | 'log' | 'debug';
3
+ type LoggerLevelOrNull = LoggerLevel | null;
4
+ type Logger = Record<LoggerLevel, LoggerFn> & {
5
+ replace: (level: string, fn: LoggerFn) => void;
6
+ };
7
+ type LoggerOptions = Partial<Record<LoggerLevel, LoggerFn>> & {
8
+ name: string;
9
+ };
10
+ type AddonToolsGlobal = {
11
+ log?: (name: string, level: LoggerLevel, ...args: unknown[]) => void;
12
+ };
13
+ declare global {
14
+ var AddonTools: AddonToolsGlobal | undefined;
15
+ }
16
+ export declare const createLogger: (opts: LoggerOptions) => Logger;
17
+ export declare const setLevel: (levelOrNull: LoggerLevelOrNull) => void;
18
+ export declare const getLevel: () => LoggerLevelOrNull;
19
+ export declare const getLoggers: () => Record<string, Logger>;
20
+ export declare const getLogger: (name: string) => Logger;
21
+ export {};
@@ -0,0 +1,62 @@
1
+ global.AddonTools ??= {};
2
+ const loggers = {};
3
+ const levels = [
4
+ null,
5
+ 'error',
6
+ 'warn',
7
+ 'info',
8
+ 'log',
9
+ 'debug'
10
+ ];
11
+ let currentLevel = 'log';
12
+ const levelIdx = {};
13
+ for(let i = 0; i < levels.length; i++)levelIdx[levels[i] ?? 'null'] = i;
14
+ const wrapOutput = (outputFn, level)=>(...args)=>{
15
+ const outputLevel = levelIdx[level] ?? 0;
16
+ const activeLevel = levelIdx[currentLevel ?? 'null'] ?? 0;
17
+ if (outputLevel > activeLevel) return;
18
+ outputFn(...args);
19
+ };
20
+ const isLoggerLevel = (value)=>'error' === value || 'warn' === value || 'info' === value || 'log' === value || 'debug' === value;
21
+ const assignMethods = (logger, methods)=>{
22
+ for (const [k, v] of Object.entries(methods))if (isLoggerLevel(k) && v) logger.replace(k, v);
23
+ };
24
+ const createLogger = (opts)=>{
25
+ const prev = loggers[opts.name];
26
+ if (prev) {
27
+ assignMethods(prev, opts);
28
+ return prev;
29
+ }
30
+ const newLogger = {
31
+ debug: console.debug,
32
+ log: console.log,
33
+ info: console.info,
34
+ warn: console.warn,
35
+ error: console.error,
36
+ replace: (level, fn)=>{
37
+ if (levelIdx[level]) newLogger[level] = wrapOutput(fn || console.log, level);
38
+ }
39
+ };
40
+ assignMethods(newLogger, opts);
41
+ loggers[opts.name] = newLogger;
42
+ return newLogger;
43
+ };
44
+ const setLevel = (levelOrNull)=>{
45
+ if (levels.includes(levelOrNull)) currentLevel = levelOrNull;
46
+ };
47
+ const getLevel = ()=>currentLevel;
48
+ const getLoggers = ()=>({
49
+ ...loggers
50
+ });
51
+ const getLogger = (name)=>loggers[name] || createLogger({
52
+ name
53
+ });
54
+ if (!global.AddonTools.log) global.AddonTools.log = (name, level, ...args)=>{
55
+ const logger = loggers[name];
56
+ if (!logger) return;
57
+ logger[level](...args);
58
+ };
59
+ createLogger({
60
+ name: 'addon-tools'
61
+ });
62
+ export { createLogger, getLevel, getLogger, getLoggers, setLevel };
@@ -0,0 +1,3 @@
1
+ import type { Oxfmtrc } from 'oxfmt';
2
+ declare const _default: Oxfmtrc;
3
+ export default _default;