@capawesome/cli 4.18.2 → 4.18.4
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
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [4.18.4](https://github.com/capawesome-team/cli/compare/v4.18.3...v4.18.4) (2026-08-20)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* report unreadable files with an actionable error message ([#208](https://github.com/capawesome-team/cli/issues/208)) ([cc18dee](https://github.com/capawesome-team/cli/commit/cc18dee898658c99affd660c99e64ef8e45cdf9a))
|
|
11
|
+
|
|
12
|
+
## [4.18.3](https://github.com/capawesome-team/cli/compare/v4.18.2...v4.18.3) (2026-08-18)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* write the manifest file before uploading a bundle ([#206](https://github.com/capawesome-team/cli/issues/206)) ([631e99b](https://github.com/capawesome-team/cli/commit/631e99bff0b9426260e1f997d00c3fd36e840e78))
|
|
18
|
+
|
|
5
19
|
## [4.18.2](https://github.com/capawesome-team/cli/compare/v4.18.1...v4.18.2) (2026-08-10)
|
|
6
20
|
|
|
7
21
|
|
|
@@ -7,7 +7,7 @@ import { parseCustomProperties } from '../../../utils/custom-properties.js';
|
|
|
7
7
|
import { defineCommand, defineOptions } from 'zodline';
|
|
8
8
|
import { createBufferFromPath, createBufferFromReadStream, createBufferFromString, isPrivateKeyContent, } from '../../../utils/buffer.js';
|
|
9
9
|
import { isInteractive } from '../../../utils/environment.js';
|
|
10
|
-
import { directoryContainsSourceMaps, directoryContainsSymlinks, isReadable, getFilesInDirectoryAndSubdirectories, isDirectory, } from '../../../utils/file.js';
|
|
10
|
+
import { directoryContainsSourceMaps, directoryContainsSymlinks, isReadable, getFilesInDirectoryAndSubdirectories, isDirectory, readFileFromDirectory, } from '../../../utils/file.js';
|
|
11
11
|
import { createHash } from '../../../utils/hash.js';
|
|
12
12
|
import { generateManifestJson } from '../../../utils/manifest.js';
|
|
13
13
|
import { formatPrivateKey } from '../../../utils/private-key.js';
|
|
@@ -351,7 +351,7 @@ const uploadFiles = async (options) => {
|
|
|
351
351
|
const file = files[fileIndex];
|
|
352
352
|
fileIndex++;
|
|
353
353
|
consola.start(`Uploading file (${fileIndex}/${files.length})...`);
|
|
354
|
-
const buffer = await
|
|
354
|
+
const buffer = await readFileFromDirectory(file.path);
|
|
355
355
|
await uploadFile({
|
|
356
356
|
appId,
|
|
357
357
|
appBundleId: appBundleId,
|
package/dist/utils/file.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import mime from 'mime';
|
|
3
3
|
import pathModule from 'path';
|
|
4
|
+
import { createBufferFromPath } from './buffer.js';
|
|
5
|
+
import { getCodeFromUnknownError, UserError } from './error.js';
|
|
6
|
+
const concurrentModificationHint = 'Make sure that no other process (e.g. a build or file sync client) modifies the folder while the command is running.';
|
|
7
|
+
const permissionHint = 'Make sure that you have permission to read the file.';
|
|
8
|
+
const unreadableFileErrorHints = {
|
|
9
|
+
EACCES: permissionHint,
|
|
10
|
+
EBUSY: concurrentModificationHint,
|
|
11
|
+
ENOENT: concurrentModificationHint,
|
|
12
|
+
EPERM: permissionHint,
|
|
13
|
+
};
|
|
4
14
|
export const getFilesInDirectoryAndSubdirectories = async (path) => {
|
|
5
15
|
const files = [];
|
|
6
16
|
const walk = async (directory) => {
|
|
@@ -38,6 +48,23 @@ export const getFilesInDirectoryAndSubdirectories = async (path) => {
|
|
|
38
48
|
await walk(path);
|
|
39
49
|
return files;
|
|
40
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Reads a file that was found by `getFilesInDirectoryAndSubdirectories`.
|
|
53
|
+
* Such files can vanish, get locked or become inaccessible in the meantime (e.g. by a running build or a file sync client).
|
|
54
|
+
*/
|
|
55
|
+
export const readFileFromDirectory = async (path) => {
|
|
56
|
+
try {
|
|
57
|
+
return await createBufferFromPath(path);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const code = getCodeFromUnknownError(error);
|
|
61
|
+
const hint = code ? unreadableFileErrorHints[code] : undefined;
|
|
62
|
+
if (hint) {
|
|
63
|
+
throw new UserError(`The file could not be read: ${path}. ${hint}`);
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
41
68
|
export const directoryContainsSymlinks = async (path) => {
|
|
42
69
|
const dirEntries = await fs.promises.readdir(path, { withFileTypes: true, recursive: true }).catch(() => []);
|
|
43
70
|
return dirEntries.some((dirEntry) => dirEntry.isSymbolicLink());
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import pathModule from 'path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { UserError } from './error.js';
|
|
6
|
+
import { readFileFromDirectory } from './file.js';
|
|
7
|
+
const { mockCreateBufferFromPath } = vi.hoisted(() => ({ mockCreateBufferFromPath: vi.fn() }));
|
|
8
|
+
vi.mock('./buffer.js', async (importOriginal) => {
|
|
9
|
+
const actual = await importOriginal();
|
|
10
|
+
mockCreateBufferFromPath.mockImplementation(actual.createBufferFromPath);
|
|
11
|
+
return { ...actual, createBufferFromPath: mockCreateBufferFromPath };
|
|
12
|
+
});
|
|
13
|
+
const createErrorWithCode = (code) => Object.assign(new Error(code), { code });
|
|
14
|
+
describe('readFileFromDirectory', () => {
|
|
15
|
+
let directory;
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
directory = fs.mkdtempSync(pathModule.join(os.tmpdir(), 'file-'));
|
|
18
|
+
});
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
fs.rmSync(directory, { force: true, recursive: true });
|
|
21
|
+
});
|
|
22
|
+
it('should read the file', async () => {
|
|
23
|
+
const path = pathModule.join(directory, 'index.html');
|
|
24
|
+
fs.writeFileSync(path, '<html></html>');
|
|
25
|
+
const buffer = await readFileFromDirectory(path);
|
|
26
|
+
expect(buffer.toString()).toBe('<html></html>');
|
|
27
|
+
});
|
|
28
|
+
it('should throw a user error if the file no longer exists', async () => {
|
|
29
|
+
const path = pathModule.join(directory, 'missing.png');
|
|
30
|
+
const promise = readFileFromDirectory(path);
|
|
31
|
+
await expect(promise).rejects.toThrow(UserError);
|
|
32
|
+
await expect(promise).rejects.toThrow(`The file could not be read: ${path}. Make sure that no other process`);
|
|
33
|
+
});
|
|
34
|
+
it('should throw a user error if the file is not readable', async () => {
|
|
35
|
+
const path = pathModule.join(directory, 'index.html');
|
|
36
|
+
mockCreateBufferFromPath.mockRejectedValueOnce(createErrorWithCode('EACCES'));
|
|
37
|
+
const promise = readFileFromDirectory(path);
|
|
38
|
+
await expect(promise).rejects.toThrow(UserError);
|
|
39
|
+
await expect(promise).rejects.toThrow(`The file could not be read: ${path}. Make sure that you have permission to read the file.`);
|
|
40
|
+
});
|
|
41
|
+
it('should rethrow errors that are not related to reading the file', async () => {
|
|
42
|
+
const error = createErrorWithCode('EISDIR');
|
|
43
|
+
mockCreateBufferFromPath.mockRejectedValueOnce(error);
|
|
44
|
+
await expect(readFileFromDirectory(pathModule.join(directory, 'assets'))).rejects.toBe(error);
|
|
45
|
+
});
|
|
46
|
+
});
|
package/dist/utils/manifest.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { MANIFEST_JSON_FILE_NAME } from '../config/index.js';
|
|
2
|
-
import {
|
|
3
|
-
import { getFilesInDirectoryAndSubdirectories, writeFile } from './file.js';
|
|
2
|
+
import { getFilesInDirectoryAndSubdirectories, readFileFromDirectory, writeFile } from './file.js';
|
|
4
3
|
import { createHash } from './hash.js';
|
|
5
4
|
const ignoreFiles = ['.DS_Store', MANIFEST_JSON_FILE_NAME];
|
|
6
5
|
export const generateManifestJson = async (path) => {
|
|
@@ -9,7 +8,7 @@ export const generateManifestJson = async (path) => {
|
|
|
9
8
|
const files = await getFilesInDirectoryAndSubdirectories(path);
|
|
10
9
|
// Iterate over each file
|
|
11
10
|
for (const [index, file] of files.entries()) {
|
|
12
|
-
const fileBuffer = await
|
|
11
|
+
const fileBuffer = await readFileFromDirectory(file.path);
|
|
13
12
|
const checksum = await createHash(fileBuffer);
|
|
14
13
|
const sizeInBytes = fileBuffer.byteLength;
|
|
15
14
|
// Skip ignored files
|
|
@@ -23,5 +22,5 @@ export const generateManifestJson = async (path) => {
|
|
|
23
22
|
});
|
|
24
23
|
}
|
|
25
24
|
// Write the manifest file
|
|
26
|
-
writeFile(`${path}/${MANIFEST_JSON_FILE_NAME}`, JSON.stringify(manifestItems, null, 2));
|
|
25
|
+
await writeFile(`${path}/${MANIFEST_JSON_FILE_NAME}`, JSON.stringify(manifestItems, null, 2));
|
|
27
26
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import pathModule from 'path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { MANIFEST_JSON_FILE_NAME } from '../config/index.js';
|
|
6
|
+
import { generateManifestJson } from './manifest.js';
|
|
7
|
+
describe('generateManifestJson', () => {
|
|
8
|
+
let directory;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
directory = fs.mkdtempSync(pathModule.join(os.tmpdir(), 'manifest-'));
|
|
11
|
+
fs.mkdirSync(pathModule.join(directory, 'assets'));
|
|
12
|
+
fs.writeFileSync(pathModule.join(directory, 'index.html'), '<html></html>');
|
|
13
|
+
fs.writeFileSync(pathModule.join(directory, 'assets', 'main.js'), 'console.log(1);');
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
fs.rmSync(directory, { force: true, recursive: true });
|
|
17
|
+
});
|
|
18
|
+
it('should write the manifest file before resolving', async () => {
|
|
19
|
+
await generateManifestJson(directory);
|
|
20
|
+
expect(fs.existsSync(pathModule.join(directory, MANIFEST_JSON_FILE_NAME))).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
it('should list all files except the manifest file itself', async () => {
|
|
23
|
+
await generateManifestJson(directory);
|
|
24
|
+
const manifestItems = JSON.parse(fs.readFileSync(pathModule.join(directory, MANIFEST_JSON_FILE_NAME), 'utf8'));
|
|
25
|
+
expect(manifestItems.map((item) => item.href).sort()).toEqual(['assets/main.js', 'index.html']);
|
|
26
|
+
});
|
|
27
|
+
});
|