tsoft-cli 3.12.3 → 3.12.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 +5 -0
- package/package.json +1 -1
- package/src/theme-download.js +57 -51
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,11 @@ All notable changes to T-Soft CLI will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.12.4] - 2026-08-11
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- `theme pull` no longer fails on Windows with `EBUSY: resource busy or locked`. Putting a finished download in place moved the existing theme directory aside first, and Windows refuses to rename a directory that a terminal is sitting in or that has a file open in an editor — so the command failed for anyone whose editor had the theme open, which is the normal case. The theme directory now stays where it is: fresh files are copied in and files no longer present in the archive are removed afterwards, so nothing has to be renamed. macOS and Linux were unaffected because they allow renaming a directory that is in use.
|
|
12
|
+
|
|
8
13
|
## [3.12.3] - 2026-08-11
|
|
9
14
|
|
|
10
15
|
### Fixed
|
package/package.json
CHANGED
package/src/theme-download.js
CHANGED
|
@@ -21,79 +21,85 @@ async function stageArchive(stream, stagingPath) {
|
|
|
21
21
|
await fs.rm(zipPath, { force: true });
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* Hedefteki dosyalar silinmez; arşivdekiler üzerine yazılır. Bozuk veya yarım
|
|
28
|
-
* inen bir arşiv hedefe hiç dokunmaz.
|
|
29
|
-
*
|
|
30
|
-
* @param {import('stream').Readable} stream - Arşiv veri akışı
|
|
31
|
-
* @param {string} themePath - Arşivin açılacağı dizin
|
|
32
|
-
* @returns {Promise<void>}
|
|
33
|
-
*/
|
|
34
|
-
export async function mergeThemeArchive(stream, themePath) {
|
|
35
|
-
await fs.mkdir(themePath, { recursive: true });
|
|
24
|
+
async function withStaging(themePath, apply) {
|
|
25
|
+
const parent = path.dirname(themePath);
|
|
26
|
+
await fs.mkdir(parent, { recursive: true });
|
|
36
27
|
|
|
37
|
-
const staging = await fs.mkdtemp(path.join(
|
|
28
|
+
const staging = await fs.mkdtemp(path.join(parent, `.${path.basename(themePath)}-`));
|
|
38
29
|
|
|
39
30
|
try {
|
|
40
|
-
|
|
31
|
+
return await apply(staging);
|
|
32
|
+
} finally {
|
|
33
|
+
await fs.rm(staging, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
41
36
|
|
|
42
|
-
|
|
37
|
+
async function copyInto(sourceDir, targetDir) {
|
|
38
|
+
for (const entry of await fs.readdir(sourceDir)) {
|
|
39
|
+
await fs.cp(path.join(sourceDir, entry), path.join(targetDir, entry), {
|
|
40
|
+
recursive: true,
|
|
41
|
+
force: true,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Arşivde bulunmayan girdileri hedeften siler.
|
|
48
|
+
*
|
|
49
|
+
* Dizini yeniden adlandırmak Windows'ta içine girilmişse veya içindeki bir
|
|
50
|
+
* dosya açıksa EBUSY verir; bu yüzden hedef dizin yerinde bırakılır ve yalnızca
|
|
51
|
+
* fazlalık girdiler kaldırılır. Kalıcı bir kilit tek bir dosyayı geride
|
|
52
|
+
* bırakabilir, ama temanın geri kalanı yine güncellenmiş olur.
|
|
53
|
+
*/
|
|
54
|
+
async function removeStaleEntries(themePath, stagingPath) {
|
|
55
|
+
const fresh = new Set(await fs.readdir(stagingPath));
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
for (const entry of await fs.readdir(themePath)) {
|
|
58
|
+
if (!fresh.has(entry)) {
|
|
59
|
+
await fs.rm(path.join(themePath, entry), { recursive: true, force: true });
|
|
48
60
|
}
|
|
49
|
-
|
|
50
|
-
const zip = new AdmZip(zipPath);
|
|
51
|
-
zip.extractAllTo(themePath, true);
|
|
52
|
-
} finally {
|
|
53
|
-
await fs.rm(staging, { recursive: true, force: true });
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
/**
|
|
58
|
-
* Tema arşivini indirir, doğrular ve ancak ondan sonra
|
|
65
|
+
* Tema arşivini indirir, doğrular ve ancak ondan sonra hedefe yerleştirir.
|
|
59
66
|
*
|
|
60
67
|
* Arşiv hedefin yanındaki geçici dizine açılır; indirme yarıda koparsa veya
|
|
61
|
-
* arşiv bozuksa hedef dizine dokunulmaz.
|
|
62
|
-
*
|
|
68
|
+
* arşiv bozuksa hedef dizine dokunulmaz. Hedef dizinin kendisi taşınmaz,
|
|
69
|
+
* yalnızca içeriği değiştirilir — dizin bir editörde veya kabukta açıkken de
|
|
70
|
+
* çalışır.
|
|
63
71
|
*
|
|
64
72
|
* @param {import('stream').Readable} stream - Arşiv veri akışı
|
|
65
73
|
* @param {string} themePath - Temanın açılacağı hedef dizin
|
|
66
74
|
* @returns {Promise<void>}
|
|
67
75
|
*/
|
|
68
76
|
export async function extractThemeArchive(stream, themePath) {
|
|
69
|
-
|
|
70
|
-
await fs.mkdir(parent, { recursive: true });
|
|
71
|
-
|
|
72
|
-
const staging = await fs.mkdtemp(path.join(parent, `.${path.basename(themePath)}-`));
|
|
73
|
-
const backup = `${staging}-previous`;
|
|
74
|
-
|
|
75
|
-
try {
|
|
77
|
+
await withStaging(themePath, async (staging) => {
|
|
76
78
|
await stageArchive(stream, staging);
|
|
77
|
-
} catch (error) {
|
|
78
|
-
await fs.rm(staging, { recursive: true, force: true });
|
|
79
|
-
throw error;
|
|
80
|
-
}
|
|
81
79
|
|
|
82
|
-
|
|
80
|
+
await fs.mkdir(themePath, { recursive: true });
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
await
|
|
86
|
-
}
|
|
82
|
+
await copyInto(staging, themePath);
|
|
83
|
+
await removeStaleEntries(themePath, staging);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Tema arşivini indirir, doğrular ve ancak ondan sonra hedefin üzerine açar.
|
|
89
|
+
*
|
|
90
|
+
* Hedefteki dosyalar silinmez; arşivdekiler üzerine yazılır. Bozuk veya yarım
|
|
91
|
+
* inen bir arşiv hedefe hiç dokunmaz.
|
|
92
|
+
*
|
|
93
|
+
* @param {import('stream').Readable} stream - Arşiv veri akışı
|
|
94
|
+
* @param {string} themePath - Arşivin açılacağı dizin
|
|
95
|
+
* @returns {Promise<void>}
|
|
96
|
+
*/
|
|
97
|
+
export async function mergeThemeArchive(stream, themePath) {
|
|
98
|
+
await withStaging(themePath, async (staging) => {
|
|
99
|
+
await stageArchive(stream, staging);
|
|
100
|
+
|
|
101
|
+
await fs.mkdir(themePath, { recursive: true });
|
|
97
102
|
|
|
98
|
-
|
|
103
|
+
await copyInto(staging, themePath);
|
|
104
|
+
});
|
|
99
105
|
}
|