@hahnpro/flow-sdk 2026.7.8 → 2026.7.10
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/CHANGELOG.md +4 -0
- package/package.json +5 -4
- package/src/index.d.ts +1 -0
- package/src/index.js +3 -1
- package/src/lib/archive.d.ts +4 -0
- package/src/lib/archive.js +144 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @hahnpro/flow-sdk
|
|
2
2
|
|
|
3
|
+
## 2026.7.9
|
|
4
|
+
|
|
5
|
+
- Added `extractZip(zipPath, dir)` for extracting a ZIP archive into an absolute target directory. Replaces the unmaintained `extract-zip` package, which stalls mid-entry on Node 26 and leaves the returned promise pending forever
|
|
6
|
+
|
|
3
7
|
## 2026.6.1
|
|
4
8
|
|
|
5
9
|
- Fixed sdk api type in FlowApplication
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hahnpro/flow-sdk",
|
|
3
|
-
"version": "2026.7.
|
|
3
|
+
"version": "2026.7.10",
|
|
4
4
|
"description": "SDK for building Flow Modules",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@hahnpro/hpc-api": "2026.7.
|
|
20
|
+
"@hahnpro/hpc-api": "2026.7.10",
|
|
21
21
|
"@nats-io/jetstream": "3.4.0",
|
|
22
22
|
"@nats-io/nats-core": "3.4.0",
|
|
23
23
|
"@nats-io/transport-node": "3.4.0",
|
|
@@ -29,11 +29,12 @@
|
|
|
29
29
|
"python-shell": "5.0.0",
|
|
30
30
|
"reflect-metadata": "0.2.2",
|
|
31
31
|
"rxjs": "7.8.2",
|
|
32
|
-
"string-interp": "0.3.6"
|
|
32
|
+
"string-interp": "0.3.6",
|
|
33
|
+
"yauzl": "3.4.0"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"@types/lodash": "4.17.24",
|
|
36
|
-
"@types/node": "
|
|
37
|
+
"@types/node": "24.13.3",
|
|
37
38
|
"class-validator-jsonschema": "5.1.0",
|
|
38
39
|
"typescript": "6.0.3"
|
|
39
40
|
},
|
package/src/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from '@hahnpro/hpc-api';
|
|
2
2
|
export { LifecycleEvent, type ClassType, type DeploymentMessage, type Flow, type FlowContext, type FlowElementContext, type StreamOptions, } from './lib/flow.interface';
|
|
3
3
|
export * from './lib/utils';
|
|
4
|
+
export { extractZip } from './lib/archive';
|
|
4
5
|
export * from './lib/FlowApplication';
|
|
5
6
|
export { FlowDashboard, FlowElement, FlowFunction, FlowResource, FlowTask, FlowTrigger, InputStream } from './lib/FlowElement';
|
|
6
7
|
export * from './lib/FlowEvent';
|
package/src/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.IncompatableWith = exports.FlowModule = exports.InputStream = exports.FlowTrigger = exports.FlowTask = exports.FlowResource = exports.FlowFunction = exports.FlowElement = exports.FlowDashboard = exports.LifecycleEvent = void 0;
|
|
3
|
+
exports.IncompatableWith = exports.FlowModule = exports.InputStream = exports.FlowTrigger = exports.FlowTask = exports.FlowResource = exports.FlowFunction = exports.FlowElement = exports.FlowDashboard = exports.extractZip = exports.LifecycleEvent = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
tslib_1.__exportStar(require("@hahnpro/hpc-api"), exports);
|
|
6
6
|
var flow_interface_1 = require("./lib/flow.interface");
|
|
7
7
|
Object.defineProperty(exports, "LifecycleEvent", { enumerable: true, get: function () { return flow_interface_1.LifecycleEvent; } });
|
|
8
8
|
tslib_1.__exportStar(require("./lib/utils"), exports);
|
|
9
|
+
var archive_1 = require("./lib/archive");
|
|
10
|
+
Object.defineProperty(exports, "extractZip", { enumerable: true, get: function () { return archive_1.extractZip; } });
|
|
9
11
|
tslib_1.__exportStar(require("./lib/FlowApplication"), exports);
|
|
10
12
|
var FlowElement_1 = require("./lib/FlowElement");
|
|
11
13
|
Object.defineProperty(exports, "FlowDashboard", { enumerable: true, get: function () { return FlowElement_1.FlowDashboard; } });
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractZip = extractZip;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const promises_1 = require("node:stream/promises");
|
|
8
|
+
const yauzl_1 = tslib_1.__importDefault(require("yauzl"));
|
|
9
|
+
const S_IFMT = 0o170000;
|
|
10
|
+
const S_IFDIR = 0o040000;
|
|
11
|
+
const S_IFLNK = 0o120000;
|
|
12
|
+
const DEFAULT_DIR_MODE = 0o755;
|
|
13
|
+
const DEFAULT_FILE_MODE = 0o644;
|
|
14
|
+
const WRITE_FLAGS = node_fs_1.constants.O_WRONLY | node_fs_1.constants.O_CREAT | node_fs_1.constants.O_TRUNC | (node_fs_1.constants.O_NOFOLLOW ?? 0);
|
|
15
|
+
const openZip = (zipPath) => new Promise((resolve, reject) => {
|
|
16
|
+
yauzl_1.default.open(zipPath, { lazyEntries: true }, (err, zipfile) => (err ? reject(err) : resolve(zipfile)));
|
|
17
|
+
});
|
|
18
|
+
const openReadStream = (zipfile, entry) => new Promise((resolve, reject) => {
|
|
19
|
+
zipfile.openReadStream(entry, (err, stream) => (err ? reject(err) : resolve(stream)));
|
|
20
|
+
});
|
|
21
|
+
const readAll = async (stream) => {
|
|
22
|
+
const chunks = [];
|
|
23
|
+
for await (const chunk of stream) {
|
|
24
|
+
chunks.push(Buffer.from(chunk));
|
|
25
|
+
}
|
|
26
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
27
|
+
};
|
|
28
|
+
const isDirectoryEntry = (entry, mode) => {
|
|
29
|
+
if ((mode & S_IFMT) === S_IFDIR) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (entry.fileName.endsWith('/')) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
return entry.versionMadeBy >> 8 === 0 && entry.externalFileAttributes === 16;
|
|
36
|
+
};
|
|
37
|
+
const outOfBounds = (path, fileName) => new Error(`Out of bound path "${path}" found while processing file ${fileName}`);
|
|
38
|
+
/** Resolves an entry against the target directory, rejecting names that point outside it. */
|
|
39
|
+
const resolveEntryPath = (targetDir, fileName) => {
|
|
40
|
+
const dest = (0, node_path_1.join)(targetDir, fileName);
|
|
41
|
+
const rel = (0, node_path_1.relative)(targetDir, dest);
|
|
42
|
+
if (rel !== '' && (rel.split(node_path_1.sep).includes('..') || (0, node_path_1.isAbsolute)(rel))) {
|
|
43
|
+
throw outOfBounds(dest, fileName);
|
|
44
|
+
}
|
|
45
|
+
return dest;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Creates the missing components of `dir` one level at a time, refusing to descend through
|
|
49
|
+
* a symlink. A recursive mkdir would instead follow a symlink planted by an earlier entry
|
|
50
|
+
* and create directories outside the target before any check could reject the path.
|
|
51
|
+
*/
|
|
52
|
+
const mkdirContained = async (targetDir, dir, fileName, mode) => {
|
|
53
|
+
const rel = (0, node_path_1.relative)(targetDir, dir);
|
|
54
|
+
if (rel === '') {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let current = targetDir;
|
|
58
|
+
for (const segment of rel.split(node_path_1.sep)) {
|
|
59
|
+
current = (0, node_path_1.join)(current, segment);
|
|
60
|
+
const stats = await node_fs_1.promises.lstat(current).catch(() => null);
|
|
61
|
+
if (stats === null) {
|
|
62
|
+
await node_fs_1.promises.mkdir(current, mode === undefined ? undefined : { mode }).catch((err) => {
|
|
63
|
+
if (err.code !== 'EEXIST') {
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
70
|
+
throw outOfBounds(current, fileName);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const extractEntry = async (zipfile, entry, targetDir, dest) => {
|
|
75
|
+
const mode = (entry.externalFileAttributes >>> 16) & 0xffff;
|
|
76
|
+
const isDir = isDirectoryEntry(entry, mode);
|
|
77
|
+
const isSymlink = (mode & S_IFMT) === S_IFLNK;
|
|
78
|
+
const effectiveMode = mode === 0 ? (isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE) : mode;
|
|
79
|
+
const procMode = effectiveMode & 0o777;
|
|
80
|
+
if (isDir) {
|
|
81
|
+
await mkdirContained(targetDir, dest, entry.fileName, procMode);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const readStream = await openReadStream(zipfile, entry);
|
|
85
|
+
if (isSymlink) {
|
|
86
|
+
await node_fs_1.promises.symlink(await readAll(readStream), dest);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const handle = await node_fs_1.promises.open(dest, WRITE_FLAGS, procMode);
|
|
90
|
+
await (0, promises_1.pipeline)(readStream, handle.createWriteStream());
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Extracts a ZIP archive into an absolute target directory
|
|
95
|
+
*/
|
|
96
|
+
async function extractZip(zipPath, dir) {
|
|
97
|
+
if (!(0, node_path_1.isAbsolute)(dir)) {
|
|
98
|
+
throw new Error('Target directory is expected to be absolute');
|
|
99
|
+
}
|
|
100
|
+
await node_fs_1.promises.mkdir(dir, { recursive: true });
|
|
101
|
+
const targetDir = await node_fs_1.promises.realpath(dir);
|
|
102
|
+
const zipfile = await openZip(zipPath);
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
let canceled = false;
|
|
105
|
+
const fail = (err) => {
|
|
106
|
+
if (canceled) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
canceled = true;
|
|
110
|
+
zipfile.close();
|
|
111
|
+
reject(err);
|
|
112
|
+
};
|
|
113
|
+
zipfile.on('error', (err) => {
|
|
114
|
+
canceled = true;
|
|
115
|
+
reject(err);
|
|
116
|
+
});
|
|
117
|
+
zipfile.on('close', () => {
|
|
118
|
+
if (!canceled) {
|
|
119
|
+
resolve();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
zipfile.on('entry', (entry) => {
|
|
123
|
+
if (canceled) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (entry.fileName.startsWith('__MACOSX/')) {
|
|
127
|
+
zipfile.readEntry();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
void (async () => {
|
|
131
|
+
try {
|
|
132
|
+
const dest = resolveEntryPath(targetDir, entry.fileName);
|
|
133
|
+
await mkdirContained(targetDir, (0, node_path_1.dirname)(dest), entry.fileName);
|
|
134
|
+
await extractEntry(zipfile, entry, targetDir, dest);
|
|
135
|
+
zipfile.readEntry();
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
fail(err);
|
|
139
|
+
}
|
|
140
|
+
})();
|
|
141
|
+
});
|
|
142
|
+
zipfile.readEntry();
|
|
143
|
+
});
|
|
144
|
+
}
|