@ya-modbus/driver-loader 0.8.0 → 0.9.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.
@@ -0,0 +1,37 @@
1
+ import { ValidationError } from './errors.js';
2
+ /**
3
+ * Describes the allowed driver package names, for error messages.
4
+ * Written to follow "name must be ...", so callers can put their own words in front.
5
+ */
6
+ export const DRIVER_PACKAGE_NAME_RULE = 'must be @ya-modbus/driver-<name>, ya-modbus-driver-<name> or @<scope>/ya-modbus-driver-<name>, ' +
7
+ 'where <name> and <scope> use only lowercase letters, numbers and hyphens';
8
+ /**
9
+ * The three allowed forms. Only lowercase letters, numbers and hyphens can follow the prefix,
10
+ * so a name can never contain a path separator, a dot, a backslash or a whitespace.
11
+ */
12
+ const DRIVER_PACKAGE_NAME_PATTERN = /^(?:@ya-modbus\/driver-|(?:@[a-z0-9][a-z0-9-]*\/)?ya-modbus-driver-)[a-z0-9-]+$/;
13
+ /**
14
+ * Check a driver package name against the shared naming rule for drivers that are loaded
15
+ * from a configured name, such as in the MQTT bridge.
16
+ *
17
+ * Allowed forms: `@ya-modbus/driver-<name>`, `ya-modbus-driver-<name>` and
18
+ * `@<scope>/ya-modbus-driver-<name>`.
19
+ *
20
+ * @param name - Value to check
21
+ * @returns true if the value is an allowed driver package name
22
+ */
23
+ export function isValidDriverPackageName(name) {
24
+ return typeof name === 'string' && DRIVER_PACKAGE_NAME_PATTERN.test(name);
25
+ }
26
+ /**
27
+ * Throw if a driver package name does not follow the naming rule.
28
+ *
29
+ * @param name - Driver package name to check
30
+ * @throws ValidationError (field `driverPackage`) listing the allowed forms
31
+ */
32
+ export function assertValidDriverPackageName(name) {
33
+ if (!isValidDriverPackageName(name)) {
34
+ throw new ValidationError(`Invalid driver package name ${JSON.stringify(name)}: ${DRIVER_PACKAGE_NAME_RULE}`, 'driverPackage');
35
+ }
36
+ }
37
+ //# sourceMappingURL=driver-package-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driver-package-name.js","sourceRoot":"","sources":["../../../src/driver-package-name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE7C;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GACnC,iGAAiG;IACjG,0EAA0E,CAAA;AAE5E;;;GAGG;AACH,MAAM,2BAA2B,GAC/B,iFAAiF,CAAA;AAEnF;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAa;IACpD,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC3E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAAY;IACvD,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,eAAe,CACvB,+BAA+B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,wBAAwB,EAAE,EAClF,eAAe,CAChB,CAAA;IACH,CAAC;AACH,CAAC"}
@@ -1,5 +1,8 @@
1
1
  export { loadDriver, clearDriverCache, getDriverCacheStats } from './loader.js';
2
2
  export type { LoadedDriver, LoadDriverOptions, SystemDependencies, DriverCacheStats, Logger, } from './loader.js';
3
3
  export { validateDefaultConfig, validateSupportedConfig, validateDevices, crossValidateConfigs, } from './config-validator.js';
4
+ export { DRIVER_PACKAGE_NAME_RULE, assertValidDriverPackageName, isValidDriverPackageName, } from './driver-package-name.js';
4
5
  export { ValidationError, DriverNotFoundError, PackageJsonError } from './errors.js';
6
+ export { getCallerPackageInfo } from './package-info.js';
7
+ export type { PackageInfo } from './package-info.js';
5
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAC/E,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,MAAM,GACP,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,GACrB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAC/E,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,MAAM,GACP,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,GACrB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,wBAAwB,EACxB,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA"}
@@ -1,4 +1,6 @@
1
1
  export { loadDriver, clearDriverCache, getDriverCacheStats } from './loader.js';
2
2
  export { validateDefaultConfig, validateSupportedConfig, validateDevices, crossValidateConfigs, } from './config-validator.js';
3
+ export { DRIVER_PACKAGE_NAME_RULE, assertValidDriverPackageName, isValidDriverPackageName, } from './driver-package-name.js';
3
4
  export { ValidationError, DriverNotFoundError, PackageJsonError } from './errors.js';
5
+ export { getCallerPackageInfo } from './package-info.js';
4
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAQ/E,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,GACrB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAQ/E,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,GACrB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,wBAAwB,EACxB,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA"}
@@ -59,7 +59,7 @@ export interface Logger {
59
59
  export interface LoadDriverOptions {
60
60
  /**
61
61
  * Explicit driver package name
62
- * e.g., 'ya-modbus-driver-xymd1' or '@org/driver-pkg'
62
+ * e.g., 'ya-modbus-driver-xymd1' or '@org/ya-modbus-driver-xymd1'
63
63
  */
64
64
  driverPackage?: string;
65
65
  /**
@@ -0,0 +1,43 @@
1
+ export interface PackageInfo {
2
+ version: string;
3
+ description: string;
4
+ }
5
+ type ReadTextFile = (path: string) => string;
6
+ /**
7
+ * Read the version and description from the closest package.json above a file
8
+ *
9
+ * package.json files without a version are skipped. This passes over the
10
+ * `{"type": "module"}` and `{"type": "commonjs"}` markers in `dist/esm` and `dist/cjs`.
11
+ *
12
+ * @param file - Path or `file:` URL of a module inside the package
13
+ * @param readFile - Reads a file as text and throws if it does not exist (replaceable in tests)
14
+ * @throws Error if no package.json with a version is found
15
+ */
16
+ export declare function readPackageInfo(file: string, readFile?: ReadTextFile): PackageInfo;
17
+ /**
18
+ * Pick the calling module from the file names of a stack, innermost first
19
+ *
20
+ * The first frame is this module. The caller is the first frame from another file that is a
21
+ * real module path or `file:` URL, which skips frames such as `node:internal/timers`.
22
+ *
23
+ * @throws Error if the stack has no such frame
24
+ */
25
+ export declare function findCallerFile(files: ReadonlyArray<string | undefined>): string;
26
+ /**
27
+ * Get the version and description of the package that contains the calling module
28
+ *
29
+ * Lets a CLI report its own package.json values without hardcoding them. The caller's
30
+ * location comes from the call stack, so it works the same in ES module and CommonJS
31
+ * builds, where `import.meta.url` and `__dirname` are each available in only one.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const { version } = getCallerPackageInfo()
36
+ * program.version(version)
37
+ * ```
38
+ *
39
+ * @throws Error if the calling module cannot be determined or has no package.json with a version
40
+ */
41
+ export declare function getCallerPackageInfo(): PackageInfo;
42
+ export {};
43
+ //# sourceMappingURL=package-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-info.d.ts","sourceRoot":"","sources":["../../../src/package-info.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;CACpB;AAwBD,KAAK,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;AAiB5C;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,YAA2B,GAAG,WAAW,CAehG;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,CAY/E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,IAAI,WAAW,CAIlD"}
@@ -0,0 +1,96 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, isAbsolute, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const MIN_STACK_TRACE_LIMIT = 10;
5
+ /**
6
+ * Call sites of the current stack, innermost first.
7
+ *
8
+ * V8 formats `stack` lazily, so it must be read while the formatter is replaced.
9
+ */
10
+ function getCallSites() {
11
+ // eslint-disable-next-line @typescript-eslint/unbound-method -- only restored, never called
12
+ const originalPrepare = Error.prepareStackTrace;
13
+ const originalLimit = Error.stackTraceLimit;
14
+ try {
15
+ Error.prepareStackTrace = (_error, callSites) => callSites;
16
+ // An application can lower the limit so far that the caller is not on the stack
17
+ Error.stackTraceLimit = Math.max(originalLimit, MIN_STACK_TRACE_LIMIT);
18
+ return new Error().stack;
19
+ }
20
+ finally {
21
+ Error.prepareStackTrace = originalPrepare;
22
+ Error.stackTraceLimit = originalLimit;
23
+ }
24
+ }
25
+ const readUtf8File = (path) => readFileSync(path, 'utf-8');
26
+ function readPackageJson(path, readFile) {
27
+ let content;
28
+ try {
29
+ content = readFile(path);
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ return JSON.parse(content);
35
+ }
36
+ /**
37
+ * Read the version and description from the closest package.json above a file
38
+ *
39
+ * package.json files without a version are skipped. This passes over the
40
+ * `{"type": "module"}` and `{"type": "commonjs"}` markers in `dist/esm` and `dist/cjs`.
41
+ *
42
+ * @param file - Path or `file:` URL of a module inside the package
43
+ * @param readFile - Reads a file as text and throws if it does not exist (replaceable in tests)
44
+ * @throws Error if no package.json with a version is found
45
+ */
46
+ export function readPackageInfo(file, readFile = readUtf8File) {
47
+ const path = file.startsWith('file:') ? fileURLToPath(file) : file;
48
+ for (let dir = dirname(path);; dir = dirname(dir)) {
49
+ const packageJson = readPackageJson(join(dir, 'package.json'), readFile);
50
+ if (typeof packageJson?.version === 'string') {
51
+ return {
52
+ version: packageJson.version,
53
+ description: typeof packageJson.description === 'string' ? packageJson.description : '',
54
+ };
55
+ }
56
+ if (dirname(dir) === dir) {
57
+ throw new Error(`No package.json with a version found for ${path}`);
58
+ }
59
+ }
60
+ }
61
+ /**
62
+ * Pick the calling module from the file names of a stack, innermost first
63
+ *
64
+ * The first frame is this module. The caller is the first frame from another file that is a
65
+ * real module path or `file:` URL, which skips frames such as `node:internal/timers`.
66
+ *
67
+ * @throws Error if the stack has no such frame
68
+ */
69
+ export function findCallerFile(files) {
70
+ const [ownFile] = files;
71
+ const callerFile = files.find((file) => file !== undefined && file !== ownFile && (file.startsWith('file:') || isAbsolute(file)));
72
+ if (callerFile === undefined) {
73
+ throw new Error('Cannot determine the calling module');
74
+ }
75
+ return callerFile;
76
+ }
77
+ /**
78
+ * Get the version and description of the package that contains the calling module
79
+ *
80
+ * Lets a CLI report its own package.json values without hardcoding them. The caller's
81
+ * location comes from the call stack, so it works the same in ES module and CommonJS
82
+ * builds, where `import.meta.url` and `__dirname` are each available in only one.
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const { version } = getCallerPackageInfo()
87
+ * program.version(version)
88
+ * ```
89
+ *
90
+ * @throws Error if the calling module cannot be determined or has no package.json with a version
91
+ */
92
+ export function getCallerPackageInfo() {
93
+ const files = getCallSites().map((callSite) => callSite.getFileName() ?? undefined);
94
+ return readPackageInfo(findCallerFile(files));
95
+ }
96
+ //# sourceMappingURL=package-info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-info.js","sourceRoot":"","sources":["../../../src/package-info.ts"],"names":[],"mappings":"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"}