@saltcorn/plugins-loader 1.6.0-alpha.8 → 1.6.0-beta.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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/plugin_installer.js +58 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/plugins-loader",
3
- "version": "1.6.0-alpha.8",
3
+ "version": "1.6.0-beta.1",
4
4
  "description": "Saltcorn plugin loader",
5
5
  "homepage": "https://saltcorn.com",
6
6
  "scripts": {
@@ -9,7 +9,7 @@
9
9
  "clean": "echo 'No TypeScript build'"
10
10
  },
11
11
  "dependencies": {
12
- "@saltcorn/data": "1.6.0-alpha.8",
12
+ "@saltcorn/data": "1.6.0-beta.1",
13
13
  "env-paths": "^2.2.1",
14
14
  "npm-registry-fetch": "17.1.0",
15
15
  "https-proxy-agent": "^7.0.6"
@@ -1,5 +1,6 @@
1
1
  const { join, normalize, dirname } = require("path");
2
2
  const { writeFile, mkdir, pathExists, copy, symlink } = require("fs-extra");
3
+ const { readdirSync } = require("fs");
3
4
  const { spawn } = require("child_process");
4
5
  const {
5
6
  downloadFromNpm,
@@ -55,6 +56,30 @@ const npmInstallNeeded = (oldPckJSON, newPckJSON) => {
55
56
 
56
57
  const defaultRootFolder = envPaths("saltcorn", { suffix: "plugins" }).data;
57
58
 
59
+ // tracks local plugins already copied in this process
60
+ // is only checked in the master process
61
+ const installedLocalPlugins = new Set();
62
+
63
+ /**
64
+ * Find the most recently created localversion_<timestamp> directory for a local plugin.
65
+ * Falls back to "localversion" if none exist yet.
66
+ */
67
+ const findLatestLocalversionDir = (parentDir) => {
68
+ try {
69
+ const dirs = readdirSync(parentDir).filter((e) =>
70
+ /^localversion_\d+$/.test(e)
71
+ );
72
+ if (dirs.length === 0) return "localversion";
73
+ return dirs.reduce((latest, d) => {
74
+ return parseInt(d.split("_")[1]) > parseInt(latest.split("_")[1])
75
+ ? d
76
+ : latest;
77
+ });
78
+ } catch {
79
+ return "localversion";
80
+ }
81
+ };
82
+
58
83
  /**
59
84
  * PluginInstaller class
60
85
  */
@@ -62,6 +87,8 @@ class PluginInstaller {
62
87
  constructor(plugin, opts = Object.create(null)) {
63
88
  this.plugin = plugin;
64
89
  this.rootFolder = opts.rootFolder || defaultRootFolder;
90
+ this.reloadModule = !!opts.reloadModule;
91
+ this.force = !!opts.force;
65
92
  this.tempRootFolder =
66
93
  opts.tempRootFolder || envPaths("saltcorn", { suffix: "tmp" }).temp;
67
94
  const tokens =
@@ -69,11 +96,24 @@ class PluginInstaller {
69
96
  ? plugin.location.split("/")
70
97
  : plugin.name.split("/");
71
98
  this.name = tokens[tokens.length - 1];
99
+ const localPluginParentDir = join(
100
+ opts.rootFolder || defaultRootFolder,
101
+ "plugins_folder",
102
+ ...tokens
103
+ );
104
+ const localversionDir =
105
+ opts.reloadModule && plugin.source === "local"
106
+ ? opts.force
107
+ ? `localversion_${Date.now()}`
108
+ : findLatestLocalversionDir(localPluginParentDir)
109
+ : "localversion";
72
110
  this.pluginDir = join(
73
111
  this.rootFolder,
74
112
  plugin.source === "git" ? "git_plugins" : "plugins_folder",
75
113
  ...tokens,
76
- plugin.version || "unknownversion"
114
+ plugin.source === "local"
115
+ ? localversionDir
116
+ : plugin.version || "unknownversion"
77
117
  );
78
118
  this.pckJsonPath = join(this.pluginDir, "package.json");
79
119
  this.tempDir = join(this.tempRootFolder, "temp_install", ...tokens);
@@ -89,11 +129,10 @@ class PluginInstaller {
89
129
 
90
130
  /**
91
131
  *
92
- * @param {boolean} force
93
132
  * @param {boolean} preInstall Only npm install without loading the module
94
133
  * @returns
95
134
  */
96
- async install(force = false, preInstall = false) {
135
+ async install(preInstall = false) {
97
136
  getState().log(5, `loading plugin ${this.plugin.name}`);
98
137
  await this._ensurePluginsRootFolders();
99
138
  if (Plugin.is_fixed_plugin(this.plugin.location))
@@ -104,13 +143,13 @@ class PluginInstaller {
104
143
  const msgs = [];
105
144
  let module = null;
106
145
  let loadedWithReload = false;
107
- let pckJSON = await this._installHelper(force, msgs);
146
+ let pckJSON = await this._installHelper(msgs);
108
147
  if (!preInstall) {
109
148
  try {
110
149
  module = await this._loadMainFile(pckJSON);
111
150
  } catch (e) {
112
151
  if (e.code === "MODULE_NOT_FOUND") await this._dumpNodeMoules();
113
- if (force) {
152
+ if (this.force) {
114
153
  // remove and try again
115
154
  // could happen when there is a directory with a package.json
116
155
  // but without a valid node modules folder
@@ -119,7 +158,7 @@ class PluginInstaller {
119
158
  `Error loading plugin ${this.plugin.name}. Removing and trying again.`
120
159
  );
121
160
  await this.remove();
122
- pckJSON = await this._installHelper(force, msgs);
161
+ pckJSON = await this._installHelper(msgs);
123
162
  } else {
124
163
  getState().log(
125
164
  2,
@@ -154,15 +193,13 @@ class PluginInstaller {
154
193
 
155
194
  /**
156
195
  * prepare the plugin folder and npm install if needed
157
- * @param {*} force
158
- * @param {*} pckJSON
159
196
  * @param {*} msgs
160
197
  */
161
- async _installHelper(force, msgs) {
198
+ async _installHelper(msgs) {
162
199
  const airgap = getState().getConfig("airgap", false);
163
200
  let pckJSON = await readPackageJson(this.pckJsonPath);
164
201
  if (airgap) return pckJSON;
165
- else if (await this._prepPluginsFolder(force, pckJSON)) {
202
+ else if (await this._prepPluginsFolder(pckJSON)) {
166
203
  const tmpPckJSON = await this._removeDependencies(
167
204
  await readPackageJson(this.tempPckJsonPath),
168
205
  true
@@ -206,17 +243,16 @@ class PluginInstaller {
206
243
 
207
244
  /**
208
245
  * helper to prepare the plugin folder
209
- * @param {*} force
210
246
  * @param {*} pckJSON
211
247
  * @returns
212
248
  */
213
- async _prepPluginsFolder(force, pckJSON) {
249
+ async _prepPluginsFolder(pckJSON) {
214
250
  let wasLoaded = false;
215
251
  const folderExists = await pathExists(this.pluginDir);
216
252
  switch (this.plugin.source) {
217
253
  case "npm":
218
254
  if (
219
- (force && !(await this._versionIsInstalled(pckJSON))) ||
255
+ (this.force && !(await this._versionIsInstalled(pckJSON))) ||
220
256
  !folderExists
221
257
  ) {
222
258
  getState().log(6, "downloading from npm");
@@ -229,24 +265,28 @@ class PluginInstaller {
229
265
  }
230
266
  break;
231
267
  case "github":
232
- if (force || !folderExists) {
268
+ if (this.force || !folderExists) {
233
269
  getState().log(6, "downloading from github");
234
270
  await downloadFromGithub(this.plugin, this.rootFolder, this.tempDir);
235
271
  wasLoaded = true;
236
272
  }
237
273
  break;
238
274
  case "local":
239
- if (force || !folderExists) {
275
+ if (
276
+ (this.force || !folderExists) &&
277
+ (!installedLocalPlugins.has(this.pluginDir) || this.reloadModule)
278
+ ) {
240
279
  getState().log(6, "copying from local");
241
280
  await copy(this.plugin.location, this.tempDir);
242
281
  // if tempdir has a node_modules folder, remove it
243
282
  if (await pathExists(join(this.tempDir, "node_modules")))
244
283
  await rm(join(this.tempDir, "node_modules"), { recursive: true });
284
+ installedLocalPlugins.add(this.pluginDir);
245
285
  wasLoaded = true;
246
286
  }
247
287
  break;
248
288
  case "git":
249
- if (force || !folderExists) {
289
+ if (this.force || !folderExists) {
250
290
  getState().log(6, "downloading from git");
251
291
  await gitPullOrClone(this.plugin, this.tempDir);
252
292
  this.pckJsonPath = join(this.pluginDir, "package.json");
@@ -294,8 +334,8 @@ class PluginInstaller {
294
334
  return require(normalize(join(this.pluginDir, pckJSON.main)));
295
335
  } else {
296
336
  const url = `${isWindows ? `file://` : ""}${normalize(
297
- join(this.pluginDir, pckJSON.main + (reload ? "?reload=true" : ""))
298
- )}`;
337
+ join(this.pluginDir, pckJSON.main)
338
+ )}${reload ? `?reload=${Date.now()}` : ""}`;
299
339
  const res = await import(url);
300
340
  return res.default;
301
341
  }