@staffbase/create-staffbase-plugin 1.0.6 → 1.0.14

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.
@@ -1,9 +1 @@
1
- # Shared
2
- .github/CODEOWNERS @Staffbase/cc-tech
3
-
4
- # Global
5
- * @maximizeIT
6
-
7
- # Misc
8
- package.json @maximizeIT @Staffbot
9
- yarn.lock @maximizeIT @Staffbot
1
+ * @Staffbase/cs-tech
@@ -1,9 +1,4 @@
1
1
  version: 2
2
- registries:
3
- npm-github:
4
- type: npm-registry
5
- url: https://npm.pkg.github.com
6
- token: ${{secrets.STAFFBOT_NPM_READ}}
7
2
  updates:
8
3
  - package-ecosystem: "github-actions"
9
4
  directory: "/"
@@ -11,7 +6,7 @@ updates:
11
6
  interval: "daily"
12
7
  time: "08:00"
13
8
  timezone: "Europe/Berlin"
14
- target-branch: "master"
9
+ target-branch: "main"
15
10
  open-pull-requests-limit: 10
16
11
  labels:
17
12
  - "github dependency"
@@ -33,14 +28,12 @@ updates:
33
28
  interval: "daily"
34
29
  time: "08:00"
35
30
  timezone: "Europe/Berlin"
36
- target-branch: "master"
31
+ target-branch: "main"
37
32
  open-pull-requests-limit: 10
38
33
  labels:
39
34
  - "npm dependency"
40
35
  reviewers:
41
36
  - "maximizeIT"
42
- registries:
43
- - npm-github
44
37
  commit-message:
45
38
  prefix: chore(deps)
46
39
  groups:
@@ -4,7 +4,7 @@ on: pull_request
4
4
 
5
5
  jobs:
6
6
  dependabot:
7
- uses: Staffbase/gha-workflows/.github/workflows/template_automerge_dependabot.yml@v4.0.2
7
+ uses: Staffbase/gha-workflows/.github/workflows/template_automerge_dependabot.yml@v9.1.0
8
8
  secrets:
9
9
  app_id: ${{ vars.STAFFBASE_ACTIONS_APP_ID }}
10
10
  private_key: ${{ secrets.STAFFBASE_ACTIONS_PRIVATE_KEY }}
@@ -0,0 +1,22 @@
1
+ name: Publish to NPM Registry
2
+ on:
3
+ release:
4
+ types: [created]
5
+
6
+ permissions:
7
+ contents: read
8
+ id-token: write
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - name: Checkout
15
+ uses: actions/checkout@v6
16
+ - name: Setup Node
17
+ uses: actions/setup-node@v6
18
+ with:
19
+ node-version: '24'
20
+ registry-url: 'https://registry.npmjs.org'
21
+ - name: Publish package on NPM 📦
22
+ run: npm publish --provenance --access public
@@ -1,5 +1,6 @@
1
1
  # Create-Staffbase-SSO-CLI Documentation
2
- ![Staffbase Logo](https://staffbase.com/wp-content/themes/staffbase-theme/img/logo-blau.svg)
2
+ <img src="docs/assets/images/staffbase.png" alt="Staffbase SE" width="96" />
3
+
3
4
  ## Getting started
4
5
  Install the package in your global namespace and then run the command.
5
6
 
@@ -15,6 +16,7 @@ Example:
15
16
  ```bash
16
17
  $ yarn create @staffbase/staffbase-plugin /home/user1/work/staffbase-server/ --name staffbase-sso-server
17
18
  ```
19
+
18
20
  ## Interactive Mode
19
21
  You can also run the command without passing any arguments to get into interactive
20
22
  mode which would ask you the name of your app and the path where it need to be installed.
@@ -74,19 +76,19 @@ $ npm start
74
76
 
75
77
  ## License
76
78
 
77
- Copyright 2017-2024 Staffbase GmbH.
79
+ Copyright 2017-2025 Staffbase SE.
78
80
 
79
81
  Licensed under the Apache License, Version 2.0: https://www.apache.org/licenses/LICENSE-2.0
80
82
 
81
83
  <table>
82
84
  <tr>
83
85
  <td>
84
- <img src="docs/assets/images/staffbase.png" alt="Staffbase GmbH" width="96" />
86
+ <img src="docs/assets/images/staffbase.png" alt="Staffbase SE" width="96" />
85
87
  </td>
86
88
  <td>
87
- <b>Staffbase GmbH</b>
88
- <br />Staffbase is an internal communications platform built to revolutionize the way you work and unite your company. Staffbase is hiring: <a href="https://jobs.staffbase.com" target="_blank" rel="noreferrer">jobs.staffbase.com</a>
89
- <br /><a href="https://github.com/Staffbase" target="_blank" rel="noreferrer">GitHub</a> | <a href="https://staffbase.com/" target="_blank" rel="noreferrer">Website</a> | <a href="https://jobs.staffbase.com" target="_blank" rel="noreferrer">Jobs</a>
89
+ <b>Staffbase SE</b>
90
+ <br />Staffbase is an internal communications platform built to revolutionize the way you work and unite your company. Staffbase is hiring: <a href="https://staffbase.com/jobs/" target="_blank" rel="noreferrer">https://staffbase.com/jobs</a>
91
+ <br /><a href="https://github.com/Staffbase" target="_blank" rel="noreferrer">GitHub</a> | <a href="https://staffbase.com/" target="_blank" rel="noreferrer">Website</a> | <a href="https://staffbase.com/jobs/" target="_blank" rel="noreferrer">Jobs</a>
90
92
  </td>
91
93
  </tr>
92
94
  </table>
@@ -0,0 +1,14 @@
1
+ apiVersion: backstage.io/v1alpha1
2
+ kind: Component
3
+ metadata:
4
+ name: create-staffbase-plugin-nodejs
5
+ description: Node.js plugin skeleton generator provided by Staffbase
6
+ annotations:
7
+ github.com/project-slug: Staffbase/create-staffbase-plugin-nodejs
8
+ jfrog-artifactory/image-name: create-staffbase-plugin-nodejs
9
+ tags:
10
+ - js
11
+ - nodejs
12
+ spec:
13
+ type: service
14
+ lifecycle: production
package/csss.js CHANGED
@@ -1,39 +1,40 @@
1
1
  #! /usr/bin/env node
2
- 'use strict';
3
- const fs = require('fs-extra');
4
- const path = require('path');
5
- const yargv = require('yargs');
6
- const validateNPM = require('validate-npm-package-name');
7
- const execFile = require('child_process').execFile;
8
- const prompt = require('prompt');
9
- const filepath = require('filepath');
10
- const validatePath = require('./lib/helpers').validatePath;
11
- const isRelativePath = require('is-relative');
12
- const colors = require('colors/safe');
2
+ "use strict";
3
+ const fs = require("fs-extra");
4
+ const path = require("path");
5
+ const yargs = require("yargs");
6
+ const validateNPM = require("validate-npm-package-name");
7
+ const execFile = require("child_process").execFile;
8
+ const prompt = require("prompt");
9
+ const filepath = require("filepath");
10
+ const validatePath = require("./lib/helpers").validatePath;
11
+ const isRelativePath = require("is-relative");
12
+ const colors = require("colors/safe");
13
13
 
14
14
  // configure prompt
15
- prompt.message = '';
16
- prompt.delimiter = colors.green(':');
15
+ prompt.message = "";
16
+ prompt.delimiter = colors.green(":");
17
17
  // set default values
18
- const defaultNPMName = 'my-staffbase-backend';
19
- const scaffoldFolder = path.resolve(__dirname, './scaffoldTpl');
18
+ const defaultNPMName = "my-staffbase-backend";
19
+ const scaffoldFolder = path.resolve(__dirname, "./scaffoldTpl");
20
20
 
21
- yargv
22
- .usage('Usage: create-staffbase-sso-server <project-directory> [Options]')
23
- .alias('name', 'N')
24
- .string('name')
25
- .describe('name', 'a sepcific package.json name of your app')
26
- .version('0.0.1')
27
- .help('help')
28
- .epilogue(`for more information,\please see the README at:
29
- https://github.com/Staffbase/create-staffbase-plugin-nodejs/blob/master/README.MD`);
30
- // console.log('YARGS Parsed Data:\n', yargv.argv);
31
- const packageJSON = fs.readJSONSync(path.join(scaffoldFolder, 'package.json'));
21
+ const yargv = yargs(process.argv.slice(2))
22
+ .usage("Usage: create-staffbase-sso-server <project-directory> [Options]")
23
+ .alias("name", "N")
24
+ .string("name")
25
+ .describe("name", "a specific package.json name of your app")
26
+ .version("0.0.1")
27
+ .help("help")
28
+ .epilogue(`for more information,\please see the README at:
29
+ https://github.com/Staffbase/create-staffbase-plugin-nodejs/blob/main/README.MD`)
30
+ .argv;
31
+ // console.log('YARGS Parsed Data:\n', yargv);
32
+ const packageJSON = fs.readJSONSync(path.join(scaffoldFolder, "package.json"));
32
33
  // Defaults package name to current folder name
33
- const nameParam = yargv.argv.N || yargv.argv.name || defaultNPMName;
34
+ const nameParam = yargv.N || yargv.name || defaultNPMName;
34
35
  prompt.override = {
35
- name: yargv.argv.N,
36
- path: yargv.argv._[0],
36
+ name: yargv.N,
37
+ path: yargv._[0],
37
38
  };
38
39
  /**
39
40
  * Prompts user for just the npm name value.
@@ -44,25 +45,24 @@ function promptName(name) {
44
45
  const namePromptSchema = {
45
46
  properties: {
46
47
  name: {
47
- description: 'What is the npm name of your plugin?',
48
- type: 'string',
48
+ description: "What is the npm name of your plugin?",
49
+ type: "string",
49
50
  default: name,
50
- message: 'Name must be npm.js compatible',
51
+ message: "Name must be npm.js compatible",
51
52
  required: true,
52
- conform: function(value) {
53
+ conform: function (value) {
53
54
  return validateNPM(value).validForNewPackages;
54
55
  },
55
56
  },
56
57
  },
57
58
  };
58
- return new Promise(function(resolve, reject) {
59
- prompt.start()
60
- .get(namePromptSchema, function(err, res) {
61
- if (err) {
62
- return reject(err);
63
- }
64
- return resolve(res);
65
- });
59
+ return new Promise(function (resolve, reject) {
60
+ prompt.start().get(namePromptSchema, function (err, res) {
61
+ if (err) {
62
+ return reject(err);
63
+ }
64
+ return resolve(res);
65
+ });
66
66
  });
67
67
  }
68
68
  /**
@@ -75,33 +75,34 @@ function promptPath(promtedName) {
75
75
  const pathPromptSchema = {
76
76
  properties: {
77
77
  path: {
78
- description: 'Please enter the folder path for the App',
79
- type: 'string',
78
+ description: "Please enter the folder path for the new plugin project",
79
+ type: "string",
80
80
  message:
81
- 'Entered path is invalid or an already present file on the File System. Please enter a correct filepath,',
81
+ "Entered path is invalid or an already present file on the File System. Please enter a correct filepath.",
82
82
  default: defPath,
83
83
  required: true,
84
84
  conform: validatePath,
85
85
  },
86
86
  override: {
87
87
  message:
88
- colors.yellow('The directory you specified already exists. It directory will be overridden!') +
89
- '\nDo you wish to proceed (y)es|(n)o?',
88
+ colors.yellow(
89
+ "The directory you specified already exists. The directory will be overridden!"
90
+ ) + "\nDo you wish to proceed (y)es|(n)o?",
90
91
  validator: /y[es]*|n[o]?/,
91
- warning: 'Must respond yes or no',
92
- default: 'yes',
93
- ask: function() {
92
+ warning: "Must respond yes or no",
93
+ default: "yes",
94
+ ask: function () {
94
95
  let chkPath = defPath;
95
- if (prompt.history('path')) {
96
- chkPath = prompt.history('path').value;
96
+ if (prompt.history("path")) {
97
+ chkPath = prompt.history("path").value;
97
98
  }
98
99
  return filepath.create(chkPath).exists();
99
100
  },
100
101
  },
101
102
  },
102
103
  };
103
- return new Promise(function(resolve, reject) {
104
- prompt.get(pathPromptSchema, function(err, res) {
104
+ return new Promise(function (resolve, reject) {
105
+ prompt.get(pathPromptSchema, function (err, res) {
105
106
  if (err) {
106
107
  return reject(err);
107
108
  }
@@ -111,18 +112,18 @@ function promptPath(promtedName) {
111
112
  }
112
113
 
113
114
  /**
114
- * Copy contants from the Scaffold Template to the specified folder
115
- * @param {String} dstDir THe destination directory where files are to be copied
115
+ * Copy contents from the Scaffold Template to the specified folder
116
+ * @param {String} dstDir The destination directory where files are to be copied
116
117
  * @return {Promise} Promise resolved when the copy process is complete. Rejected
117
118
  * if there is some error in copying files.
118
119
  */
119
120
  function copyContents(dstDir) {
120
121
  // console.log(colors.blue('Copying from:' + path.resolve(__dirname, './scaffoldTpl')));
121
- const scaffoldFolder = path.resolve(__dirname, './scaffoldTpl');
122
+ const scaffoldFolder = path.resolve(__dirname, "./scaffoldTpl");
122
123
  return fs.copy(scaffoldFolder, dstDir);
123
124
  }
124
125
  /**
125
- * Repace the package.json file from copied fromplate to the new generated one.
126
+ * Replace the package.json file from copied template to the new generated one.
126
127
  * @return {Promise} Promise resolved when the Package.json is successfully replaced.
127
128
  * @param {String} dstPath The path of the folder where the package.json needs to be replaced
128
129
  * @param {String} nameVal name value that should be replaced
@@ -130,14 +131,13 @@ function copyContents(dstDir) {
130
131
  */
131
132
  function replacePackageJSON(dstPath, nameVal) {
132
133
  // console.log("replacePackageJSON");
133
- const newPackageJSON = Object.assign({}, packageJSON, {name: nameVal});
134
+ const newPackageJSON = Object.assign({}, packageJSON, { name: nameVal });
134
135
  const curDir = path.resolve(dstPath);
135
- const packagePath = path.resolve(path.join(curDir, 'package.json'));
136
- return fs.remove(packagePath)
137
- .then(function(data) {
138
- // console.log(colors.yellow('Writing json...'));
139
- return fs.writeJson(packagePath, newPackageJSON, {spaces: 2});
140
- });
136
+ const packagePath = path.resolve(path.join(curDir, "package.json"));
137
+ return fs.remove(packagePath).then(function (data) {
138
+ // console.log(colors.yellow('Writing json...'));
139
+ return fs.writeJson(packagePath, newPackageJSON, { spaces: 2 });
140
+ });
141
141
  }
142
142
  /**
143
143
  * Installs the node modules in the folder where the template was created.
@@ -146,12 +146,12 @@ function replacePackageJSON(dstPath, nameVal) {
146
146
  * @return {Promise} Promise resolved when the packages are successfulyl installed.
147
147
  */
148
148
  function installDeps(dstPath) {
149
- console.log(colors.italic('\nInstalling dependencies...'));
149
+ console.log(colors.italic("\nInstalling dependencies..."));
150
150
  const opts = {
151
151
  cwd: path.resolve(dstPath),
152
152
  };
153
- return new Promise( (resolve, reject) => {
154
- execFile('npm', ['install'], opts, (err, stdout, stderr) => {
153
+ return new Promise((resolve, reject) => {
154
+ execFile("npm", ["install"], opts, (err, stdout, stderr) => {
155
155
  // console.log(colors.red('Inside Child result', stderr, stdout, err));
156
156
  if (err) {
157
157
  // console.log(colors.red(err));
@@ -170,11 +170,10 @@ function installDeps(dstPath) {
170
170
  function removeExistingFolder(dstPath) {
171
171
  const fp = filepath.create(dstPath);
172
172
  if (fp.exists()) {
173
- return fs.remove(fp.toString())
174
- .then(function() {
175
- console.log(colors.red('Removing existing folder and its contents...'));
176
- return dstPath;
177
- });
173
+ return fs.remove(fp.toString()).then(function () {
174
+ console.log(colors.red("Removing existing folder and its contents..."));
175
+ return dstPath;
176
+ });
178
177
  } else {
179
178
  return Promise.resolve(dstPath);
180
179
  }
@@ -183,56 +182,58 @@ function removeExistingFolder(dstPath) {
183
182
  const promptRes = {};
184
183
  // promot package name
185
184
  promptName(nameParam)
186
- // prompt file path
187
- .then(function(pathResp) {
188
- const nameRecv = pathResp.name;
189
- Object.assign(promptRes, pathResp);
190
- return promptPath(nameRecv);
191
- })
192
- // remove the folder if it exists
193
- .then(function(pathResp) {
194
- if (pathResp.override === 'n' || pathResp.override === 'no') {
195
- return Promise.reject(console.log(colors.green('Good Bye!')));
196
- }
197
- Object.assign(promptRes, pathResp);
198
- let pathRecv = pathResp.path;
199
- // if the entered path is relative, resolve to absolute
200
- if (isRelativePath(pathRecv)) {
201
- pathRecv = path.resolve(path.join(process.cwd(), pathRecv));
202
- promptRes.path = pathRecv;
203
- }
204
- return removeExistingFolder(pathRecv);
205
- })
206
- // copy contents to folder
207
- .then((pathRecv) => {
208
- return (copyContents(pathRecv));
209
- })
210
- // replace package.json with new one
211
- .then((res) => {
212
- return replacePackageJSON(promptRes.path, promptRes.name);
213
- })
214
- // install npm dependencies
215
- .then((res) => {
216
- return installDeps(promptRes.path);
217
- })
218
- // output end results
219
- .then(function(npmOutput) {
220
- console.log(colors.yellow(npmOutput));
221
- console.log(colors.green(`
185
+ // prompt file path
186
+ .then(function (pathResp) {
187
+ const nameRecv = pathResp.name;
188
+ Object.assign(promptRes, pathResp);
189
+ return promptPath(nameRecv);
190
+ })
191
+ // remove the folder if it exists
192
+ .then(function (pathResp) {
193
+ if (pathResp.override === "n" || pathResp.override === "no") {
194
+ return Promise.reject(console.log(colors.green("Good Bye!")));
195
+ }
196
+ Object.assign(promptRes, pathResp);
197
+ let pathRecv = pathResp.path;
198
+ // if the entered path is relative, resolve to absolute
199
+ if (isRelativePath(pathRecv)) {
200
+ pathRecv = path.resolve(path.join(process.cwd(), pathRecv));
201
+ promptRes.path = pathRecv;
202
+ }
203
+ return removeExistingFolder(pathRecv);
204
+ })
205
+ // copy contents to folder
206
+ .then((pathRecv) => {
207
+ return copyContents(pathRecv);
208
+ })
209
+ // replace package.json with new one
210
+ .then((res) => {
211
+ return replacePackageJSON(promptRes.path, promptRes.name);
212
+ })
213
+ // install npm dependencies
214
+ .then((res) => {
215
+ return installDeps(promptRes.path);
216
+ })
217
+ // output end results
218
+ .then(function (npmOutput) {
219
+ console.log(colors.yellow(npmOutput));
220
+ console.log(
221
+ colors.green(`
222
222
  Your application setup is complete!
223
223
  Please see the generated README.MD file to get more details about next steps.
224
224
  You can find your application template in: ${promptRes.path}.
225
- `));
226
- })
227
- // handle errors if any
228
- .catch(function(err) {
229
- if (err.message === 'canceled') {
230
- return console.log(colors.green('\nGood Bye!'));
231
- }
232
- if (err.message) {
233
- console.log('An error occured.', err);
234
- }
235
- });
225
+ `)
226
+ );
227
+ })
228
+ // handle errors if any
229
+ .catch(function (err) {
230
+ if (err.message === "canceled") {
231
+ return console.log(colors.green("\nGood Bye!"));
232
+ }
233
+ if (err.message) {
234
+ console.log("An error occurred.", err);
235
+ }
236
+ });
236
237
 
237
238
  module.exports = {
238
239
  validatePath: validatePath,
package/csss.test.js CHANGED
@@ -1,13 +1,13 @@
1
- const validatePath = require('./lib/helpers').validatePath;
1
+ const validatePath = require("./lib/helpers").validatePath;
2
2
 
3
- const validPath = './scaffoldTpl';
4
- const inValidPath = '*/,js';
3
+ const validPath = "./scaffoldTpl";
4
+ const inValidPath = "*/,js";
5
5
 
6
- describe('Testing csss.validatePath', () => {
7
- test('test validating existing path', () => {
6
+ describe("Testing csss.validatePath", () => {
7
+ test("test validating existing path", () => {
8
8
  expect(validatePath(validPath)).toBe(true);
9
9
  });
10
- test('test validating invalid path', () => {
10
+ test("test validating invalid path", () => {
11
11
  expect(validatePath(inValidPath)).toBe(false);
12
12
  });
13
13
  });
package/lib/helpers.js CHANGED
@@ -1,5 +1,5 @@
1
- const isValidPath = require('is-valid-path');
2
- const filepath = require('filepath');
1
+ const isValidPath = require("is-valid-path");
2
+ const filepath = require("filepath");
3
3
  /**
4
4
  * Checks if the path specified is valid for setting up the template.
5
5
  * Valid means if the path is a valid path and it is not a File (not a folder).
@@ -13,7 +13,7 @@ function validatePath(path) {
13
13
  try {
14
14
  const fp = filepath.create(path);
15
15
  // console.log(colors.yellow(fp));
16
- return !(fp.isFile());
16
+ return !fp.isFile();
17
17
  } catch (err) {
18
18
  console.log(err);
19
19
  }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@staffbase/create-staffbase-plugin",
3
- "version": "1.0.6",
3
+ "version": "1.0.14",
4
+ "license": "MIT",
4
5
  "bin": {
5
6
  "create-staffbase-plugin": "csss.js"
6
7
  },
@@ -25,27 +26,25 @@
25
26
  "is-relative": "^1.0.0",
26
27
  "is-valid-path": "^0.1.1",
27
28
  "prompt": "^1.0.0",
28
- "validate-npm-package-name": "^3.0.0",
29
- "yargs": "^15.3.1"
29
+ "validate-npm-package-name": "^7.0.1",
30
+ "yargs": "^18.0.0"
30
31
  },
31
32
  "devDependencies": {
32
- "@babel/core": "^7.20.12",
33
- "@babel/eslint-parser": "^7.0.0",
34
- "@emotion/eslint-plugin": "^11.0.0",
35
- "@typescript-eslint/eslint-plugin": "^5.50.0",
36
- "@typescript-eslint/parser": "^5.50.0",
37
- "eslint": "^8.33.0",
38
- "eslint-config-prettier": "^8.6.0",
39
- "eslint-plugin-react": "^7.32.2",
40
- "eslint-plugin-import-helpers": "^1.1.0",
41
- "eslint-plugin-react-hooks": "^4.6.0",
42
- "eslint-plugin-simple-import-sort": "^10.0.0",
33
+ "@babel/core": "^7.28.5",
34
+ "@babel/eslint-parser": "^7.28.5",
35
+ "@typescript-eslint/eslint-plugin": "^8.51.0",
36
+ "@typescript-eslint/parser": "^8.51.0",
37
+ "eslint": "^9.39.2",
43
38
  "eslint-config-google": "^0.14.0",
44
- "highlight.js": "^10.0.3",
45
- "husky": "^4.2.5",
46
- "jest": "^26.0.1",
39
+ "eslint-config-prettier": "^9.1.0",
40
+ "eslint-plugin-react": "^7.37.5",
41
+ "eslint-plugin-react-hooks": "^5.1.0",
42
+ "eslint-plugin-simple-import-sort": "^12.1.1",
43
+ "highlight.js": "^11.11.1",
44
+ "husky": "^9.1.7",
45
+ "jest": "^29.7.0",
47
46
  "markdown-it": "^12.3.2",
48
47
  "marked": "^4.0.10",
49
- "typescript": "^4.9.5"
48
+ "typescript": "^5.9.3"
50
49
  }
51
50
  }
@@ -1,5 +1,3 @@
1
- ![Staffbase Logo](https://staffbase.com/wp-content/themes/staffbase-theme/img/logo-blau.svg)
2
-
3
1
  Welcome to your Staffbase SSO plugin server. Please follow the following instructions
4
2
  to get started with your Plugin Application.
5
3
 
@@ -49,4 +47,4 @@ $ npm start
49
47
  For getting more information about Staffbase SSO, please check out the following links:
50
48
 
51
49
  - [Developer Portal: Custom Plugins](https://developers.staffbase.com/concepts/customplugin-overview/)
52
- - [Staffbase Plugins SDK for Node.js](https://github.com/Staffbase/plugins-sdk-nodejs/blob/master/README.MD)
50
+ - [Staffbase Plugins SDK for Node.js](https://github.com/Staffbase/plugins-sdk-nodejs/blob/main/README.MD)