wyvrnpm 1.2.0 → 1.3.2
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/README.md +413 -413
- package/bin/wyvrn.js +224 -157
- package/package.json +37 -37
- package/src/commands/clean.js +40 -40
- package/src/commands/configure.js +80 -80
- package/src/commands/init.js +33 -33
- package/src/commands/install.js +109 -109
- package/src/commands/link.js +316 -0
- package/src/commands/publish.js +180 -167
- package/src/config.js +61 -60
- package/src/download.js +172 -164
- package/src/index.js +9 -9
- package/src/link-utils.js +95 -0
- package/src/manifest.js +77 -77
- package/src/providers/base.js +56 -45
- package/src/providers/file.js +71 -65
- package/src/providers/http.js +118 -102
- package/src/providers/index.js +43 -43
- package/src/providers/s3.js +136 -109
- package/src/resolve.js +257 -200
package/src/config.js
CHANGED
|
@@ -1,60 +1,61 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Returns the directory where wyvrnpm stores its config.
|
|
9
|
-
* Windows : %LOCALAPPDATA%\wyvrnpm
|
|
10
|
-
* Other : ~/.config/wyvrnpm
|
|
11
|
-
*/
|
|
12
|
-
function getConfigDir() {
|
|
13
|
-
if (process.env.LOCALAPPDATA) {
|
|
14
|
-
return path.join(process.env.LOCALAPPDATA, 'wyvrnpm');
|
|
15
|
-
}
|
|
16
|
-
return path.join(os.homedir(), '.config', 'wyvrnpm');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Full path to config.json. */
|
|
20
|
-
function getConfigPath() {
|
|
21
|
-
return path.join(getConfigDir(), 'config.json');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Read and parse config.json.
|
|
26
|
-
* Returns a default empty config if the file doesn't exist yet.
|
|
27
|
-
* @returns {{ installSources: SourceEntry[], publishSources: SourceEntry[] }}
|
|
28
|
-
*/
|
|
29
|
-
function readConfig() {
|
|
30
|
-
const configPath = getConfigPath();
|
|
31
|
-
if (!fs.existsSync(configPath)) {
|
|
32
|
-
return { installSources: [], publishSources: [] };
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
36
|
-
return {
|
|
37
|
-
installSources: raw.installSources ?? [],
|
|
38
|
-
publishSources: raw.publishSources ?? [],
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
fs.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns the directory where wyvrnpm stores its config.
|
|
9
|
+
* Windows : %LOCALAPPDATA%\wyvrnpm
|
|
10
|
+
* Other : ~/.config/wyvrnpm
|
|
11
|
+
*/
|
|
12
|
+
function getConfigDir() {
|
|
13
|
+
if (process.env.LOCALAPPDATA) {
|
|
14
|
+
return path.join(process.env.LOCALAPPDATA, 'wyvrnpm');
|
|
15
|
+
}
|
|
16
|
+
return path.join(os.homedir(), '.config', 'wyvrnpm');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Full path to config.json. */
|
|
20
|
+
function getConfigPath() {
|
|
21
|
+
return path.join(getConfigDir(), 'config.json');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Read and parse config.json.
|
|
26
|
+
* Returns a default empty config if the file doesn't exist yet.
|
|
27
|
+
* @returns {{ installSources: SourceEntry[], publishSources: SourceEntry[], linkedPackages: Object<string, string> }}
|
|
28
|
+
*/
|
|
29
|
+
function readConfig() {
|
|
30
|
+
const configPath = getConfigPath();
|
|
31
|
+
if (!fs.existsSync(configPath)) {
|
|
32
|
+
return { installSources: [], publishSources: [], linkedPackages: {} };
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
36
|
+
return {
|
|
37
|
+
installSources: raw.installSources ?? [],
|
|
38
|
+
publishSources: raw.publishSources ?? [],
|
|
39
|
+
linkedPackages: raw.linkedPackages ?? {},
|
|
40
|
+
};
|
|
41
|
+
} catch {
|
|
42
|
+
console.warn('[wyvrn] Warning: could not parse config.json — using defaults');
|
|
43
|
+
return { installSources: [], publishSources: [], linkedPackages: {} };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Persist a config object to disk.
|
|
49
|
+
* @param {{ installSources: SourceEntry[], publishSources: SourceEntry[] }} config
|
|
50
|
+
*/
|
|
51
|
+
function writeConfig(config) {
|
|
52
|
+
const dir = getConfigDir();
|
|
53
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
54
|
+
fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { getConfigDir, getConfigPath, readConfig, writeConfig };
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @typedef {{ name: string, url: string, profile?: string, token?: string }} SourceEntry
|
|
61
|
+
*/
|
package/src/download.js
CHANGED
|
@@ -1,164 +1,172 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { pipeline } = require('stream/promises');
|
|
6
|
-
const { Readable } = require('stream');
|
|
7
|
-
const StreamZip = require('node-stream-zip');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* @param {string}
|
|
14
|
-
* @param {
|
|
15
|
-
* @param {
|
|
16
|
-
* @
|
|
17
|
-
* @
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* @param {string}
|
|
44
|
-
* @param {string
|
|
45
|
-
* @param {string}
|
|
46
|
-
* @param {
|
|
47
|
-
* @param {
|
|
48
|
-
* @param {
|
|
49
|
-
* @
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
`${base}/${platform}/${name}/${version}/
|
|
65
|
-
`${base}
|
|
66
|
-
`${base}/common/${name}/${version}/
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* @param {string
|
|
85
|
-
* @param {string}
|
|
86
|
-
* @param {string}
|
|
87
|
-
* @param {
|
|
88
|
-
* @param {
|
|
89
|
-
* @
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
fs.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { pipeline } = require('stream/promises');
|
|
6
|
+
const { Readable } = require('stream');
|
|
7
|
+
const StreamZip = require('node-stream-zip');
|
|
8
|
+
const { isLink, getLinkTarget } = require('./link-utils');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Downloads a single URL to a local file path, respecting the given timeout.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} url - Remote URL to download.
|
|
14
|
+
* @param {string} destPath - Local path to write the response body to.
|
|
15
|
+
* @param {Function} httpClient - fetch-compatible function.
|
|
16
|
+
* @param {number} timeoutMs - Abort timeout in milliseconds.
|
|
17
|
+
* @returns {Promise<void>}
|
|
18
|
+
* @throws {Error} On non-OK HTTP status or network / timeout error.
|
|
19
|
+
*/
|
|
20
|
+
async function downloadFile(url, destPath, httpClient, timeoutMs) {
|
|
21
|
+
const controller = new AbortController();
|
|
22
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
23
|
+
|
|
24
|
+
let response;
|
|
25
|
+
try {
|
|
26
|
+
response = await httpClient(url, { signal: controller.signal });
|
|
27
|
+
} finally {
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(`HTTP ${response.status} for ${url}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Stream the response body directly to disk — avoids loading large files into RAM.
|
|
36
|
+
const fileStream = fs.createWriteStream(destPath);
|
|
37
|
+
await pipeline(Readable.fromWeb(response.body), fileStream);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolves which source URL contains a given package by probing each one in order.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} name
|
|
44
|
+
* @param {string} version
|
|
45
|
+
* @param {string[]} packageSources
|
|
46
|
+
* @param {string} platform
|
|
47
|
+
* @param {Function} httpClient
|
|
48
|
+
* @param {number} timeoutMs
|
|
49
|
+
* @param {string} destZipPath - Where to save the downloaded zip.
|
|
50
|
+
* @returns {Promise<boolean>} True if successfully downloaded, false otherwise.
|
|
51
|
+
*/
|
|
52
|
+
async function tryDownloadFromSources(
|
|
53
|
+
name,
|
|
54
|
+
version,
|
|
55
|
+
packageSources,
|
|
56
|
+
platform,
|
|
57
|
+
httpClient,
|
|
58
|
+
timeoutMs,
|
|
59
|
+
destZipPath,
|
|
60
|
+
) {
|
|
61
|
+
for (const source of packageSources) {
|
|
62
|
+
const base = source.replace(/\/$/, '');
|
|
63
|
+
const candidates = [
|
|
64
|
+
`${base}/${platform}/${name}/${version}/wyvrn.zip`,
|
|
65
|
+
`${base}/${platform}/${name}/${version}/razer.zip`,
|
|
66
|
+
`${base}/common/${name}/${version}/wyvrn.zip`,
|
|
67
|
+
`${base}/common/${name}/${version}/razer.zip`,
|
|
68
|
+
];
|
|
69
|
+
for (const url of candidates) {
|
|
70
|
+
try {
|
|
71
|
+
await downloadFile(url, destZipPath, httpClient, timeoutMs);
|
|
72
|
+
return true;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.warn(`[wyvrn] Could not download ${name}@${version} from ${url}: ${err.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Downloads and extracts all resolved dependencies that are not already present on disk.
|
|
83
|
+
*
|
|
84
|
+
* @param {Map<string, string>} resolvedDeps - Map of package name -> version string.
|
|
85
|
+
* @param {string[]} packageSources - Ordered list of base URLs to try.
|
|
86
|
+
* @param {string} platform - Target platform string (e.g. "win_x64").
|
|
87
|
+
* @param {string} razerDir - Local directory used for extraction (e.g. "wyvrn_internal").
|
|
88
|
+
* @param {Function} httpClient - fetch-compatible function.
|
|
89
|
+
* @param {number} timeout - HTTP timeout in seconds.
|
|
90
|
+
* @returns {Promise<void>}
|
|
91
|
+
*/
|
|
92
|
+
async function downloadDependencies(
|
|
93
|
+
resolvedDeps,
|
|
94
|
+
packageSources,
|
|
95
|
+
platform,
|
|
96
|
+
razerDir,
|
|
97
|
+
httpClient,
|
|
98
|
+
timeout,
|
|
99
|
+
) {
|
|
100
|
+
const timeoutMs = timeout * 1000;
|
|
101
|
+
|
|
102
|
+
if (!fs.existsSync(razerDir)) {
|
|
103
|
+
fs.mkdirSync(razerDir, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const [name, version] of resolvedDeps) {
|
|
107
|
+
const extractDir = path.join(razerDir, name);
|
|
108
|
+
const versionFile = path.join(extractDir, '.wyvrn-version');
|
|
109
|
+
|
|
110
|
+
// Skip linked packages - don't overwrite symlinks/junctions
|
|
111
|
+
if (isLink(extractDir)) {
|
|
112
|
+
const linkTarget = getLinkTarget(extractDir);
|
|
113
|
+
console.log(`[wyvrn] Linked: ${name} → ${linkTarget} (skipping download)`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (fs.existsSync(extractDir)) {
|
|
118
|
+
const installedVersion = fs.existsSync(versionFile)
|
|
119
|
+
? fs.readFileSync(versionFile, 'utf8').trim()
|
|
120
|
+
: null;
|
|
121
|
+
if (installedVersion === version) {
|
|
122
|
+
console.log(`[wyvrn] Already present: ${name}@${version} — skipping`);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
console.log(`[wyvrn] Version changed: ${name} ${installedVersion} → ${version}, re-downloading`);
|
|
126
|
+
fs.rmSync(extractDir, { recursive: true, force: true });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log(`[wyvrn] Downloading: ${name}@${version}`);
|
|
130
|
+
|
|
131
|
+
const destZipPath = path.join(razerDir, `${name}.zip`);
|
|
132
|
+
|
|
133
|
+
const ok = await tryDownloadFromSources(
|
|
134
|
+
name,
|
|
135
|
+
version,
|
|
136
|
+
packageSources,
|
|
137
|
+
platform,
|
|
138
|
+
httpClient,
|
|
139
|
+
timeoutMs,
|
|
140
|
+
destZipPath,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if (!ok) {
|
|
144
|
+
console.warn(
|
|
145
|
+
`[wyvrn] Warning: package ${name}@${version} was not found in any configured source`,
|
|
146
|
+
);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
152
|
+
const zip = new StreamZip.async({ file: destZipPath });
|
|
153
|
+
await zip.extract(null, extractDir);
|
|
154
|
+
await zip.close();
|
|
155
|
+
fs.writeFileSync(versionFile, version, 'utf8');
|
|
156
|
+
console.log(`[wyvrn] Extracted: ${name}@${version} → ${extractDir}`);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
console.warn(`[wyvrn] Failed to extract ${name}@${version}: ${err.message}`);
|
|
159
|
+
} finally {
|
|
160
|
+
// Always remove the zip, even if extraction failed.
|
|
161
|
+
try {
|
|
162
|
+
fs.unlinkSync(destZipPath);
|
|
163
|
+
} catch {
|
|
164
|
+
// Ignore cleanup errors.
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = {
|
|
171
|
+
downloadDependencies,
|
|
172
|
+
};
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
module.exports = {
|
|
4
|
-
init: require('./commands/init'),
|
|
5
|
-
install: require('./commands/install'),
|
|
6
|
-
clean: require('./commands/clean'),
|
|
7
|
-
publish: require('./commands/publish'),
|
|
8
|
-
configure: require('./commands/configure'),
|
|
9
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
init: require('./commands/init'),
|
|
5
|
+
install: require('./commands/install'),
|
|
6
|
+
clean: require('./commands/clean'),
|
|
7
|
+
publish: require('./commands/publish'),
|
|
8
|
+
configure: require('./commands/configure'),
|
|
9
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a symlink/junction from linkPath pointing to targetPath.
|
|
8
|
+
* Uses junctions on Windows (no admin rights required), symlinks on Unix.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} targetPath - The actual directory to link to.
|
|
11
|
+
* @param {string} linkPath - The path where the link will be created.
|
|
12
|
+
*/
|
|
13
|
+
function createLink(targetPath, linkPath) {
|
|
14
|
+
const absoluteTarget = path.resolve(targetPath);
|
|
15
|
+
const absoluteLink = path.resolve(linkPath);
|
|
16
|
+
|
|
17
|
+
// Ensure parent directory exists
|
|
18
|
+
const parentDir = path.dirname(absoluteLink);
|
|
19
|
+
if (!fs.existsSync(parentDir)) {
|
|
20
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Remove existing link/directory if present
|
|
24
|
+
if (fs.existsSync(absoluteLink)) {
|
|
25
|
+
const stats = fs.lstatSync(absoluteLink);
|
|
26
|
+
if (stats.isSymbolicLink()) {
|
|
27
|
+
fs.unlinkSync(absoluteLink);
|
|
28
|
+
} else {
|
|
29
|
+
fs.rmSync(absoluteLink, { recursive: true, force: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Use 'junction' on Windows (no admin required), 'dir' on Unix
|
|
34
|
+
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
|
|
35
|
+
fs.symlinkSync(absoluteTarget, absoluteLink, linkType);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Checks if a path is a symlink or junction.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} linkPath - Path to check.
|
|
42
|
+
* @returns {boolean} True if it's a symlink/junction.
|
|
43
|
+
*/
|
|
44
|
+
function isLink(linkPath) {
|
|
45
|
+
try {
|
|
46
|
+
const stats = fs.lstatSync(linkPath);
|
|
47
|
+
return stats.isSymbolicLink();
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Removes a symlink/junction without affecting the target directory.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} linkPath - Path to the symlink/junction to remove.
|
|
57
|
+
* @returns {boolean} True if removed, false if it didn't exist or wasn't a link.
|
|
58
|
+
*/
|
|
59
|
+
function removeLink(linkPath) {
|
|
60
|
+
try {
|
|
61
|
+
const stats = fs.lstatSync(linkPath);
|
|
62
|
+
if (stats.isSymbolicLink()) {
|
|
63
|
+
fs.unlinkSync(linkPath);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Gets the target path of a symlink/junction.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} linkPath - Path to the symlink/junction.
|
|
76
|
+
* @returns {string|null} The target path, or null if not a link.
|
|
77
|
+
*/
|
|
78
|
+
function getLinkTarget(linkPath) {
|
|
79
|
+
try {
|
|
80
|
+
const stats = fs.lstatSync(linkPath);
|
|
81
|
+
if (stats.isSymbolicLink()) {
|
|
82
|
+
return fs.readlinkSync(linkPath);
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
createLink,
|
|
92
|
+
isLink,
|
|
93
|
+
removeLink,
|
|
94
|
+
getLinkTarget,
|
|
95
|
+
};
|