@nwutils/builder 0.1.3 → 0.1.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/package.json +4 -4
- package/src/main.js +2 -16
- package/src/util.js +164 -0
- package/types/main.d.ts +1 -1
- package/types/util.d.ts +35 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nwutils/builder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Build NW.js applications for Linux, MacOS and Windows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"NW.js",
|
|
@@ -46,13 +46,13 @@
|
|
|
46
46
|
"test": "node --test \"../../tests/specs/builder/*.test.js\""
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"
|
|
50
|
-
"glob": "^13.0.6",
|
|
49
|
+
"glob": "^14.0.3",
|
|
51
50
|
"pe-library": "^2.0.1",
|
|
52
51
|
"plist": "^5.0.0",
|
|
53
52
|
"resedit": "^3.1.0",
|
|
54
53
|
"semver": "^7.8.5",
|
|
55
|
-
"tar": "^7.5.22"
|
|
54
|
+
"tar": "^7.5.22",
|
|
55
|
+
"zip-stream": "^7.0.5"
|
|
56
56
|
},
|
|
57
57
|
"devEngines": {
|
|
58
58
|
"runtime": {
|
package/src/main.js
CHANGED
|
@@ -4,7 +4,6 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
|
|
7
|
-
import { ZipArchive } from "archiver";
|
|
8
7
|
import * as resedit from "resedit";
|
|
9
8
|
// pe-library is a direct dependency of resedit
|
|
10
9
|
import * as peLibrary from "pe-library";
|
|
@@ -149,16 +148,7 @@ async function build({
|
|
|
149
148
|
await fs.promises.mkdir(nwProjectDir, { recursive: true });
|
|
150
149
|
|
|
151
150
|
if (glob) {
|
|
152
|
-
|
|
153
|
-
const stats = await fs.promises.stat(file);
|
|
154
|
-
if (stats.isDirectory()) {
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
await fs.promises.cp(file, path.resolve(nwProjectDir, file), {
|
|
158
|
-
recursive: true,
|
|
159
|
-
force: true,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
151
|
+
await util.copyGlobFiles(/** @type {string[]} */ (files), nwProjectDir);
|
|
162
152
|
} else {
|
|
163
153
|
await fs.promises.cp(
|
|
164
154
|
/** @type {string} */ (files),
|
|
@@ -404,11 +394,7 @@ const setWinConfig = async ({ app, outDir }) => {
|
|
|
404
394
|
*/
|
|
405
395
|
const compress = async ({ zip, outDir }) => {
|
|
406
396
|
if (zip === true || zip === "zip") {
|
|
407
|
-
|
|
408
|
-
const writeStream = fs.createWriteStream(`${outDir}.zip`);
|
|
409
|
-
archive.pipe(writeStream);
|
|
410
|
-
archive.directory(outDir, false);
|
|
411
|
-
await archive.finalize();
|
|
397
|
+
await util.zipDirectory(outDir, `${outDir}.zip`);
|
|
412
398
|
} else if (zip === "tar") {
|
|
413
399
|
await tar.create(
|
|
414
400
|
{
|
package/src/util.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { pipeline } from "node:stream/promises";
|
|
5
|
+
|
|
1
6
|
import * as GlobModule from "glob";
|
|
7
|
+
import ZipStream from "zip-stream";
|
|
2
8
|
|
|
3
9
|
const PLATFORM_KV = {
|
|
4
10
|
darwin: "osx",
|
|
@@ -44,8 +50,166 @@ async function globFiles({ srcDir, glob }) {
|
|
|
44
50
|
return files;
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Recursively list a directory without following symlinks. A directory is
|
|
55
|
+
* yielded before its contents, and entries are sorted so output is stable.
|
|
56
|
+
* @param {string} absDir - directory to walk
|
|
57
|
+
* @param {string} relDir - path of `absDir` relative to the walk root, using `/`
|
|
58
|
+
* @returns {AsyncGenerator<{ absolute: string, relative: string, stats: fs.Stats }>}
|
|
59
|
+
*/
|
|
60
|
+
async function* walkDirectory(absDir, relDir) {
|
|
61
|
+
const names = (await fs.promises.readdir(absDir)).sort();
|
|
62
|
+
for (const name of names) {
|
|
63
|
+
const absolute = path.join(absDir, name);
|
|
64
|
+
const relative = relDir === "" ? name : `${relDir}/${name}`;
|
|
65
|
+
const stats = await fs.promises.lstat(absolute);
|
|
66
|
+
yield { absolute, relative, stats };
|
|
67
|
+
if (stats.isDirectory()) {
|
|
68
|
+
yield* walkDirectory(absolute, relative);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Zip the contents of a directory, so its children sit at the archive root.
|
|
75
|
+
*
|
|
76
|
+
* Matches what `archiver`'s `directory(srcDir, false)` produced: directories
|
|
77
|
+
* get their own entries, symlinks are stored as symlinks (relative to the
|
|
78
|
+
* link's directory) instead of being followed, and each entry keeps its
|
|
79
|
+
* permission bits and mtime. Other file types (sockets, FIFOs) are skipped.
|
|
80
|
+
* @async
|
|
81
|
+
* @function
|
|
82
|
+
* @param {string} srcDir - directory to compress
|
|
83
|
+
* @param {string} destFile - path of the zip file to write
|
|
84
|
+
* @returns {Promise<void>} - resolves once the zip file is fully written
|
|
85
|
+
*/
|
|
86
|
+
async function zipDirectory(srcDir, destFile) {
|
|
87
|
+
const isWin = process.platform === "win32";
|
|
88
|
+
const archive = new ZipStream();
|
|
89
|
+
const written = pipeline(archive, fs.createWriteStream(destFile));
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {Buffer | fs.ReadStream} source
|
|
93
|
+
* @param {import("zip-stream").FileDataInput} data
|
|
94
|
+
* @returns {Promise<void>}
|
|
95
|
+
*/
|
|
96
|
+
const addEntry = (source, data) =>
|
|
97
|
+
new Promise((resolve, reject) => {
|
|
98
|
+
archive.entry(source, data, (error) =>
|
|
99
|
+
error ? reject(error) : resolve(),
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
for await (const { absolute, relative, stats } of walkDirectory(
|
|
105
|
+
srcDir,
|
|
106
|
+
"",
|
|
107
|
+
)) {
|
|
108
|
+
const date = stats.mtime;
|
|
109
|
+
let mode = stats.mode & (isWin ? 0o777 : 0o7777);
|
|
110
|
+
if (stats.isFile()) {
|
|
111
|
+
await addEntry(fs.createReadStream(absolute), {
|
|
112
|
+
name: relative,
|
|
113
|
+
type: "file",
|
|
114
|
+
date,
|
|
115
|
+
mode,
|
|
116
|
+
});
|
|
117
|
+
} else if (stats.isDirectory()) {
|
|
118
|
+
// stat is unreliable for directories on Windows
|
|
119
|
+
if (isWin) mode = 0o755;
|
|
120
|
+
await addEntry(Buffer.alloc(0), {
|
|
121
|
+
name: `${relative}/`,
|
|
122
|
+
type: "directory",
|
|
123
|
+
date,
|
|
124
|
+
mode,
|
|
125
|
+
});
|
|
126
|
+
} else if (stats.isSymbolicLink()) {
|
|
127
|
+
const linkDir = path.dirname(absolute);
|
|
128
|
+
const target = await fs.promises.readlink(absolute);
|
|
129
|
+
const linkname = path
|
|
130
|
+
.relative(linkDir, path.resolve(linkDir, target))
|
|
131
|
+
.replace(/\\/g, "/");
|
|
132
|
+
await addEntry(Buffer.alloc(0), {
|
|
133
|
+
name: relative,
|
|
134
|
+
type: "symlink",
|
|
135
|
+
linkname,
|
|
136
|
+
date,
|
|
137
|
+
mode,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
archive.finalize();
|
|
142
|
+
} catch (error) {
|
|
143
|
+
archive.destroy(/** @type {Error} */ (error));
|
|
144
|
+
}
|
|
145
|
+
await written;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Copy globbed files into the NW.js project directory.
|
|
150
|
+
*
|
|
151
|
+
* In glob mode the current working directory is the app root: each file keeps
|
|
152
|
+
* its path relative to `cwd`, so `package.json src/**` produces
|
|
153
|
+
* `package.json` and `src/...` at the top of `destDir`. Absolute matches
|
|
154
|
+
* inside `cwd` are placed the same way.
|
|
155
|
+
*
|
|
156
|
+
* A match outside `cwd` (for example from `../app/**`) has no place in that
|
|
157
|
+
* layout, so this rejects before copying anything instead of writing files
|
|
158
|
+
* next to, or outside, `destDir`.
|
|
159
|
+
* @async
|
|
160
|
+
* @function
|
|
161
|
+
* @param {string[]} files - paths returned by `globFiles`
|
|
162
|
+
* @param {string} destDir - NW.js project directory (`package.nw` / `app.nw`)
|
|
163
|
+
* @param {string} [cwd] - app root the files are relative to
|
|
164
|
+
* @returns {Promise<void>}
|
|
165
|
+
*/
|
|
166
|
+
async function copyGlobFiles(files, destDir, cwd = process.cwd()) {
|
|
167
|
+
/** @type {{ src: string, dest: string }[]} */
|
|
168
|
+
const copies = [];
|
|
169
|
+
/** @type {string[]} */
|
|
170
|
+
const outside = [];
|
|
171
|
+
for (const file of files) {
|
|
172
|
+
const src = path.resolve(cwd, file);
|
|
173
|
+
const relative = path.relative(cwd, src);
|
|
174
|
+
// ".." or "../x" escapes cwd; an absolute result means another drive on Windows.
|
|
175
|
+
if (
|
|
176
|
+
relative === ".." ||
|
|
177
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
178
|
+
path.isAbsolute(relative)
|
|
179
|
+
) {
|
|
180
|
+
outside.push(file);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
copies.push({ src, dest: path.join(destDir, relative) });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (outside.length > 0) {
|
|
187
|
+
const shown = outside.slice(0, 5).join(", ");
|
|
188
|
+
const more = outside.length > 5 ? ` and ${outside.length - 5} more` : "";
|
|
189
|
+
throw new Error(
|
|
190
|
+
`srcDir glob matched files outside the current working directory (${cwd}): ${shown}${more}. ` +
|
|
191
|
+
"With glob enabled, paths are kept relative to the current working directory, which becomes the app root, " +
|
|
192
|
+
"so these files have no place in the build. Run nwbuild from the app directory, " +
|
|
193
|
+
'or set glob to false and srcDir to the app directory (for example srcDir: "../app").',
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for (const { src, dest } of copies) {
|
|
198
|
+
const stats = await fs.promises.stat(src);
|
|
199
|
+
if (stats.isDirectory()) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
await fs.promises.cp(src, dest, {
|
|
203
|
+
recursive: true,
|
|
204
|
+
force: true,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
47
209
|
export default {
|
|
48
210
|
PLATFORM_KV,
|
|
49
211
|
ARCH_KV,
|
|
50
212
|
globFiles,
|
|
213
|
+
zipDirectory,
|
|
214
|
+
copyGlobFiles,
|
|
51
215
|
};
|
package/types/main.d.ts
CHANGED
package/types/util.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ declare namespace _default {
|
|
|
2
2
|
export { PLATFORM_KV };
|
|
3
3
|
export { ARCH_KV };
|
|
4
4
|
export { globFiles };
|
|
5
|
+
export { zipDirectory };
|
|
6
|
+
export { copyGlobFiles };
|
|
5
7
|
}
|
|
6
8
|
export default _default;
|
|
7
9
|
declare namespace PLATFORM_KV {
|
|
@@ -27,3 +29,36 @@ declare function globFiles({ srcDir, glob }: {
|
|
|
27
29
|
srcDir: string | string[];
|
|
28
30
|
glob: boolean;
|
|
29
31
|
}): Promise<string[] | string>;
|
|
32
|
+
/**
|
|
33
|
+
* Zip the contents of a directory, so its children sit at the archive root.
|
|
34
|
+
*
|
|
35
|
+
* Matches what `archiver`'s `directory(srcDir, false)` produced: directories
|
|
36
|
+
* get their own entries, symlinks are stored as symlinks (relative to the
|
|
37
|
+
* link's directory) instead of being followed, and each entry keeps its
|
|
38
|
+
* permission bits and mtime. Other file types (sockets, FIFOs) are skipped.
|
|
39
|
+
* @async
|
|
40
|
+
* @function
|
|
41
|
+
* @param {string} srcDir - directory to compress
|
|
42
|
+
* @param {string} destFile - path of the zip file to write
|
|
43
|
+
* @returns {Promise<void>} - resolves once the zip file is fully written
|
|
44
|
+
*/
|
|
45
|
+
declare function zipDirectory(srcDir: string, destFile: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Copy globbed files into the NW.js project directory.
|
|
48
|
+
*
|
|
49
|
+
* In glob mode the current working directory is the app root: each file keeps
|
|
50
|
+
* its path relative to `cwd`, so `package.json src/**` produces
|
|
51
|
+
* `package.json` and `src/...` at the top of `destDir`. Absolute matches
|
|
52
|
+
* inside `cwd` are placed the same way.
|
|
53
|
+
*
|
|
54
|
+
* A match outside `cwd` (for example from `../app/**`) has no place in that
|
|
55
|
+
* layout, so this rejects before copying anything instead of writing files
|
|
56
|
+
* next to, or outside, `destDir`.
|
|
57
|
+
* @async
|
|
58
|
+
* @function
|
|
59
|
+
* @param {string[]} files - paths returned by `globFiles`
|
|
60
|
+
* @param {string} destDir - NW.js project directory (`package.nw` / `app.nw`)
|
|
61
|
+
* @param {string} [cwd] - app root the files are relative to
|
|
62
|
+
* @returns {Promise<void>}
|
|
63
|
+
*/
|
|
64
|
+
declare function copyGlobFiles(files: string[], destDir: string, cwd?: string): Promise<void>;
|