@nocobase/utils 2.1.0-beta.9 → 2.1.1
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/lib/index.d.ts +2 -0
- package/lib/index.js +5 -1
- package/lib/parse-filter.js +5 -1
- package/lib/server-request.d.ts +36 -0
- package/lib/server-request.js +128 -0
- package/lib/storage-path.d.ts +25 -0
- package/lib/storage-path.js +71 -0
- package/package.json +4 -2
- package/plugin-package.d.ts +45 -0
- package/plugin-package.js +268 -0
- package/plugin-symlink.d.ts +4 -1
- package/plugin-symlink.js +125 -17
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -80,6 +80,8 @@ __reExport(src_exports, require("./variable-usage"), module.exports);
|
|
|
80
80
|
__reExport(src_exports, require("./wrap-middleware"), module.exports);
|
|
81
81
|
__reExport(src_exports, require("./run-sql"), module.exports);
|
|
82
82
|
__reExport(src_exports, require("./liquidjs"), module.exports);
|
|
83
|
+
__reExport(src_exports, require("./server-request"), module.exports);
|
|
84
|
+
__reExport(src_exports, require("./storage-path"), module.exports);
|
|
83
85
|
// Annotate the CommonJS export names for ESM import in node:
|
|
84
86
|
0 && (module.exports = {
|
|
85
87
|
Schema,
|
|
@@ -119,5 +121,7 @@ __reExport(src_exports, require("./liquidjs"), module.exports);
|
|
|
119
121
|
...require("./variable-usage"),
|
|
120
122
|
...require("./wrap-middleware"),
|
|
121
123
|
...require("./run-sql"),
|
|
122
|
-
...require("./liquidjs")
|
|
124
|
+
...require("./liquidjs"),
|
|
125
|
+
...require("./server-request"),
|
|
126
|
+
...require("./storage-path")
|
|
123
127
|
});
|
package/lib/parse-filter.js
CHANGED
|
@@ -128,6 +128,10 @@ function isDate(input) {
|
|
|
128
128
|
return input instanceof Date || Object.prototype.toString.call(input) === "[object Date]";
|
|
129
129
|
}
|
|
130
130
|
__name(isDate, "isDate");
|
|
131
|
+
function isDateFieldWithoutTimezone(field) {
|
|
132
|
+
return (field == null ? void 0 : field.type) === "dateOnly" || (field == null ? void 0 : field.type) === "datetimeNoTz" || (field == null ? void 0 : field.constructor.name) === "DateOnlyField" || (field == null ? void 0 : field.constructor.name) === "DatetimeNoTzField";
|
|
133
|
+
}
|
|
134
|
+
__name(isDateFieldWithoutTimezone, "isDateFieldWithoutTimezone");
|
|
131
135
|
const dateValueWrapper = /* @__PURE__ */ __name((value, timezone) => {
|
|
132
136
|
if (!value) {
|
|
133
137
|
return null;
|
|
@@ -195,7 +199,7 @@ const parseFilter = /* @__PURE__ */ __name(async (filter, opts = {}) => {
|
|
|
195
199
|
}
|
|
196
200
|
if (isDateOperator(operator)) {
|
|
197
201
|
const field = getField == null ? void 0 : getField(path);
|
|
198
|
-
if ((field
|
|
202
|
+
if (isDateFieldWithoutTimezone(field)) {
|
|
199
203
|
if (value.type) {
|
|
200
204
|
return (0, import_dateRangeUtils.getDayRangeByParams)({ ...value, timezone: (field == null ? void 0 : field.timezone) || timezone });
|
|
201
205
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
10
|
+
/**
|
|
11
|
+
* Match a hostname against a domain pattern.
|
|
12
|
+
* `*.example.com` matches exactly one subdomain level (e.g. `foo.example.com`)
|
|
13
|
+
* but not `example.com` itself or deeper levels like `a.b.example.com`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function matchesDomainPattern(hostname: string, pattern: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Validate a URL against the SERVER_REQUEST_WHITELIST environment variable.
|
|
18
|
+
*
|
|
19
|
+
* Throws an error if:
|
|
20
|
+
* - The URL scheme is not http or https.
|
|
21
|
+
* - SERVER_REQUEST_WHITELIST is set and the host does not match any entry.
|
|
22
|
+
*
|
|
23
|
+
* Silently returns for relative URLs (no scheme) so that internal API calls
|
|
24
|
+
* that use a relative path are not affected.
|
|
25
|
+
*
|
|
26
|
+
* Prefer using {@link serverRequest} over calling this directly.
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkUrlAgainstWhitelist(url?: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Drop-in replacement for `axios.request()` with built-in SSRF protection.
|
|
31
|
+
*
|
|
32
|
+
* Validates `config.url` against {@link checkUrlAgainstWhitelist} before
|
|
33
|
+
* forwarding to axios. Use this instead of calling axios directly for all
|
|
34
|
+
* server-initiated outbound HTTP requests.
|
|
35
|
+
*/
|
|
36
|
+
export declare function serverRequest<T = any>(config: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
20
|
+
};
|
|
21
|
+
var __copyProps = (to, from, except, desc) => {
|
|
22
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(from))
|
|
24
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
25
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
30
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
31
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
32
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
33
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
34
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
35
|
+
mod
|
|
36
|
+
));
|
|
37
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
38
|
+
var server_request_exports = {};
|
|
39
|
+
__export(server_request_exports, {
|
|
40
|
+
checkUrlAgainstWhitelist: () => checkUrlAgainstWhitelist,
|
|
41
|
+
matchesDomainPattern: () => matchesDomainPattern,
|
|
42
|
+
serverRequest: () => serverRequest
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(server_request_exports);
|
|
45
|
+
var import_ipaddr = __toESM(require("ipaddr.js"));
|
|
46
|
+
var import_axios = __toESM(require("axios"));
|
|
47
|
+
const ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
48
|
+
function matchesIpEntry(hostname, entry) {
|
|
49
|
+
try {
|
|
50
|
+
let addr = import_ipaddr.default.parse(hostname);
|
|
51
|
+
if (addr.kind() === "ipv6" && addr.isIPv4MappedAddress()) {
|
|
52
|
+
addr = addr.toIPv4Address();
|
|
53
|
+
}
|
|
54
|
+
if (entry.includes("/")) {
|
|
55
|
+
const cidr = import_ipaddr.default.parseCIDR(entry);
|
|
56
|
+
if (addr.kind() !== cidr[0].kind()) return false;
|
|
57
|
+
if (addr.kind() === "ipv4") {
|
|
58
|
+
return addr.match(cidr);
|
|
59
|
+
}
|
|
60
|
+
return addr.match(cidr);
|
|
61
|
+
}
|
|
62
|
+
let entryAddr = import_ipaddr.default.parse(entry);
|
|
63
|
+
if (entryAddr.kind() === "ipv6" && entryAddr.isIPv4MappedAddress()) {
|
|
64
|
+
entryAddr = entryAddr.toIPv4Address();
|
|
65
|
+
}
|
|
66
|
+
return addr.toString() === entryAddr.toString();
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
__name(matchesIpEntry, "matchesIpEntry");
|
|
72
|
+
function matchesDomainPattern(hostname, pattern) {
|
|
73
|
+
const h = hostname.toLowerCase();
|
|
74
|
+
const p = pattern.toLowerCase();
|
|
75
|
+
if (p.startsWith("*.")) {
|
|
76
|
+
const suffix = p.slice(1);
|
|
77
|
+
if (!h.endsWith(suffix) || h.length <= suffix.length) return false;
|
|
78
|
+
const prefix = h.slice(0, h.length - suffix.length);
|
|
79
|
+
return !prefix.includes(".");
|
|
80
|
+
}
|
|
81
|
+
return h === p;
|
|
82
|
+
}
|
|
83
|
+
__name(matchesDomainPattern, "matchesDomainPattern");
|
|
84
|
+
function matchesEntry(hostname, entry) {
|
|
85
|
+
const e = entry.trim();
|
|
86
|
+
if (!e) return false;
|
|
87
|
+
return import_ipaddr.default.isValid(hostname) ? matchesIpEntry(hostname, e) : matchesDomainPattern(hostname, e);
|
|
88
|
+
}
|
|
89
|
+
__name(matchesEntry, "matchesEntry");
|
|
90
|
+
function checkUrlAgainstWhitelist(url) {
|
|
91
|
+
if (!url) return;
|
|
92
|
+
if (!url.includes("://")) return;
|
|
93
|
+
let parsed;
|
|
94
|
+
try {
|
|
95
|
+
parsed = new URL(url);
|
|
96
|
+
} catch {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (!ALLOWED_PROTOCOLS.has(parsed.protocol)) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`URL scheme "${parsed.protocol.replace(":", "")}" is not allowed. Only http and https are permitted.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const whitelist = process.env.SERVER_REQUEST_WHITELIST;
|
|
105
|
+
if (!whitelist || !whitelist.trim()) return;
|
|
106
|
+
const entries = whitelist.split(",").map((e) => e.trim()).filter(Boolean);
|
|
107
|
+
if (entries.length === 0) return;
|
|
108
|
+
const { hostname } = parsed;
|
|
109
|
+
const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
if (matchesEntry(host, entry)) return;
|
|
112
|
+
}
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Outbound request to "${host}" is blocked. Add it to SERVER_REQUEST_WHITELIST to allow this request.`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
__name(checkUrlAgainstWhitelist, "checkUrlAgainstWhitelist");
|
|
118
|
+
async function serverRequest(config) {
|
|
119
|
+
checkUrlAgainstWhitelist(config.url);
|
|
120
|
+
return import_axios.default.request(config);
|
|
121
|
+
}
|
|
122
|
+
__name(serverRequest, "serverRequest");
|
|
123
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
124
|
+
0 && (module.exports = {
|
|
125
|
+
checkUrlAgainstWhitelist,
|
|
126
|
+
matchesDomainPattern,
|
|
127
|
+
serverRequest
|
|
128
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Absolute path to the application storage root (same rules as CLI `resolveStorageRoot` in `cli-v1/src/util.js`).
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveStorageRoot(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Join path segments under the application storage root.
|
|
15
|
+
* Resolution matches CLI `resolveStorageRoot()` / `initEnv`: use `STORAGE_PATH` when set
|
|
16
|
+
* (absolute or relative to cwd), otherwise `<cwd>/storage`.
|
|
17
|
+
*
|
|
18
|
+
* @example storagePathJoin('tmp')
|
|
19
|
+
* @example storagePathJoin('cache', 'apps', appName)
|
|
20
|
+
*/
|
|
21
|
+
export declare function storagePathJoin(...segments: string[]): string;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve plugin storage path: `PLUGIN_STORAGE_PATH` first, else `<STORAGE_PATH>/plugins`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolvePluginStoragePath(): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
20
|
+
};
|
|
21
|
+
var __copyProps = (to, from, except, desc) => {
|
|
22
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(from))
|
|
24
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
25
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
30
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
31
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
32
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
33
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
34
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
35
|
+
mod
|
|
36
|
+
));
|
|
37
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
38
|
+
var storage_path_exports = {};
|
|
39
|
+
__export(storage_path_exports, {
|
|
40
|
+
resolvePluginStoragePath: () => resolvePluginStoragePath,
|
|
41
|
+
resolveStorageRoot: () => resolveStorageRoot,
|
|
42
|
+
storagePathJoin: () => storagePathJoin
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(storage_path_exports);
|
|
45
|
+
var import_path = __toESM(require("path"));
|
|
46
|
+
function resolveStorageRoot() {
|
|
47
|
+
const raw = process.env.STORAGE_PATH;
|
|
48
|
+
if (raw) {
|
|
49
|
+
return import_path.default.isAbsolute(raw) ? raw : import_path.default.resolve(process.cwd(), raw);
|
|
50
|
+
}
|
|
51
|
+
return import_path.default.resolve(process.cwd(), "storage");
|
|
52
|
+
}
|
|
53
|
+
__name(resolveStorageRoot, "resolveStorageRoot");
|
|
54
|
+
function storagePathJoin(...segments) {
|
|
55
|
+
return import_path.default.join(resolveStorageRoot(), ...segments);
|
|
56
|
+
}
|
|
57
|
+
__name(storagePathJoin, "storagePathJoin");
|
|
58
|
+
function resolvePluginStoragePath() {
|
|
59
|
+
if (process.env.PLUGIN_STORAGE_PATH) {
|
|
60
|
+
const pluginStoragePath = process.env.PLUGIN_STORAGE_PATH;
|
|
61
|
+
return import_path.default.isAbsolute(pluginStoragePath) ? pluginStoragePath : import_path.default.resolve(process.cwd(), pluginStoragePath);
|
|
62
|
+
}
|
|
63
|
+
return storagePathJoin("plugins");
|
|
64
|
+
}
|
|
65
|
+
__name(resolvePluginStoragePath, "resolvePluginStoragePath");
|
|
66
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
67
|
+
0 && (module.exports = {
|
|
68
|
+
resolvePluginStoragePath,
|
|
69
|
+
resolveStorageRoot,
|
|
70
|
+
storagePathJoin
|
|
71
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/utils",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"types": "./lib/index.d.ts",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -8,16 +8,18 @@
|
|
|
8
8
|
"@budibase/handlebars-helpers": "0.14.0",
|
|
9
9
|
"@hapi/topo": "^6.0.0",
|
|
10
10
|
"@rc-component/mini-decimal": "^1.1.0",
|
|
11
|
+
"axios": "^1.7.0",
|
|
11
12
|
"dayjs": "^1.11.9",
|
|
12
13
|
"deepmerge": "^4.2.2",
|
|
13
14
|
"flat-to-nested": "^1.1.1",
|
|
14
15
|
"fs-extra": "^11.1.1",
|
|
15
16
|
"graphlib": "^2.1.8",
|
|
16
17
|
"handlebars": "^4.7.8",
|
|
18
|
+
"ipaddr.js": "^1.9.1",
|
|
17
19
|
"liquidjs": "^10.23.0",
|
|
18
20
|
"multer": "^1.4.5-lts.2",
|
|
19
21
|
"object-path": "^0.11.8",
|
|
20
22
|
"ses": "^1.14.0"
|
|
21
23
|
},
|
|
22
|
-
"gitHead": "
|
|
24
|
+
"gitHead": "13ba59813dc5420e61cb403960d588f9ada7f28e"
|
|
23
25
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface PluginPackageInfo {
|
|
2
|
+
name: string;
|
|
3
|
+
packageName: string;
|
|
4
|
+
origins: string[];
|
|
5
|
+
resolvedPath: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ParsePluginNameOptions {
|
|
9
|
+
nodeModulesPath?: string;
|
|
10
|
+
pluginPackagePrefixes?: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PresetPackageJsonLike {
|
|
14
|
+
dependencies?: Record<string, string>;
|
|
15
|
+
builtIn?: string[];
|
|
16
|
+
deprecated?: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export declare const DEFAULT_PLUGIN_PACKAGE_PREFIXES: string[];
|
|
20
|
+
export declare function splitPluginNames(value?: string): string[];
|
|
21
|
+
export declare function getPluginPackagePrefixes(): string[];
|
|
22
|
+
export declare function getPluginNameFromPackageName(packageName: string, prefixes?: string[]): string;
|
|
23
|
+
export declare function isValidPackageName(packageName: string): boolean;
|
|
24
|
+
export declare function looksLikePluginPackage(packageName: string, prefixes?: string[]): boolean;
|
|
25
|
+
export declare function parsePluginName(
|
|
26
|
+
nameOrPkg: string,
|
|
27
|
+
options?: ParsePluginNameOptions,
|
|
28
|
+
): Promise<{ name: string; packageName: string }>;
|
|
29
|
+
export declare function getPresetNocoBasePackageJson(options?: {
|
|
30
|
+
nodeModulesPath?: string;
|
|
31
|
+
cwd?: string;
|
|
32
|
+
}): Promise<PresetPackageJsonLike | null>;
|
|
33
|
+
export declare function resolvePluginPackagePath(
|
|
34
|
+
packageName: string,
|
|
35
|
+
options?: {
|
|
36
|
+
nodeModulesPath?: string;
|
|
37
|
+
storagePluginsPath?: string;
|
|
38
|
+
},
|
|
39
|
+
): Promise<string>;
|
|
40
|
+
export declare function discoverPluginPackages(options?: {
|
|
41
|
+
nodeModulesPath?: string;
|
|
42
|
+
storagePluginsPath?: string;
|
|
43
|
+
cwd?: string;
|
|
44
|
+
pluginPackagePrefixes?: string[];
|
|
45
|
+
}): Promise<PluginPackageInfo[]>;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const {
|
|
4
|
+
getPluginSourceRoots,
|
|
5
|
+
getStoragePluginNames,
|
|
6
|
+
resolvePluginSourcePath,
|
|
7
|
+
resolvePluginStoragePath,
|
|
8
|
+
} = require('./plugin-symlink');
|
|
9
|
+
|
|
10
|
+
const DEFAULT_PLUGIN_PACKAGE_PREFIXES = ['@nocobase/plugin-', '@nocobase/preset-'];
|
|
11
|
+
const PRESET_PACKAGE_NAME = '@nocobase/preset-nocobase';
|
|
12
|
+
|
|
13
|
+
function uniqStrings(values) {
|
|
14
|
+
return [...new Set(values.filter(Boolean))];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function splitPluginNames(value) {
|
|
18
|
+
return uniqStrings(
|
|
19
|
+
String(value || '')
|
|
20
|
+
.split(',')
|
|
21
|
+
.map((item) => item.trim())
|
|
22
|
+
.filter(Boolean),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getPluginPackagePrefixes() {
|
|
27
|
+
const prefixes = splitPluginNames(process.env.PLUGIN_PACKAGE_PREFIX || DEFAULT_PLUGIN_PACKAGE_PREFIXES.join(','));
|
|
28
|
+
return prefixes.length > 0 ? prefixes : DEFAULT_PLUGIN_PACKAGE_PREFIXES;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getPluginNameFromPackageName(packageName, prefixes = getPluginPackagePrefixes()) {
|
|
32
|
+
const prefix = prefixes.find((item) => packageName.startsWith(item));
|
|
33
|
+
return prefix ? packageName.slice(prefix.length) : packageName;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isValidPackageName(packageName) {
|
|
37
|
+
if (!packageName || typeof packageName !== 'string') {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (packageName.includes('\0')) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (path.isAbsolute(packageName)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (packageName.includes('..') || packageName.includes('\\')) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(packageName);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function looksLikePluginPackage(packageName) {
|
|
57
|
+
return isValidPackageName(packageName);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function parsePluginName(nameOrPkg, options = {}) {
|
|
61
|
+
const input = String(nameOrPkg || '').trim();
|
|
62
|
+
const prefixes = options.pluginPackagePrefixes || getPluginPackagePrefixes();
|
|
63
|
+
|
|
64
|
+
if (!input) {
|
|
65
|
+
return { name: '', packageName: '' };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const matchedPrefix = prefixes.find((prefix) => input.startsWith(prefix));
|
|
69
|
+
if (matchedPrefix) {
|
|
70
|
+
return {
|
|
71
|
+
packageName: input,
|
|
72
|
+
name: input.slice(matchedPrefix.length),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const nodeModulesPath = String(options.nodeModulesPath || process.env.NODE_MODULES_PATH || '').trim();
|
|
77
|
+
if (nodeModulesPath) {
|
|
78
|
+
for (const prefix of prefixes) {
|
|
79
|
+
const candidate = `${prefix}${input}`;
|
|
80
|
+
if (await fs.pathExists(path.resolve(nodeModulesPath, candidate, 'package.json'))) {
|
|
81
|
+
return { name: input, packageName: candidate };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { name: input, packageName: input };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getPresetPackageJsonCandidates(options = {}) {
|
|
90
|
+
const candidates = [];
|
|
91
|
+
const nodeModulesPath = String(options.nodeModulesPath || process.env.NODE_MODULES_PATH || '').trim();
|
|
92
|
+
|
|
93
|
+
if (nodeModulesPath) {
|
|
94
|
+
candidates.push(path.resolve(nodeModulesPath, PRESET_PACKAGE_NAME, 'package.json'));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
candidates.push(path.resolve(options.cwd || process.cwd(), 'packages/presets/nocobase/package.json'));
|
|
98
|
+
return uniqStrings(candidates);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function getPresetNocoBasePackageJson(options = {}) {
|
|
102
|
+
for (const packageJsonPath of getPresetPackageJsonCandidates(options)) {
|
|
103
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
104
|
+
return fs.readJson(packageJsonPath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function getNodeModulesEntryType(linkPath) {
|
|
111
|
+
try {
|
|
112
|
+
const statResult = await fs.lstat(linkPath);
|
|
113
|
+
if (statResult.isSymbolicLink()) {
|
|
114
|
+
return 'symlink';
|
|
115
|
+
}
|
|
116
|
+
if (statResult.isDirectory()) {
|
|
117
|
+
return 'dir';
|
|
118
|
+
}
|
|
119
|
+
return 'other';
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (error.code === 'ENOENT') {
|
|
122
|
+
return 'missing';
|
|
123
|
+
}
|
|
124
|
+
return 'other';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function resolvePluginPackagePath(packageName, options = {}) {
|
|
129
|
+
const normalizedPackageName = String(packageName || '').trim();
|
|
130
|
+
if (!normalizedPackageName) {
|
|
131
|
+
return '';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const nodeModulesPath = String(options.nodeModulesPath || process.env.NODE_MODULES_PATH || '').trim();
|
|
135
|
+
const storagePluginsPath = options.storagePluginsPath || resolvePluginStoragePath();
|
|
136
|
+
const nodeModulesPackagePath = nodeModulesPath ? path.resolve(nodeModulesPath, normalizedPackageName) : '';
|
|
137
|
+
|
|
138
|
+
if (nodeModulesPackagePath) {
|
|
139
|
+
const entryType = await getNodeModulesEntryType(nodeModulesPackagePath);
|
|
140
|
+
if (entryType === 'dir') {
|
|
141
|
+
return nodeModulesPackagePath;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const sourcePath = await resolvePluginSourcePath(normalizedPackageName, storagePluginsPath);
|
|
146
|
+
if (sourcePath) {
|
|
147
|
+
return sourcePath;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (nodeModulesPackagePath && (await fs.pathExists(path.resolve(nodeModulesPackagePath, 'package.json')))) {
|
|
151
|
+
return nodeModulesPackagePath;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return '';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function readPluginPackagesFromRoot(rootPath) {
|
|
158
|
+
if (!rootPath || !(await fs.pathExists(rootPath))) {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const relativePluginDirs = await getStoragePluginNames(rootPath);
|
|
163
|
+
const items = [];
|
|
164
|
+
|
|
165
|
+
for (const relativePluginDir of relativePluginDirs) {
|
|
166
|
+
const packageJsonPath = path.resolve(rootPath, relativePluginDir, 'package.json');
|
|
167
|
+
if (!(await fs.pathExists(packageJsonPath))) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
171
|
+
const packageName = String(packageJson?.name || '').trim();
|
|
172
|
+
|
|
173
|
+
if (!looksLikePluginPackage(packageName)) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
items.push({
|
|
178
|
+
packageName,
|
|
179
|
+
packagePath: path.resolve(rootPath, relativePluginDir),
|
|
180
|
+
sourceRoot: rootPath,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return items;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function pushOrigin(targetMap, packageName, origin) {
|
|
188
|
+
if (!packageName) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (!targetMap.has(packageName)) {
|
|
192
|
+
targetMap.set(packageName, new Set());
|
|
193
|
+
}
|
|
194
|
+
targetMap.get(packageName).add(origin);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function discoverPluginPackages(options = {}) {
|
|
198
|
+
const nodeModulesPath = String(options.nodeModulesPath || process.env.NODE_MODULES_PATH || '').trim();
|
|
199
|
+
const storagePluginsPath = options.storagePluginsPath || resolvePluginStoragePath();
|
|
200
|
+
const prefixes = options.pluginPackagePrefixes || getPluginPackagePrefixes();
|
|
201
|
+
const originsByPackageName = new Map();
|
|
202
|
+
|
|
203
|
+
const presetPackageJson = await getPresetNocoBasePackageJson({ nodeModulesPath, cwd: options.cwd });
|
|
204
|
+
const presetDependencies = Object.keys(presetPackageJson?.dependencies || {}).filter((packageName) =>
|
|
205
|
+
packageName.startsWith('@nocobase/plugin-'),
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
for (const packageName of presetDependencies) {
|
|
209
|
+
pushOrigin(originsByPackageName, packageName, 'preset-dependency');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const sourceRoots = getPluginSourceRoots(storagePluginsPath);
|
|
213
|
+
for (const sourceRoot of sourceRoots) {
|
|
214
|
+
const packages = await readPluginPackagesFromRoot(sourceRoot);
|
|
215
|
+
for (const item of packages) {
|
|
216
|
+
pushOrigin(originsByPackageName, item.packageName, sourceRoot);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
for (const packageNameOrName of splitPluginNames(process.env.APPEND_PRESET_BUILT_IN_PLUGINS)) {
|
|
221
|
+
const { packageName } = await parsePluginName(packageNameOrName, {
|
|
222
|
+
nodeModulesPath,
|
|
223
|
+
pluginPackagePrefixes: prefixes,
|
|
224
|
+
});
|
|
225
|
+
pushOrigin(originsByPackageName, packageName, 'append-built-in');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const packageNameOrName of splitPluginNames(process.env.APPEND_PRESET_LOCAL_PLUGINS)) {
|
|
229
|
+
const { packageName } = await parsePluginName(packageNameOrName, {
|
|
230
|
+
nodeModulesPath,
|
|
231
|
+
pluginPackagePrefixes: prefixes,
|
|
232
|
+
});
|
|
233
|
+
pushOrigin(originsByPackageName, packageName, 'append-local');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const items = [];
|
|
237
|
+
for (const [packageName, origins] of originsByPackageName.entries()) {
|
|
238
|
+
const resolvedPath = await resolvePluginPackagePath(packageName, {
|
|
239
|
+
nodeModulesPath,
|
|
240
|
+
storagePluginsPath,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
if (!resolvedPath) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const { name } = await parsePluginName(packageName, { nodeModulesPath, pluginPackagePrefixes: prefixes });
|
|
248
|
+
items.push({
|
|
249
|
+
name,
|
|
250
|
+
packageName,
|
|
251
|
+
origins: [...origins],
|
|
252
|
+
resolvedPath,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return items.sort((a, b) => a.packageName.localeCompare(b.packageName));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
exports.DEFAULT_PLUGIN_PACKAGE_PREFIXES = DEFAULT_PLUGIN_PACKAGE_PREFIXES;
|
|
260
|
+
exports.getPluginPackagePrefixes = getPluginPackagePrefixes;
|
|
261
|
+
exports.getPluginNameFromPackageName = getPluginNameFromPackageName;
|
|
262
|
+
exports.getPresetNocoBasePackageJson = getPresetNocoBasePackageJson;
|
|
263
|
+
exports.isValidPackageName = isValidPackageName;
|
|
264
|
+
exports.looksLikePluginPackage = looksLikePluginPackage;
|
|
265
|
+
exports.parsePluginName = parsePluginName;
|
|
266
|
+
exports.resolvePluginPackagePath = resolvePluginPackagePath;
|
|
267
|
+
exports.discoverPluginPackages = discoverPluginPackages;
|
|
268
|
+
exports.splitPluginNames = splitPluginNames;
|
package/plugin-symlink.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
export declare function resolvePluginStoragePath(): string;
|
|
1
2
|
export declare function getStoragePluginNames(target: any): Promise<any[]>;
|
|
2
|
-
export declare function
|
|
3
|
+
export declare function getPluginSourceRoots(storagePluginsPath: string): string[];
|
|
4
|
+
export declare function resolvePluginSourcePath(pluginName: string, storagePluginsPath: string): Promise<string>;
|
|
3
5
|
export declare function createStoragePluginSymLink(pluginName: any): Promise<void>;
|
|
4
6
|
export declare function createStoragePluginsSymlink(): Promise<void>;
|
|
5
7
|
export declare function createDevPluginSymLink(pluginName: any): Promise<void>;
|
|
6
8
|
export declare function createDevPluginsSymlink(): Promise<void>;
|
|
9
|
+
export declare function syncPluginSymlinks(): Promise<void>;
|
package/plugin-symlink.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
const
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { resolve } = path;
|
|
2
3
|
const fs = require('fs-extra');
|
|
3
4
|
|
|
5
|
+
function resolvePluginStoragePath() {
|
|
6
|
+
if (process.env.PLUGIN_STORAGE_PATH) {
|
|
7
|
+
const p = process.env.PLUGIN_STORAGE_PATH;
|
|
8
|
+
return path.isAbsolute(p) ? p : path.resolve(process.cwd(), p);
|
|
9
|
+
}
|
|
10
|
+
return path.join(process.env.STORAGE_PATH || path.resolve(process.cwd(), 'storage'), 'plugins');
|
|
11
|
+
}
|
|
12
|
+
|
|
4
13
|
/**
|
|
5
14
|
* Recursively get plugin names from a directory
|
|
6
15
|
* @param {string} target - Target directory to scan
|
|
@@ -28,6 +37,36 @@ async function getStoragePluginNames(target) {
|
|
|
28
37
|
return plugins;
|
|
29
38
|
}
|
|
30
39
|
|
|
40
|
+
async function getPluginNamesFromSourceRoot(rootPath) {
|
|
41
|
+
if (!(await fs.pathExists(rootPath))) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
return await getStoragePluginNames(rootPath);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function isValidPluginSourcePath(candidate) {
|
|
48
|
+
return await fs.pathExists(resolve(candidate, 'package.json'));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getPluginSourceRoots(storagePluginsPath) {
|
|
52
|
+
return [
|
|
53
|
+
resolve(process.cwd(), 'packages/plugins'),
|
|
54
|
+
resolve(process.cwd(), 'packages/pro-plugins'),
|
|
55
|
+
storagePluginsPath,
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function resolvePluginSourcePath(pluginName, storagePluginsPath) {
|
|
60
|
+
const sourceRoots = getPluginSourceRoots(storagePluginsPath);
|
|
61
|
+
for (const rootPath of sourceRoots) {
|
|
62
|
+
const candidate = resolve(rootPath, pluginName);
|
|
63
|
+
if (await isValidPluginSourcePath(candidate)) {
|
|
64
|
+
return candidate;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
|
|
31
70
|
/**
|
|
32
71
|
* Ensure the organization directory exists for scoped packages
|
|
33
72
|
* @param {string} nodeModulesPath - Path to node_modules
|
|
@@ -43,22 +82,35 @@ async function ensureOrgDirectory(nodeModulesPath, pluginName) {
|
|
|
43
82
|
}
|
|
44
83
|
|
|
45
84
|
/**
|
|
46
|
-
* Check
|
|
47
|
-
* @param {string} linkPath - Path
|
|
48
|
-
* @
|
|
49
|
-
* @returns {Promise<boolean>} True if symlink exists and points to target
|
|
85
|
+
* Check whether node_modules entry is a real directory, symlink, or missing
|
|
86
|
+
* @param {string} linkPath - Path inside node_modules
|
|
87
|
+
* @returns {Promise<'missing'|'dir'|'symlink'|'other'>}
|
|
50
88
|
*/
|
|
51
|
-
async function
|
|
89
|
+
async function getNodeModulesEntryType(linkPath) {
|
|
52
90
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return
|
|
91
|
+
const statResult = await fs.lstat(linkPath);
|
|
92
|
+
if (statResult.isSymbolicLink()) {
|
|
93
|
+
return 'symlink';
|
|
94
|
+
}
|
|
95
|
+
if (statResult.isDirectory()) {
|
|
96
|
+
return 'dir';
|
|
56
97
|
}
|
|
98
|
+
return 'other';
|
|
57
99
|
} catch (error) {
|
|
58
|
-
|
|
100
|
+
if (error.code === 'ENOENT') {
|
|
101
|
+
return 'missing';
|
|
102
|
+
}
|
|
103
|
+
return 'other';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function isSymlinkValid(linkPath, targetPath) {
|
|
108
|
+
try {
|
|
109
|
+
const realPath = await fs.realpath(linkPath);
|
|
110
|
+
return realPath === targetPath;
|
|
111
|
+
} catch {
|
|
59
112
|
return false;
|
|
60
113
|
}
|
|
61
|
-
return false;
|
|
62
114
|
}
|
|
63
115
|
|
|
64
116
|
/**
|
|
@@ -110,13 +162,18 @@ async function createPluginSymLink(pluginName, sourcePath, nodeModulesPath, plug
|
|
|
110
162
|
return;
|
|
111
163
|
}
|
|
112
164
|
|
|
165
|
+
const linkPath = resolve(nodeModulesPath, pluginName);
|
|
166
|
+
const entryType = await getNodeModulesEntryType(linkPath);
|
|
167
|
+
|
|
168
|
+
if (entryType === 'dir') {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
113
172
|
// Ensure organization directory exists for scoped packages
|
|
114
173
|
await ensureOrgDirectory(nodeModulesPath, pluginName);
|
|
115
174
|
|
|
116
|
-
const linkPath = resolve(nodeModulesPath, pluginName);
|
|
117
|
-
|
|
118
175
|
// Check if symlink already points to the correct target
|
|
119
|
-
if (await isSymlinkValid(linkPath, targetPath)) {
|
|
176
|
+
if (entryType === 'symlink' && (await isSymlinkValid(linkPath, targetPath))) {
|
|
120
177
|
return; // Symlink is already correct, no need to recreate
|
|
121
178
|
}
|
|
122
179
|
|
|
@@ -133,10 +190,9 @@ async function createPluginSymLink(pluginName, sourcePath, nodeModulesPath, plug
|
|
|
133
190
|
/**
|
|
134
191
|
* Create a symlink for a storage plugin
|
|
135
192
|
* @param {string} pluginName - Name of the plugin
|
|
136
|
-
* @returns {Promise<void>}
|
|
137
193
|
*/
|
|
138
194
|
async function createStoragePluginSymLink(pluginName) {
|
|
139
|
-
const storagePluginsPath =
|
|
195
|
+
const storagePluginsPath = resolvePluginStoragePath();
|
|
140
196
|
const nodeModulesPath = process.env.NODE_MODULES_PATH;
|
|
141
197
|
await createPluginSymLink(pluginName, storagePluginsPath, nodeModulesPath, 'storage');
|
|
142
198
|
}
|
|
@@ -146,7 +202,7 @@ async function createStoragePluginSymLink(pluginName) {
|
|
|
146
202
|
* @returns {Promise<void>}
|
|
147
203
|
*/
|
|
148
204
|
async function createStoragePluginsSymlink() {
|
|
149
|
-
const storagePluginsPath =
|
|
205
|
+
const storagePluginsPath = resolvePluginStoragePath();
|
|
150
206
|
if (!(await fs.pathExists(storagePluginsPath))) {
|
|
151
207
|
return;
|
|
152
208
|
}
|
|
@@ -178,7 +234,59 @@ async function createDevPluginsSymlink() {
|
|
|
178
234
|
await Promise.all(pluginNames.map((pluginName) => createDevPluginSymLink(pluginName)));
|
|
179
235
|
}
|
|
180
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Create symlinks for all plugins from all sources
|
|
239
|
+
* @returns {Promise<void>}
|
|
240
|
+
*/
|
|
241
|
+
async function syncPluginSymlinks() {
|
|
242
|
+
const nodeModulesPath = process.env.NODE_MODULES_PATH;
|
|
243
|
+
if (!nodeModulesPath) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const storagePluginsPath = resolvePluginStoragePath();
|
|
248
|
+
const sourceRoots = getPluginSourceRoots(storagePluginsPath);
|
|
249
|
+
const pluginNames = new Set();
|
|
250
|
+
|
|
251
|
+
for (const rootPath of sourceRoots) {
|
|
252
|
+
const names = await getPluginNamesFromSourceRoot(rootPath);
|
|
253
|
+
names.forEach((name) => pluginNames.add(name));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
await Promise.all(
|
|
257
|
+
[...pluginNames].map(async (pluginName) => {
|
|
258
|
+
const linkPath = resolve(nodeModulesPath, pluginName);
|
|
259
|
+
const entryType = await getNodeModulesEntryType(linkPath);
|
|
260
|
+
|
|
261
|
+
if (entryType === 'dir') {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const sourcePath = await resolvePluginSourcePath(pluginName, storagePluginsPath);
|
|
266
|
+
if (!sourcePath) {
|
|
267
|
+
if (entryType === 'symlink' || entryType === 'other') {
|
|
268
|
+
await removeExistingLink(linkPath, pluginName, 'plugin');
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (entryType === 'symlink' && (await isSymlinkValid(linkPath, sourcePath))) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
await ensureOrgDirectory(nodeModulesPath, pluginName);
|
|
278
|
+
await removeExistingLink(linkPath, pluginName, 'plugin');
|
|
279
|
+
await fs.symlink(sourcePath, linkPath, 'dir');
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
exports.resolvePluginStoragePath = resolvePluginStoragePath;
|
|
285
|
+
exports.getStoragePluginNames = getStoragePluginNames;
|
|
286
|
+
exports.getPluginSourceRoots = getPluginSourceRoots;
|
|
287
|
+
exports.resolvePluginSourcePath = resolvePluginSourcePath;
|
|
181
288
|
exports.createStoragePluginSymLink = createStoragePluginSymLink;
|
|
182
289
|
exports.createStoragePluginsSymlink = createStoragePluginsSymlink;
|
|
183
290
|
exports.createDevPluginSymLink = createDevPluginSymLink;
|
|
184
291
|
exports.createDevPluginsSymlink = createDevPluginsSymlink;
|
|
292
|
+
exports.syncPluginSymlinks = syncPluginSymlinks;
|