deboa 1.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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/dist/cjs/classes/Deboa.js +490 -0
  4. package/dist/cjs/classes/DeboaFromFile.js +66 -0
  5. package/dist/cjs/index.js +28 -0
  6. package/dist/cjs/types/IAddTarEntriesParams.js +4 -0
  7. package/dist/cjs/types/IControlFileOptions.js +4 -0
  8. package/dist/cjs/types/IDeboa.js +4 -0
  9. package/dist/cjs/types/IDeboaFromFile.js +4 -0
  10. package/dist/cjs/types/INormalizeOptionsLength.js +4 -0
  11. package/dist/cjs/types/IWriteFileFromLinesArgs.js +4 -0
  12. package/dist/cjs/types/IWriteToArchive.js +4 -0
  13. package/dist/cjs/types/MaintainerScript.js +4 -0
  14. package/dist/cjs/types/Priority.js +4 -0
  15. package/dist/cjs/types/Section.js +4 -0
  16. package/dist/cjs/types/classes/Deboa.d.ts +44 -0
  17. package/dist/cjs/types/classes/DeboaFromFile.d.ts +23 -0
  18. package/dist/cjs/types/index.d.ts +4 -0
  19. package/dist/cjs/types/index.js +25 -0
  20. package/dist/cjs/types/types/IAddTarEntriesParams.d.ts +5 -0
  21. package/dist/cjs/types/types/IControlFileOptions.d.ts +130 -0
  22. package/dist/cjs/types/types/IDeboa.d.ts +123 -0
  23. package/dist/cjs/types/types/IDeboaFromFile.d.ts +8 -0
  24. package/dist/cjs/types/types/INormalizeOptionsLength.d.ts +8 -0
  25. package/dist/cjs/types/types/IWriteFileFromLinesArgs.d.ts +10 -0
  26. package/dist/cjs/types/types/IWriteToArchive.d.ts +19 -0
  27. package/dist/cjs/types/types/MaintainerScript.d.ts +1 -0
  28. package/dist/cjs/types/types/Priority.d.ts +1 -0
  29. package/dist/cjs/types/types/Section.d.ts +1 -0
  30. package/dist/cjs/types/types/index.d.ts +10 -0
  31. package/dist/cjs/types/utils/addTarEntries.d.ts +5 -0
  32. package/dist/cjs/types/utils/createFileHeader.d.ts +2 -0
  33. package/dist/cjs/types/utils/writeFileFromLines.d.ts +5 -0
  34. package/dist/cjs/types/utils/writeToArchive.d.ts +5 -0
  35. package/dist/cjs/utils/addTarEntries.js +25 -0
  36. package/dist/cjs/utils/createFileHeader.js +61 -0
  37. package/dist/cjs/utils/writeFileFromLines.js +13 -0
  38. package/dist/cjs/utils/writeToArchive.js +78 -0
  39. package/dist/esm/classes/Deboa.js +439 -0
  40. package/dist/esm/classes/DeboaFromFile.js +55 -0
  41. package/dist/esm/index.js +4 -0
  42. package/dist/esm/types/IAddTarEntriesParams.js +1 -0
  43. package/dist/esm/types/IControlFileOptions.js +1 -0
  44. package/dist/esm/types/IDeboa.js +1 -0
  45. package/dist/esm/types/IDeboaFromFile.js +1 -0
  46. package/dist/esm/types/INormalizeOptionsLength.js +1 -0
  47. package/dist/esm/types/IWriteFileFromLinesArgs.js +4 -0
  48. package/dist/esm/types/IWriteToArchive.js +1 -0
  49. package/dist/esm/types/MaintainerScript.js +1 -0
  50. package/dist/esm/types/Priority.js +1 -0
  51. package/dist/esm/types/Section.js +1 -0
  52. package/dist/esm/types/classes/Deboa.d.ts +44 -0
  53. package/dist/esm/types/classes/DeboaFromFile.d.ts +23 -0
  54. package/dist/esm/types/index.d.ts +4 -0
  55. package/dist/esm/types/index.js +10 -0
  56. package/dist/esm/types/types/IAddTarEntriesParams.d.ts +5 -0
  57. package/dist/esm/types/types/IControlFileOptions.d.ts +130 -0
  58. package/dist/esm/types/types/IDeboa.d.ts +123 -0
  59. package/dist/esm/types/types/IDeboaFromFile.d.ts +8 -0
  60. package/dist/esm/types/types/INormalizeOptionsLength.d.ts +8 -0
  61. package/dist/esm/types/types/IWriteFileFromLinesArgs.d.ts +10 -0
  62. package/dist/esm/types/types/IWriteToArchive.d.ts +19 -0
  63. package/dist/esm/types/types/MaintainerScript.d.ts +1 -0
  64. package/dist/esm/types/types/Priority.d.ts +1 -0
  65. package/dist/esm/types/types/Section.d.ts +1 -0
  66. package/dist/esm/types/types/index.d.ts +10 -0
  67. package/dist/esm/types/utils/addTarEntries.d.ts +5 -0
  68. package/dist/esm/types/utils/createFileHeader.d.ts +2 -0
  69. package/dist/esm/types/utils/writeFileFromLines.d.ts +5 -0
  70. package/dist/esm/types/utils/writeToArchive.d.ts +5 -0
  71. package/dist/esm/utils/addTarEntries.js +19 -0
  72. package/dist/esm/utils/createFileHeader.js +53 -0
  73. package/dist/esm/utils/writeFileFromLines.js +7 -0
  74. package/dist/esm/utils/writeToArchive.js +72 -0
  75. package/package.json +57 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Erik Moura
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,178 @@
1
+ deboa
2
+ ===========
3
+
4
+ deboa is a Node.js tool for creating .deb files (and .ar files, if you ever need one for some reason).
5
+
6
+ It doesn't depend on any Unix-specific external binaries (I'm looking at you, dpkg and fakeroot), so you can create a
7
+ .deb file in pretty much any platform you can run Node.js on: Windows (🔥), OS X, Linux (because why not), you name it.
8
+
9
+
10
+ # Features
11
+
12
+ deboa provides all the tools you need to create a .deb from scratch or from your own `control` and `data` files:
13
+ - Create your .deb in any OS, no superuser privileges or external binaries required
14
+ - Supports compression in .tar, .tar.gz or .tar.xz out of the box. If you need another format, you can easily provide your own compressed files and let deboa do the rest
15
+ - Support for icon and desktop specification file
16
+ - Ability to set chmod permissions for every packaged file and to create symlinks, even on Windows
17
+
18
+
19
+ # About .deb files
20
+
21
+ A .deb file is just an .ar file, which is made up by a file signature / magic number (the string `!<arch>`) followed by
22
+ any number of metadata/file contents pairs.
23
+
24
+ .debs have three files, the last two are usually compressed in .tar, .tar.gz or .tar.xz format:
25
+
26
+ - the `debian-binary` file, containing just the string `2.0\n`
27
+ - the `control` file, containing metadata about the software it contains and possibly some scripts to be executed
28
+ before/after the software is installed/uninstalled
29
+ - the `data` file, which contains the actual software
30
+
31
+
32
+ # API
33
+
34
+ ## Creating a .deb from scratch
35
+
36
+ So you have a folder with the Linux version of your app ready to package. You can use the `Deboa` class to create
37
+ the `control` file
38
+ and compress the specified directory into the `data` file for you. The following code creates a .deb file with the bare
39
+ minimum options:
40
+
41
+ ```ts
42
+ import { Deboa } from 'deboa'
43
+
44
+ const deboa = new Deboa({
45
+ controlFileOptions: {
46
+ maintainer: 'John Doe <john@example.com>',
47
+ packageName: 'my-awesome-app',
48
+ shortDescription: 'users will see this when installing your app',
49
+ version: '1.0.0',
50
+ },
51
+ sourceDir: './my-awesome-app-linux-x64',
52
+ targetDir: './out',
53
+ })
54
+
55
+ deboa.package().then(() => {
56
+ console.log('done')
57
+ })
58
+ ```
59
+
60
+
61
+ ### Additional options
62
+
63
+ For more details, please see the [IDeboa interface](https://github.com/erikian/deboa/blob/main/src/types/IDeboa.ts).
64
+
65
+ - `additionalTarEntries`: runs after all source files are added to the .tar archive. You can use it to create any symbolic links you might need:
66
+
67
+ ```ts
68
+ const deboa = new Deboa({
69
+ additionalTarEntries: [
70
+ // creates a relative symlink from /usr/lib/my-awesome-app/some-executable-file
71
+ // to /usr/bin/my-awesome-app, equivalent to:
72
+ // cd /usr/bin && ln -s ../lib/my-awesome-app/some-executable-file some-executable-file
73
+ {
74
+ gname: 'root',
75
+ linkname: '../lib/my-awesome-app/some-executable-file', // link source
76
+ mode: parseInt('777', 8),
77
+ name: 'usr/bin/my-awesome-app', // link target
78
+ type: 'symlink',
79
+ uname: 'root',
80
+ }
81
+ ],
82
+ // other options
83
+ })
84
+ ```
85
+ - `beforeCreateDesktopEntry`: runs before the desktop entry file is created. Allows you to modify the default entries and to add your own.
86
+ - `beforePackage`: runs after the files are copied to the temporary directory and before they're packaged. You can use this to add/delete/rename any files before they're packaged.
87
+ - `controlFileOptions`: additional control file fields. See the [IControlFileOptions interface](https://github.com/erikian/deboa/blob/main/src/types/IControlFileOptions.ts) for details.
88
+ - `icon`: path to the image you want to use as your app icon.
89
+ - `modifyTarHeader`: allows you to modify the header of a file before it's added to the `data` tar archive. The main use case
90
+ for this option is setting permissions in order to make files executable when creating a .deb on Windows:
91
+
92
+ ```ts
93
+ const deboa = new Deboa({
94
+ modifyTarHeader: header => {
95
+ if (header.name === 'usr/lib/my-awesome-app/some-executable-file') {
96
+ header.mode = parseInt('0755', 8)
97
+ }
98
+
99
+ return header
100
+ },
101
+ // other options
102
+ })
103
+ ```
104
+ - `tarballFormat`: can be `tar`, `tar.gz` (default) or `tar.xz`.
105
+
106
+ ## Creating a .deb from existing `control` and `data` files
107
+
108
+ If you have your `control` file handy and your `data` file already compressed and ready to go, you can use
109
+ the `writeFromFile` method from the `DeboaFromFile` class to create the metadata and write them directly to the .deb
110
+ file. This allows you to have your files compressed by your favorite tool in the format that is best for your case.
111
+
112
+ ```ts
113
+ import { DeboaFromFile } from 'deboa'
114
+
115
+ ;(async () => {
116
+ const deboa = new DeboaFromFile({
117
+ outputFile: '/path/to/my-awesome-app_1.0.0_amd64.deb',
118
+ })
119
+
120
+ await deboa.writeFromFile('/path/to/control.tar.gz')
121
+ await deboa.writeFromFile('/path/to/data.tar.gz')
122
+
123
+ deboa.writeStream.close()
124
+
125
+ console.log('done')
126
+ })()
127
+ ```
128
+
129
+ If you need access to the underlying read streams, you can use the `createReadStream` method, which takes
130
+ care of the metadata, then pass the returned stream to the `writeFromStream` method:
131
+
132
+ ```ts
133
+ import { DeboaFromFile } from 'deboa'
134
+
135
+ ;(async () => {
136
+ const deboa = new DeboaFromFile({
137
+ outputFile: '/path/to/my-awesome-app_1.0.0_amd64.deb',
138
+ })
139
+
140
+ const controlStream = await deboa.createReadStream('/path/to/control.tar.gz')
141
+ await deboa.writeFromStream(controlStream)
142
+
143
+ const dataStream = await deboa.createReadStream('/path/to/data.tar.gz')
144
+ await deboa.writeFromStream(dataStream)
145
+
146
+ deboa.writeStream.close()
147
+
148
+ console.log('done')
149
+ })()
150
+ ```
151
+
152
+ Note that you need to write the `control` and `data` files **in that order**, otherwise the generated .deb will be
153
+ invalid.
154
+
155
+ ### Creating an .ar file
156
+
157
+ Same as above, just add the `isARFile: true` option to the constructor so the `debian-binary` file doesn't get
158
+ created:
159
+
160
+ ```ts
161
+ import { DeboaFromFile } from 'deboa'
162
+
163
+ ;(async() => {
164
+ const ar = new DeboaFromFile({
165
+ outputFile: '/path/to/output.ar',
166
+ isARFile: true,
167
+ })
168
+
169
+ await ar.writeFromFile('/path/to/some/file')
170
+
171
+ deboa.writeStream.close()
172
+
173
+ console.log('done')
174
+ })()
175
+ ```
176
+
177
+ I've added this option just because it was trivial. If you're here because of it, please leave a comment, I'm very
178
+ interested in knowing what you've been working with.
@@ -0,0 +1,490 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "Deboa", {
6
+ enumerable: true,
7
+ get: ()=>Deboa
8
+ });
9
+ const _events = require("events");
10
+ const _os = /*#__PURE__*/ _interopRequireDefault(require("os"));
11
+ const _path = /*#__PURE__*/ _interopRequireDefault(require("path"));
12
+ const _fs = /*#__PURE__*/ _interopRequireDefault(require("fs"));
13
+ const _fsExtra = /*#__PURE__*/ _interopRequireDefault(require("fs-extra"));
14
+ const _util = require("util");
15
+ const _fastFolderSize = /*#__PURE__*/ _interopRequireDefault(require("fast-folder-size"));
16
+ const _tarFs = /*#__PURE__*/ _interopRequireDefault(require("tar-fs"));
17
+ const _writeFileFromLines = require("../utils/writeFileFromLines");
18
+ const _deboaFromFile = require("./DeboaFromFile");
19
+ const _stream = require("stream");
20
+ const _addTarEntries = require("../utils/addTarEntries");
21
+ function _interopRequireDefault(obj) {
22
+ return obj && obj.__esModule ? obj : {
23
+ default: obj
24
+ };
25
+ }
26
+ function _getRequireWildcardCache(nodeInterop) {
27
+ if (typeof WeakMap !== "function") return null;
28
+ var cacheBabelInterop = new WeakMap();
29
+ var cacheNodeInterop = new WeakMap();
30
+ return (_getRequireWildcardCache = function(nodeInterop) {
31
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
32
+ })(nodeInterop);
33
+ }
34
+ function _interopRequireWildcard(obj, nodeInterop) {
35
+ if (!nodeInterop && obj && obj.__esModule) {
36
+ return obj;
37
+ }
38
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
39
+ return {
40
+ default: obj
41
+ };
42
+ }
43
+ var cache = _getRequireWildcardCache(nodeInterop);
44
+ if (cache && cache.has(obj)) {
45
+ return cache.get(obj);
46
+ }
47
+ var newObj = {};
48
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
49
+ for(var key in obj){
50
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
51
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
52
+ if (desc && (desc.get || desc.set)) {
53
+ Object.defineProperty(newObj, key, desc);
54
+ } else {
55
+ newObj[key] = obj[key];
56
+ }
57
+ }
58
+ }
59
+ newObj.default = obj;
60
+ if (cache) {
61
+ cache.set(obj, newObj);
62
+ }
63
+ return newObj;
64
+ }
65
+ /**
66
+ * @return IDeboa
67
+ */ class Deboa {
68
+ /** See {@link IDeboa.additionalTarEntries} */ additionalTarEntries = [];
69
+ /** See {@link IDeboa.beforeCreateDesktopEntry} */ beforeCreateDesktopEntry = null;
70
+ /** See {@link IDeboa.beforePackage} */ beforePackage = null;
71
+ /** See {@link IDeboa.controlFileOptions} */ controlFileOptions = {
72
+ /** See {@link IDeboa.architecture} */ architecture: null,
73
+ /** See {@link IDeboa.builtUsing} */ builtUsing: null,
74
+ /** See {@link IDeboa.conflicts} */ conflicts: null,
75
+ /** See {@link IDeboa.depends} */ depends: null,
76
+ /** See {@link IDeboa.essential} */ essential: null,
77
+ /** See {@link IDeboa.extendedDescription} */ extendedDescription: null,
78
+ /** See {@link IDeboa.homepage} */ homepage: null,
79
+ /** See {@link IDeboa.maintainer} */ maintainer: null,
80
+ /** See {@link IDeboa.maintainerScripts} */ maintainerScripts: {
81
+ preinst: null,
82
+ postinst: null,
83
+ prerm: null,
84
+ postrm: null
85
+ },
86
+ /** See {@link IDeboa.packageName} */ packageName: null,
87
+ /** See {@link IDeboa.preDepends} */ preDepends: null,
88
+ /** See {@link IDeboa.priority} */ priority: null,
89
+ /** See {@link IDeboa.recommends} */ recommends: null,
90
+ /** See {@link IDeboa.section} */ section: null,
91
+ /** See {@link IDeboa.shortDescription} */ shortDescription: null,
92
+ /** See {@link IDeboa.source} */ source: null,
93
+ /** See {@link IDeboa.suggests} */ suggests: null,
94
+ /** See {@link IDeboa.version} */ version: null
95
+ };
96
+ /** See {@link IDeboa.icon} */ icon = null;
97
+ /** See {@link IDeboa.modifyTarHeader} */ modifyTarHeader = null;
98
+ /** See {@link IDeboa.sourceDir} */ sourceDir = null;
99
+ /** See {@link IDeboa.tarballFormat} */ tarballFormat = null;
100
+ /** See {@link IDeboa.targetDir} */ targetDir = null;
101
+ #appFolderDestination = null;
102
+ #controlFolderDestination = null;
103
+ #dataFolderDestination = null;
104
+ #hooksLoaded = false;
105
+ #outputFile = null;
106
+ #tempDir = null;
107
+ constructor(options){
108
+ if (!Object.keys(options || {}).length) {
109
+ throw new Error('No configuration options provided');
110
+ }
111
+ const { sourceDir , targetDir } = options;
112
+ if (!sourceDir) {
113
+ throw new Error('The `sourceDir` field is mandatory');
114
+ }
115
+ if (!targetDir) {
116
+ throw new Error('The `targetDir` field is mandatory');
117
+ }
118
+ Deboa.#validateOptions(options);
119
+ const { controlFileOptions } = options;
120
+ const osArch = _os.default.arch();
121
+ // default values
122
+ options = {
123
+ ...options,
124
+ controlFileOptions: {
125
+ ...controlFileOptions,
126
+ ...!controlFileOptions.architecture && {
127
+ architecture: osArch === 'x64' ? 'amd64' : osArch
128
+ },
129
+ ...!controlFileOptions.maintainerScripts && {
130
+ maintainerScripts: {}
131
+ },
132
+ ...!controlFileOptions.priority && {
133
+ priority: 'optional'
134
+ },
135
+ ...!controlFileOptions.extendedDescription && {
136
+ extendedDescription: controlFileOptions.shortDescription
137
+ }
138
+ },
139
+ tarballFormat: [
140
+ 'tar',
141
+ 'tar.gz',
142
+ 'tar.xz'
143
+ ].includes(options.tarballFormat) ? options.tarballFormat : 'tar.gz'
144
+ };
145
+ for (const [property, value] of Object.entries(options)){
146
+ this[property] = value;
147
+ }
148
+ this.#tempDir = _path.default.join(_os.default.tmpdir(), 'deboa_temp');
149
+ const { architecture , packageName , version } = this.controlFileOptions;
150
+ this.#outputFile = _path.default.join(this.targetDir, `${packageName}_${version}_${architecture}.deb`);
151
+ this.#controlFolderDestination = _path.default.join(this.#tempDir, 'control');
152
+ this.#dataFolderDestination = _path.default.join(this.#tempDir, 'data');
153
+ this.#appFolderDestination = _path.default.join(this.#dataFolderDestination, 'usr', 'lib', packageName);
154
+ }
155
+ /**
156
+ * Ensures that all required options are present and that the `packageName`
157
+ * and `maintainer` fields are provided in the right format.
158
+ */ static #validateOptions(options) {
159
+ console.log('Validating options...\n');
160
+ const { controlFileOptions: { shortDescription , maintainer , packageName , version , } , } = options;
161
+ if (!packageName) {
162
+ throw new Error('The controlFileOptions.`packageName` field is mandatory');
163
+ }
164
+ if (!version) {
165
+ throw new Error('The `controlFileOptions.version` field is mandatory');
166
+ }
167
+ if (!maintainer) {
168
+ throw new Error('The `controlFileOptions.maintainer` field is mandatory');
169
+ }
170
+ if (!shortDescription) {
171
+ throw new Error('The `controlFileOptions.description` field is mandatory');
172
+ }
173
+ if (packageName.length < 2) {
174
+ throw new Error('The `controlFileOptions.packageName` field must be at least two characters long');
175
+ }
176
+ if (packageName.replace(/[^a-z\d\-+.]/g, '').replace(/^[-+.]/g, '').toLowerCase() !== packageName) {
177
+ throw new Error('The `controlFileOptions.packageName` field contains illegal characters');
178
+ }
179
+ /*
180
+ if (!maintainer.match(/^(\p{L}+ ?)+ ?<(.*?)@(.*?)>$/u)) {
181
+ throw new Error(
182
+ 'The `controlFileOptions.maintainer` field does not match the expected format `John Doe <johndoe@example.com>`',
183
+ )
184
+ }
185
+ */ const { postinst , prerm , postrm , preinst } = options.controlFileOptions.maintainerScripts || {};
186
+ for (const [scriptName, scriptPath] of Object.entries({
187
+ postinst,
188
+ postrm,
189
+ preinst,
190
+ prerm
191
+ })){
192
+ if (scriptPath) {
193
+ try {
194
+ _fs.default.accessSync(_path.default.resolve(scriptPath));
195
+ } catch (e) {
196
+ throw new Error(`Error accessing \`${scriptPath}\` (provided in \`controlFileOptions.maintainerScripts.${scriptName}\`). Make sure that this file exists and is accessible by the current user.`);
197
+ }
198
+ }
199
+ }
200
+ }
201
+ /**
202
+ * Creates the .deb file.
203
+ * @return {Promise<string>} outputFile - Absolute path to the generated .deb
204
+ */ async package() {
205
+ const startTime = process.hrtime.bigint();
206
+ const tempDir = this.#tempDir;
207
+ await _fsExtra.default.ensureDir(tempDir);
208
+ if (!this.#hooksLoaded) {
209
+ await this.loadHooks();
210
+ }
211
+ await this.#copyFolderTree();
212
+ await this.#copyMaintainerScripts();
213
+ await this.#copyPackageFiles();
214
+ await this.#createControlFile();
215
+ await this.#copyIconAndDesktopEntryFile();
216
+ if (typeof this.beforePackage === 'function') {
217
+ await this.beforePackage(this.#dataFolderDestination);
218
+ }
219
+ await this.#createTarballs();
220
+ await this.#createDeb();
221
+ console.log('Removing temporary files...\n');
222
+ await _fs.default.promises.rm(this.#tempDir, {
223
+ recursive: true
224
+ });
225
+ const endTime = process.hrtime.bigint();
226
+ const duration = parseInt(String(endTime - startTime));
227
+ console.log(`.deb created in ${(duration / 1e9).toFixed(2)}s`);
228
+ return this.#outputFile;
229
+ }
230
+ /**
231
+ * Creates the tarballs for the control and data files.
232
+ */ async #createTarballs() {
233
+ console.log('Packaging files....\n');
234
+ const { tarballFormat } = this;
235
+ const dataFileLocation = this.#dataFolderDestination + `.${tarballFormat}`;
236
+ const controlFileLocation = this.#controlFolderDestination + `.${tarballFormat}`;
237
+ let dataFileCompressor;
238
+ let controlFileCompressor;
239
+ switch(tarballFormat){
240
+ case 'tar':
241
+ {
242
+ dataFileCompressor = new _stream.PassThrough();
243
+ controlFileCompressor = new _stream.PassThrough();
244
+ break;
245
+ }
246
+ case 'tar.gz':
247
+ {
248
+ const zlib = (await Promise.resolve().then(()=>/*#__PURE__*/ _interopRequireWildcard(require("zlib")))).default;
249
+ dataFileCompressor = zlib.createGzip();
250
+ controlFileCompressor = zlib.createGzip();
251
+ break;
252
+ }
253
+ case 'tar.xz':
254
+ {
255
+ const lzma = (await Promise.resolve().then(()=>/*#__PURE__*/ _interopRequireWildcard(require("lzma-native")))).default;
256
+ dataFileCompressor = lzma.createCompressor({
257
+ threads: 0
258
+ });
259
+ controlFileCompressor = lzma.createCompressor({
260
+ threads: 0
261
+ });
262
+ break;
263
+ }
264
+ }
265
+ const dataFileWriteStream = _fs.default.createWriteStream(dataFileLocation);
266
+ const controlFileWriteStream = _fs.default.createWriteStream(controlFileLocation);
267
+ _tarFs.default.pack(this.#dataFolderDestination, {
268
+ map: (header)=>{
269
+ // sensible defaults for Windows users
270
+ if (process.platform === 'win32') {
271
+ const defaultFilePermission = parseInt('0644', 8);
272
+ const defaultFolderPermission = parseInt('0755', 8);
273
+ switch(header.type){
274
+ case 'file':
275
+ {
276
+ header.mode = defaultFilePermission;
277
+ break;
278
+ }
279
+ case 'directory':
280
+ {
281
+ header.mode = defaultFolderPermission;
282
+ break;
283
+ }
284
+ }
285
+ }
286
+ if (typeof this.modifyTarHeader === 'function') {
287
+ header = this.modifyTarHeader(header);
288
+ }
289
+ return header;
290
+ },
291
+ ...this.additionalTarEntries.length && {
292
+ finalize: false,
293
+ finish: async (pack)=>{
294
+ await (0, _addTarEntries.addTarEntries)({
295
+ entries: this.additionalTarEntries,
296
+ pack
297
+ });
298
+ pack.finalize();
299
+ }
300
+ }
301
+ }).pipe(dataFileCompressor).pipe(dataFileWriteStream);
302
+ _tarFs.default.pack(this.#controlFolderDestination, {
303
+ map: (header)=>{
304
+ const maintainerScripts = [
305
+ 'postinst',
306
+ 'postrm',
307
+ 'preinst',
308
+ 'prerm',
309
+ ];
310
+ // maintainer scripts must be executable
311
+ if (maintainerScripts.includes(header.name)) {
312
+ header.mode = parseInt('0755', 8);
313
+ }
314
+ return header;
315
+ }
316
+ }).pipe(controlFileCompressor).pipe(controlFileWriteStream);
317
+ await Promise.all([
318
+ (0, _events.once)(dataFileWriteStream, 'finish'),
319
+ (0, _events.once)(controlFileWriteStream, 'finish'),
320
+ ]);
321
+ }
322
+ /**
323
+ * Copies the empty folders to the temporary location.
324
+ */ async #copyFolderTree() {
325
+ console.log('Creating directory structure in the temporary folder...\n');
326
+ await _fsExtra.default.ensureDir(this.#appFolderDestination);
327
+ await _fsExtra.default.ensureDir(this.#controlFolderDestination);
328
+ }
329
+ /**
330
+ * Copies the source files to the temporary folder.
331
+ */ async #copyPackageFiles() {
332
+ console.log('Copying source directory...\n');
333
+ await _fsExtra.default.copy(this.sourceDir, this.#appFolderDestination);
334
+ }
335
+ /**
336
+ * Copies the maintainer scripts to the temporary folder.
337
+ */ async #copyMaintainerScripts() {
338
+ console.log('Copying maintainer scripts...\n');
339
+ const { controlFileOptions: { maintainerScripts: { postinst: postinst1 , postrm: postrm1 , preinst: preinst1 , prerm: prerm1 } = {} , } , } = this;
340
+ const scripts = Object.entries({
341
+ postinst: postinst1,
342
+ postrm: postrm1,
343
+ preinst: preinst1,
344
+ prerm: prerm1
345
+ }).filter(([, scriptPath])=>scriptPath);
346
+ for (const [scriptName1, scriptPath1] of scripts){
347
+ await _fs.default.promises.copyFile(_path.default.resolve(scriptPath1), _path.default.join(this.#controlFolderDestination, scriptName1));
348
+ }
349
+ }
350
+ /**
351
+ * Creates the control file and writes it to the temporary folder.
352
+ */ async #createControlFile() {
353
+ console.log('Creating control file...\n');
354
+ const { controlFileOptions: { packageName: packageName1 , version: version1 , section , priority , architecture , maintainer: maintainer1 , homepage , suggests =[] , depends =[] , recommends =[] , shortDescription: shortDescription1 , extendedDescription , builtUsing , conflicts =[] , essential , preDepends =[] , } , } = this;
355
+ const fastFolderSizeAsync = (0, _util.promisify)(_fastFolderSize.default);
356
+ const installedSize = await fastFolderSizeAsync(this.sourceDir);
357
+ const lines = [
358
+ `Package: ${packageName1}`,
359
+ `Version: ${version1}`,
360
+ section && `Section: ${section}`,
361
+ `Priority: ${priority}`,
362
+ `Architecture: ${architecture}`,
363
+ `Maintainer: ${maintainer1}`,
364
+ Array.isArray(depends) && depends.length && `Depends: ${depends.join(', ')}`,
365
+ preDepends.length && `Pre-Depends: ${preDepends.join(', ')}`,
366
+ recommends.length && `Recommends: ${recommends.join(', ')}`,
367
+ suggests.length && `Suggests: ${suggests.join(', ')}`,
368
+ conflicts.length && `Conflicts: ${conflicts.join(', ')}`,
369
+ `Installed-Size: ${Math.ceil(installedSize / 1024)}`,
370
+ homepage && `Homepage: ${homepage}`,
371
+ builtUsing && `Built-Using: ${builtUsing}`,
372
+ essential && `Essential: ${essential}`,
373
+ `Description: ${shortDescription1}`,
374
+ ` ${extendedDescription}`,
375
+ ].filter(Boolean);
376
+ await (0, _writeFileFromLines.writeFileFromLines)({
377
+ filePath: _path.default.join(this.#controlFolderDestination, 'control'),
378
+ lines
379
+ });
380
+ }
381
+ /**
382
+ * Copies the provided icon to the temporary folder
383
+ * and writes the desktop entry file.
384
+ */ async #copyIconAndDesktopEntryFile() {
385
+ let iconFileExists = false;
386
+ let iconDestination = null;
387
+ const { packageName: packageName2 , shortDescription: shortDescription2 } = this.controlFileOptions;
388
+ if (this.icon) {
389
+ const iconPath = _path.default.resolve(this.icon);
390
+ iconFileExists = await _fsExtra.default.pathExists(iconPath);
391
+ const { ext: extension } = _path.default.parse(iconPath);
392
+ if (iconFileExists) {
393
+ iconDestination = _path.default.join(this.#dataFolderDestination, 'usr/share/pixmaps', packageName2 + extension);
394
+ await _fsExtra.default.ensureDir(_path.default.resolve(iconDestination, '../'));
395
+ await _fsExtra.default.copy(iconPath, iconDestination);
396
+ console.log(`App icon saved to ${iconDestination}`);
397
+ } else {
398
+ console.warn(`\nWARNING: file \`${iconPath}\` not found, skipping app icon...\n`);
399
+ }
400
+ }
401
+ let desktopEntries = {
402
+ Comment: shortDescription2,
403
+ GenericName: packageName2,
404
+ Name: packageName2,
405
+ Type: 'Application',
406
+ ...iconFileExists && {
407
+ Icon: packageName2
408
+ }
409
+ };
410
+ if (typeof this.beforeCreateDesktopEntry === 'function') {
411
+ desktopEntries = await this.beforeCreateDesktopEntry(desktopEntries);
412
+ }
413
+ if (!Object.keys(desktopEntries).length) {
414
+ return;
415
+ }
416
+ const lines1 = Object.entries(desktopEntries).reduce((acc, entry)=>[
417
+ ...acc,
418
+ entry.join('=')
419
+ ], []);
420
+ const desktopFileDestination = _path.default.join(this.#dataFolderDestination, 'usr/share/applications', `${packageName2}.desktop`);
421
+ await _fsExtra.default.ensureDir(_path.default.resolve(desktopFileDestination, '../'));
422
+ await (0, _writeFileFromLines.writeFileFromLines)({
423
+ filePath: desktopFileDestination,
424
+ lines: [
425
+ '[Desktop Entry]',
426
+ ...lines1
427
+ ]
428
+ });
429
+ console.log(`Desktop entries file saved to ${desktopFileDestination}`);
430
+ }
431
+ /**
432
+ * Checks if the values provided to the hook options are file paths
433
+ * and imports the actual functions from them if necessary.
434
+ */ async loadHooks() {
435
+ const { beforePackage , modifyTarHeader , beforeCreateDesktopEntry } = this;
436
+ const hooks = {
437
+ beforeCreateDesktopEntry,
438
+ beforePackage,
439
+ modifyTarHeader
440
+ };
441
+ for (const [hookName, hookValue] of Object.entries(hooks)){
442
+ if (hookValue) {
443
+ switch(typeof hookValue){
444
+ case 'function':
445
+ {
446
+ this[hookName] = hookValue;
447
+ break;
448
+ }
449
+ case 'string':
450
+ {
451
+ const filePath = _path.default.resolve(hookValue);
452
+ const fileExists = await _fsExtra.default.pathExists(filePath);
453
+ if (!fileExists) {
454
+ throw new Error(`The file \`${hookValue}\` doesn't exist or cannot be accessed by the current user.`);
455
+ }
456
+ const importedFn = (await Promise.resolve(filePath).then((p)=>/*#__PURE__*/ _interopRequireWildcard(require(p)))).default;
457
+ if (typeof importedFn === 'function') {
458
+ this[hookName] = importedFn;
459
+ } else {
460
+ throw new Error(`The file \`${filePath}\` must have a function as its default export.`);
461
+ }
462
+ break;
463
+ }
464
+ default:
465
+ {
466
+ throw new Error(`Invalid type provided for the \`${hookName}\` option, expected a function or a path to a file that has a function as its default export.`);
467
+ }
468
+ }
469
+ }
470
+ }
471
+ this.#hooksLoaded = true;
472
+ }
473
+ /**
474
+ * Writes the .deb to the output folder.
475
+ */ async #createDeb() {
476
+ console.log('Writing .deb file...\n');
477
+ await _fsExtra.default.ensureDir(this.targetDir);
478
+ const deBoaFromFile = new _deboaFromFile.DeboaFromFile({
479
+ outputFile: this.#outputFile
480
+ });
481
+ try {
482
+ await deBoaFromFile.writeFromFile(this.#controlFolderDestination + `.${this.tarballFormat}`);
483
+ await deBoaFromFile.writeFromFile(this.#dataFolderDestination + `.${this.tarballFormat}`);
484
+ deBoaFromFile.writeStream.close();
485
+ } catch (e1) {
486
+ console.log('ERROR: ', e1);
487
+ throw e1;
488
+ }
489
+ }
490
+ }