Skip to content

Commit b2f73c8

Browse files
authored
feat: allow providing properties (such as unpack) in ordering input file (#350)
* Part 1 for electron/universal#117 with updated unit test * add additional helper function * use wrapped-fs for writeFile * `yarn prettier:write` * swap to for-of loop and extract helper function `isAlreadySorted` to make it more readable
1 parent c2a5f3a commit b2f73c8

File tree

3 files changed

+115
-53
lines changed

3 files changed

+115
-53
lines changed

src/asar.ts

Lines changed: 76 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,73 @@ function isUnpackedDir(dirPath: string, pattern: string, unpackDirs: string[]) {
3333
}
3434
}
3535

36+
export type FileProperties = {
37+
filepath: string;
38+
properties: {
39+
unpack?: boolean;
40+
};
41+
};
42+
43+
async function getFileOrdering(
44+
options: CreateOptions,
45+
src: string,
46+
filenames: string[],
47+
): Promise<FileProperties[]> {
48+
if (!options.ordering) {
49+
return filenames.map<FileProperties>((filepath) => ({ filepath, properties: {} }));
50+
}
51+
const orderingMap: FileProperties[] = (await fs.readFile(options.ordering))
52+
.toString()
53+
.split('\n')
54+
.map<FileProperties>((line) => {
55+
line = line.trim();
56+
const config: FileProperties = { filepath: line, properties: {} };
57+
const colonIndex = line.indexOf(':');
58+
if (colonIndex > -1) {
59+
config.filepath = line.substring(0, colonIndex); // file path
60+
const props = line.substring(colonIndex + 1); // props on other side of the `:`
61+
config.properties = props.length > 2 ? JSON.parse(props) : {}; // file properties
62+
}
63+
if (config.filepath.startsWith('/')) {
64+
config.filepath = config.filepath.slice(1);
65+
}
66+
return config;
67+
});
68+
69+
const ordering: FileProperties[] = [];
70+
for (const config of orderingMap) {
71+
const pathComponents = config.filepath.split(path.sep);
72+
let str = src;
73+
for (const pathComponent of pathComponents) {
74+
str = path.join(str, pathComponent);
75+
ordering.push({ filepath: str, properties: config.properties });
76+
}
77+
}
78+
79+
let missing = 0;
80+
const total = filenames.length;
81+
82+
const fileOrderingSorted: FileProperties[] = [];
83+
const isAlreadySorted = (file: string) =>
84+
fileOrderingSorted.findIndex((config) => file === config.filepath) > -1;
85+
86+
for (const config of ordering) {
87+
if (!isAlreadySorted(config.filepath) && filenames.includes(config.filepath)) {
88+
fileOrderingSorted.push(config);
89+
}
90+
}
91+
92+
for (const file of filenames) {
93+
if (!isAlreadySorted(file)) {
94+
fileOrderingSorted.push({ filepath: file, properties: {} });
95+
missing += 1;
96+
}
97+
}
98+
99+
console.log(`Ordering file has ${((total - missing) / total) * 100}% coverage.`);
100+
return fileOrderingSorted;
101+
}
102+
36103
export async function createPackage(src: string, dest: string) {
37104
return createPackageWithOptions(src, dest, {});
38105
}
@@ -84,54 +151,10 @@ export async function createPackageFromFiles(
84151
const links: disk.BasicFilesArray = [];
85152
const unpackDirs: string[] = [];
86153

87-
let filenamesSorted: string[] = [];
88-
if (options.ordering) {
89-
const orderingFiles = (await fs.readFile(options.ordering))
90-
.toString()
91-
.split('\n')
92-
.map((line) => {
93-
if (line.includes(':')) {
94-
line = line.split(':').pop()!;
95-
}
96-
line = line.trim();
97-
if (line.startsWith('/')) {
98-
line = line.slice(1);
99-
}
100-
return line;
101-
});
102-
103-
const ordering: string[] = [];
104-
for (const file of orderingFiles) {
105-
const pathComponents = file.split(path.sep);
106-
let str = src;
107-
for (const pathComponent of pathComponents) {
108-
str = path.join(str, pathComponent);
109-
ordering.push(str);
110-
}
111-
}
112-
113-
let missing = 0;
114-
const total = filenames.length;
115-
116-
for (const file of ordering) {
117-
if (!filenamesSorted.includes(file) && filenames.includes(file)) {
118-
filenamesSorted.push(file);
119-
}
120-
}
121-
122-
for (const file of filenames) {
123-
if (!filenamesSorted.includes(file)) {
124-
filenamesSorted.push(file);
125-
missing += 1;
126-
}
127-
}
154+
const filenamesSorted: FileProperties[] = await getFileOrdering(options, src, filenames);
128155

129-
console.log(`Ordering file has ${((total - missing) / total) * 100}% coverage.`);
130-
} else {
131-
filenamesSorted = filenames;
132-
}
133-
134-
const handleFile = async function (filename: string) {
156+
const handleFile = async function (config: FileProperties) {
157+
const filename = config.filepath;
135158
if (!metadata[filename]) {
136159
const fileType = await determineFileType(filename);
137160
if (!fileType) {
@@ -146,6 +169,9 @@ export async function createPackageFromFiles(
146169
unpack: string | undefined,
147170
unpackDir: string | undefined,
148171
) {
172+
if (config.properties.unpack != null) {
173+
return config.properties.unpack;
174+
}
149175
let shouldUnpack = false;
150176
if (unpack) {
151177
shouldUnpack = minimatch(filename, unpack, { matchBase: true });
@@ -190,12 +216,12 @@ export async function createPackageFromFiles(
190216

191217
const names = filenamesSorted.slice();
192218

193-
const next = async function (name?: string) {
194-
if (!name) {
219+
const next = async function (config?: FileProperties) {
220+
if (!config) {
195221
return insertsDone();
196222
}
197223

198-
await handleFile(name);
224+
await handleFile(config);
199225
return next(names.shift());
200226
};
201227

test/cli-spec.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,17 @@ describe('command line interface', function () {
190190
);
191191
});
192192
it('should unpack static framework with all underlying symlinks unpacked', async () => {
193-
const { tmpPath } = createSymlinkApp('app');
193+
const { tmpPath, buildOrderingData } = createSymlinkApp('ordered-app');
194+
const orderingPath = path.join(tmpPath, '../ordered-app-ordering.txt');
195+
196+
// this is functionally the same as `-unpack *.txt --unpack-dir var`
197+
const data = buildOrderingData((filepath) => ({
198+
unpack: filepath.endsWith('.txt') || filepath.includes('var'),
199+
}));
200+
await fs.writeFile(orderingPath, data);
201+
194202
await execAsar(
195-
`p ${tmpPath} tmp/packthis-with-symlink.asar --unpack *.txt --unpack-dir var --exclude-hidden`,
203+
`p ${tmpPath} tmp/packthis-with-symlink.asar --ordering=${orderingPath} --exclude-hidden`,
196204
);
197205

198206
assert.ok(fs.existsSync('tmp/packthis-with-symlink.asar.unpacked/private/var/file.txt'));

test/util/createSymlinkApp.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,33 @@ module.exports = (testName) => {
2626
const appPath = path.join(varPath, 'app');
2727
fs.mkdirpSync(appPath);
2828
fs.symlinkSync('../file.txt', path.join(appPath, 'file.txt'));
29-
return { appPath, tmpPath, varPath };
29+
30+
const ordering = walk(tmpPath).map((filepath) => filepath.substring(tmpPath.length)); // convert to paths relative to root
31+
32+
return {
33+
appPath,
34+
tmpPath,
35+
varPath,
36+
// helper function for generating the `ordering.txt` file data
37+
buildOrderingData: (getProps) =>
38+
ordering.reduce((prev, curr) => {
39+
return `${prev}${curr}:${JSON.stringify(getProps(curr))}\n`;
40+
}, ''),
41+
};
42+
};
43+
44+
// returns a list of all directories, files, and symlinks. Automates testing `ordering` logic easy.
45+
const walk = (root) => {
46+
const getPaths = (filepath, filter) =>
47+
fs
48+
.readdirSync(filepath, { withFileTypes: true })
49+
.filter((dirent) => filter(dirent))
50+
.map(({ name }) => path.join(filepath, name));
51+
52+
const dirs = getPaths(root, (dirent) => dirent.isDirectory());
53+
const files = dirs.map((dir) => walk(dir)).flat();
54+
return files.concat(
55+
dirs,
56+
getPaths(root, (dirent) => dirent.isFile() || dirent.isSymbolicLink()),
57+
);
3058
};

0 commit comments

Comments
 (0)