exb-community-cli 1.0.0 → 1.0.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/README.md CHANGED
@@ -1,3 +1,26 @@
1
1
  # ExB Community CLI
2
2
 
3
3
  A simple, lightweight command line interface to make using Experience Builder Developer Edition easier for new developers, looking to add widgets to their collection.
4
+
5
+ ## Using this CLI
6
+
7
+ The ExB Community CLI has a few tools to make it easy to install, update, remove, and search for Experience Builder widgets that have been posted on NPM. Installing the CLI is as simple as running `npm i exb-community-cli` in your client directory of your Experience Builder application. To then run commands, the format is `npx exb-cli [command]`. If you don't want to prefix every command with npx, you can install the exb-community-cli globally, by running `npm i -g exb-community-cli`. For information on the commands that exist, run `npx exb-cli help`, and commands, arguments, and descriptions will be listed.
8
+
9
+ When using any of the ExB Community CLI commands, the command needs to be run in the client directory of your Experience builder application. If you don't, you will get the error `Error: Run this from the ExB client folder.`.
10
+
11
+ ## Formatting widgets for NPM (And Best Practices)
12
+
13
+ To prepare your widget to be easily ingested by the ExB Community CLI, there are a few key steps to take.
14
+
15
+ 1. Prepare your widget for NPM (Required)
16
+ - The ExB Community CLI has a format tool, which you can use to quickly prepare your widget for publication, if it doesn't already have a package.json file. Before running this command, ensure your manifest.json file has been filled out completely.
17
+ - If you are already using node in your widget (For using external libraries in your widget), ensure that the name in your package.json file is unique on NPM, and that the description is accurate. Also, please add the "exb-widget" keyword to your package.json file, to ensure that your widget can be easily found via the CLI.
18
+ 2. Ensure your widget has a README.md file at the root, with a description of how it functions, how to use it, and who built it. If you have demo applications, add links to those demo applications!
19
+ 3. Use a meaningful icon.svg. It's a small touch, but having a meaningful icon for other builders makes it much easier to build with.
20
+ 4. Add a help document to your widget. Again, another really small touch, but having a help document for users to be able to open up and get a walkthrough of how to use the widget in practice really does help, beyond providing a README.md file. To prevent from reinventing the wheel, if you already have a good README.md file, try converting it to an html file using [this free converter](https://www.netsmarter.com/md-to-html/).
21
+
22
+ ## Automation
23
+
24
+ If you're like me, you probably don't want to re-publish your widget manually on NPM every time that you have an update. That'd be a pain right? Well, as long as you're using GitHub, I have a solution for you. _**GitHub Actions**_. You can use GitHub Actions to automate deployment to NPM using a fairly simple process, outlined in this [Esri Community Post](TBD).
25
+
26
+ If you are using automated deployment YAML for your Experience Builder installs, you might also want to make sure that the widgets you're using come along for the ride. This CLI has that in mind! To inline install a widget into your build pipeline, use `npx exb-community-cli i widgetname`. This will install the widget into your project. If you want to ensure that you always use the same version of the widget, simply add the version suffix ex `@1.5.3`.
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.formatWidget = formatWidget;
7
+ exports.isNameAvailable = isNameAvailable;
8
+ exports.suggestExbName = suggestExbName;
9
+ exports.makeUniqueName = makeUniqueName;
10
+ exports.validateAndChooseName = validateAndChooseName;
11
+ const fs_extra_1 = __importDefault(require("fs-extra"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const readline_1 = __importDefault(require("readline"));
14
+ const pacote_1 = __importDefault(require("pacote"));
15
+ const validate_npm_package_name_1 = __importDefault(require("validate-npm-package-name"));
16
+ async function formatWidget(widgetNameOrFolder, options = {}) {
17
+ const rootDir = process.cwd();
18
+ const widgetsDir = path_1.default.join(rootDir, 'your-extensions', 'widgets');
19
+ if (!fs_extra_1.default.existsSync(path_1.default.join(rootDir, 'your-extensions'))) {
20
+ console.error('Error: Run this from the ExB client folder.');
21
+ return;
22
+ }
23
+ if (!(await fs_extra_1.default.pathExists(widgetsDir))) {
24
+ console.error('Error: No widgets folder found (your-extensions/widgets).');
25
+ return;
26
+ }
27
+ // Find the widget folder by manifest.name or folder name
28
+ const candidates = await fs_extra_1.default.readdir(widgetsDir);
29
+ let widgetFolder = null;
30
+ let manifest = null;
31
+ for (const folder of candidates) {
32
+ const manifestPath = path_1.default.join(widgetsDir, folder, 'manifest.json');
33
+ if (await fs_extra_1.default.pathExists(manifestPath)) {
34
+ try {
35
+ const m = await fs_extra_1.default.readJson(manifestPath);
36
+ if (m && (m.name.toLowerCase() === widgetNameOrFolder.toLowerCase() || folder.toLowerCase() === widgetNameOrFolder.toLowerCase())) {
37
+ widgetFolder = path_1.default.join(widgetsDir, folder);
38
+ manifest = m;
39
+ break;
40
+ }
41
+ }
42
+ catch (err) {
43
+ console.warn(`Warning: Failed to read manifest.json for widget candidate '${folder}': ${err.message || err}`);
44
+ }
45
+ }
46
+ }
47
+ if (!widgetFolder || !manifest) {
48
+ console.error(`Error: widget '${widgetNameOrFolder}' not found in your-extensions/widgets`);
49
+ return;
50
+ }
51
+ const pkgPath = path_1.default.join(widgetFolder, 'package.json');
52
+ const pkgExists = await fs_extra_1.default.pathExists(pkgPath);
53
+ if (pkgExists && !options.force) {
54
+ const proceed = await confirmPrompt(`package.json already exists for '${widgetNameOrFolder}' and will be modified. Proceed? (y/n)`);
55
+ if (!proceed) {
56
+ console.log('Aborted — package.json left unchanged.');
57
+ return;
58
+ }
59
+ }
60
+ // Collect package.json fields from manifest or prompt for missing values
61
+ const pkg = {};
62
+ const initialName = (manifest.name ?? (await askForValue('package name (npm-safe)'))) || '';
63
+ pkg.name = await validateAndChooseName(initialName, options.force);
64
+ pkg.version = (await askForValue('version', '1.0.0')) || '1.0.0';
65
+ pkg.description = manifest.description ?? (await askForValue('description')) ?? '';
66
+ pkg.author = manifest.author ?? (await askForValue('author')) ?? '';
67
+ pkg.license = manifest.license ?? (await askForValue('license', 'MIT')) ?? 'MIT';
68
+ pkg.keywords = ['exb-widget', 'experience-builder', 'exb'];
69
+ // Backup existing package.json
70
+ if (pkgExists) {
71
+ const backupPath = pkgPath + `.bak-${Date.now()}`;
72
+ await fs_extra_1.default.copy(pkgPath, backupPath);
73
+ console.log(`Existing package.json backed up to ${path_1.default.basename(backupPath)}`);
74
+ }
75
+ // Write package.json
76
+ await fs_extra_1.default.writeJson(pkgPath, pkg, { spaces: 2 });
77
+ console.log(`Wrote package.json for widget '${widgetNameOrFolder}' (version ${pkg.version}).`);
78
+ }
79
+ async function askForValue(promptText, defaultValue) {
80
+ const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
81
+ const question = defaultValue ? `${promptText} [${defaultValue}]: ` : `${promptText}: `;
82
+ const answer = await new Promise((resolve) => rl.question(question, resolve));
83
+ rl.close();
84
+ const val = (answer || defaultValue || '').trim();
85
+ return val === '' ? undefined : val;
86
+ }
87
+ async function confirmPrompt(message) {
88
+ const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
89
+ const answer = await new Promise((resolve) => rl.question(message + ' ', resolve));
90
+ rl.close();
91
+ const input = (answer || '').trim().toLowerCase();
92
+ return input === 'y' || input === 'yes';
93
+ }
94
+ async function isNameAvailable(name) {
95
+ try {
96
+ // If pacote.packument resolves, the package exists on the registry => not available
97
+ await pacote_1.default.packument(name);
98
+ return false;
99
+ }
100
+ catch (err) {
101
+ // pkg not found (404) -> available; other errors -> rethrow
102
+ const status = err && (err.statusCode || err.status);
103
+ if (status === 404)
104
+ return true;
105
+ // Some registries return different messages — treat any 404-like as available
106
+ if (/404|not found/i.test(String(err && err.message)))
107
+ return true;
108
+ throw err;
109
+ }
110
+ }
111
+ function suggestExbName(name) {
112
+ // preserve scope if present
113
+ if (name.startsWith('@')) {
114
+ const parts = name.split('/');
115
+ const scope = parts[0];
116
+ const pkg = parts[1] || '';
117
+ return `${scope}/exb-${pkg}`;
118
+ }
119
+ return `exb-${name}`;
120
+ }
121
+ async function makeUniqueName(base) {
122
+ // append numeric suffix until available
123
+ let attempt = base;
124
+ let i = 1;
125
+ while (!(await isNameAvailable(attempt))) {
126
+ attempt = `${base}-${i}`;
127
+ i += 1;
128
+ if (i > 1000)
129
+ throw new Error('Unable to find an available package name');
130
+ }
131
+ return attempt;
132
+ }
133
+ async function validateAndChooseName(initial, force = false) {
134
+ let name = (initial || '').trim();
135
+ // keep prompting until valid & available
136
+ while (true) {
137
+ // validate format
138
+ const res = (0, validate_npm_package_name_1.default)(name || '');
139
+ if (!res.validForNewPackages) {
140
+ if (force) {
141
+ // try to coerce with exb- prefix
142
+ const suggested = suggestExbName(name || 'widget');
143
+ const validSuggested = (0, validate_npm_package_name_1.default)(suggested).validForNewPackages;
144
+ if (!validSuggested)
145
+ throw new Error(`Provided package name '${name}' is invalid and couldn't be coerced`);
146
+ name = suggested;
147
+ }
148
+ else {
149
+ console.log(`Invalid npm package name: ${(res.errors || []).concat(res.warnings || []).join('; ')}`);
150
+ const entered = await askForValue('enter a valid package name (npm-safe)');
151
+ if (!entered)
152
+ throw new Error('Package name required');
153
+ name = entered.trim();
154
+ continue;
155
+ }
156
+ }
157
+ // check availability
158
+ try {
159
+ const available = await isNameAvailable(name);
160
+ if (available)
161
+ return name;
162
+ // name taken
163
+ const suggested = suggestExbName(name);
164
+ if ((0, validate_npm_package_name_1.default)(suggested).validForNewPackages) {
165
+ if (force) {
166
+ const unique = await makeUniqueName(suggested);
167
+ return unique;
168
+ }
169
+ const useSuggested = await confirmPrompt(`Package name '${name}' is already taken on npm. Use suggested '${suggested}' instead? (y/n)`);
170
+ if (useSuggested) {
171
+ const finalName = (await isNameAvailable(suggested)) ? suggested : await makeUniqueName(suggested);
172
+ return finalName;
173
+ }
174
+ const entered = await askForValue('enter an alternative package name');
175
+ if (!entered)
176
+ throw new Error('Package name required');
177
+ name = entered.trim();
178
+ continue;
179
+ }
180
+ else {
181
+ // suggested name invalid (rare)
182
+ if (force) {
183
+ const unique = await makeUniqueName(name);
184
+ return unique;
185
+ }
186
+ console.log(`Package name '${name}' is taken and automatic suggestion is invalid.`);
187
+ const entered = await askForValue('enter an alternative package name');
188
+ if (!entered)
189
+ throw new Error('Package name required');
190
+ name = entered.trim();
191
+ continue;
192
+ }
193
+ }
194
+ catch (err) {
195
+ // network / registry error — surface message and abort
196
+ throw new Error(`Failed to check npm for package name availability: ${err.message || err}`);
197
+ }
198
+ }
199
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.removeWidget = removeWidget;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ async function removeWidget(nameOrPackage, options = {}) {
10
+ const rootDir = process.cwd();
11
+ const extensionsDir = path_1.default.join(rootDir, 'your-extensions', 'widgets');
12
+ if (!fs_extra_1.default.existsSync(path_1.default.join(rootDir, 'your-extensions'))) {
13
+ console.error('Error: Run this from the ExB client folder.');
14
+ return;
15
+ }
16
+ try {
17
+ const installedWidgets = await fs_extra_1.default.readdir(extensionsDir);
18
+ let targetWidgetDir = null;
19
+ for (const widget of installedWidgets) {
20
+ const manifestPath = path_1.default.join(extensionsDir, widget, 'manifest.json');
21
+ const pkgJsonPath = path_1.default.join(extensionsDir, widget, 'package.json');
22
+ if (await fs_extra_1.default.pathExists(manifestPath)) {
23
+ const manifest = await fs_extra_1.default.readJson(manifestPath);
24
+ const pkgJson = (await fs_extra_1.default.pathExists(pkgJsonPath)) ? await fs_extra_1.default.readJson(pkgJsonPath) : { name: manifest.name, version: manifest.version };
25
+ if (manifest.name === nameOrPackage || widget === nameOrPackage || pkgJson.name === nameOrPackage) {
26
+ targetWidgetDir = path_1.default.join(extensionsDir, widget);
27
+ break;
28
+ }
29
+ }
30
+ }
31
+ if (!targetWidgetDir) {
32
+ console.error(`Error: No installed widget found matching "${nameOrPackage}".`);
33
+ return;
34
+ }
35
+ if (!options.force) {
36
+ const confirm = await promptConfirmation(`Are you sure you want to remove the widget at ${targetWidgetDir}? (y/n)`);
37
+ if (!confirm) {
38
+ console.log('Aborting widget removal.');
39
+ return;
40
+ }
41
+ }
42
+ await fs_extra_1.default.remove(targetWidgetDir);
43
+ console.log(`Removed widget at ${targetWidgetDir} successfully!`);
44
+ }
45
+ catch (err) {
46
+ console.error(`Error: Failed to remove widget: ${err.message}`);
47
+ }
48
+ }
49
+ async function promptConfirmation(message) {
50
+ return new Promise((resolve) => {
51
+ process.stdout.write(message + ' ');
52
+ process.stdin.setEncoding('utf-8');
53
+ process.stdin.resume();
54
+ const onData = (data) => {
55
+ const input = data.trim().toLowerCase();
56
+ cleanup();
57
+ resolve(input === 'y' || input === 'yes');
58
+ };
59
+ const onEnd = () => {
60
+ cleanup();
61
+ resolve(false);
62
+ };
63
+ function cleanup() {
64
+ process.stdin.pause();
65
+ process.stdin.removeListener('data', onData);
66
+ process.stdin.removeListener('end', onEnd);
67
+ }
68
+ process.stdin.once('data', onData);
69
+ process.stdin.once('end', onEnd);
70
+ });
71
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.scaffoldWidget = scaffoldWidget;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const degit_1 = __importDefault(require("degit"));
11
+ const enquirer_1 = __importDefault(require("enquirer"));
12
+ const manifestCollector_1 = require("../utils/manifestCollector");
13
+ const format_1 = require("./format");
14
+ async function scaffoldWidget(name) {
15
+ const repoSource = "Esri/arcgis-experience-builder-sdk-resources/widgets";
16
+ const tmpDir = path_1.default.join(os_1.default.tmpdir(), `exb-scaffold-${Date.now()}`);
17
+ await fs_extra_1.default.ensureDir(tmpDir);
18
+ try {
19
+ const emitter = (0, degit_1.default)(repoSource, { cache: false, force: true, verbose: false });
20
+ await emitter.clone(tmpDir);
21
+ // Collect manifests from each widgets directory and merge results
22
+ const manifestsMap = {};
23
+ const m = await (0, manifestCollector_1.collectManifests)(tmpDir);
24
+ Object.assign(manifestsMap, m);
25
+ const items = Object.entries(manifestsMap).map(([file, manifest]) => ({ file, manifest }));
26
+ if (!items.length) {
27
+ console.log('No widgets found in the widgets folders.');
28
+ return;
29
+ }
30
+ console.log(`Found ${items.length} widgets in SDK-resources:`);
31
+ const choices = items.map((it) => ({
32
+ name: it.file,
33
+ message: `${it.manifest.label ?? it.manifest.name} — ${it.manifest.description ?? ''}`,
34
+ value: it.file
35
+ }));
36
+ try {
37
+ console.clear();
38
+ // Create prompt, and get user input
39
+ const prompt = new enquirer_1.default.Select({ name: 'widget', message: 'Choose a widget to scaffold', choices, limit: 10 });
40
+ const chosenFile = await prompt.run();
41
+ // Find the chosen item and copy its folder to the target location
42
+ const chosen = items.find((i) => i.file === chosenFile);
43
+ const widgetFolder = chosen.file.replace("/manifest.json", "");
44
+ const targetPath = path_1.default.join(process.cwd(), 'your-extensions', 'widgets', name);
45
+ await fs_extra_1.default.copy(widgetFolder, targetPath);
46
+ const manifestPath = path_1.default.join(targetPath, 'manifest.json');
47
+ const manifest = await fs_extra_1.default.readJson(manifestPath);
48
+ manifest.name = await (0, format_1.validateAndChooseName)(name);
49
+ await fs_extra_1.default.writeJson(manifestPath, manifest, { spaces: 2 });
50
+ console.log(`${chosen.manifest.label ?? chosen.manifest.name} scaffolded to ${targetPath}`);
51
+ }
52
+ catch (err) {
53
+ console.log('No selection made or prompt cancelled.');
54
+ }
55
+ }
56
+ finally {
57
+ try {
58
+ await fs_extra_1.default.remove(tmpDir);
59
+ }
60
+ catch (e) {
61
+ console.warn(`Warning: failed to remove temporary directory ${tmpDir}: ${e.message}`);
62
+ }
63
+ }
64
+ }
@@ -9,7 +9,7 @@ const pacote_1 = __importDefault(require("pacote"));
9
9
  const fs_extra_1 = __importDefault(require("fs-extra"));
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const os_1 = __importDefault(require("os"));
12
- const DEFAULT_KEYWORDS = ['arcgis-exb-widget', 'experience-builder-widget'];
12
+ const DEFAULT_KEYWORDS = ['exb-widget', 'experience-builder'];
13
13
  const DEFAULT_SIZE = 15;
14
14
  async function searchWidgets(options = {}) {
15
15
  if (options.githubList) {
package/dist/index.js CHANGED
@@ -40,6 +40,9 @@ const commander_1 = require("commander");
40
40
  const install_1 = require("./commands/install");
41
41
  const update_1 = require("./commands/update");
42
42
  const search_1 = require("./commands/search");
43
+ const remove_1 = require("./commands/remove");
44
+ const format_1 = require("./commands/format");
45
+ const scaffold_1 = require("./commands/scaffold");
43
46
  const fs = __importStar(require("fs-extra"));
44
47
  const path = __importStar(require("path"));
45
48
  const packageJsonPath = path.join(__dirname, '../package.json');
@@ -68,14 +71,14 @@ program
68
71
  .action(async (pkg, options) => {
69
72
  await (0, update_1.updateWidget)(pkg, { widgetOnly: options.widgetOnly, version: options.version });
70
73
  });
71
- // --- COMMAND: LIST (Example for future expansion) ---
74
+ // --- COMMAND: REMOVE ---
72
75
  program
73
- .command('list')
74
- .alias('ls')
75
- .description('List all community widgets currently installed in your project')
76
- .action(() => {
77
- console.log('Listing widgets is not yet implemented, but coming soon!');
78
- // Future logic: scan the client/your-extensions/widgets folder and print the names
76
+ .command('remove <package>')
77
+ .alias('rm')
78
+ .description('Remove an installed widget from your Experience Builder project')
79
+ .option('-f, --force', 'Skip confirmation prompt')
80
+ .action(async (pkg, options) => {
81
+ await (0, remove_1.removeWidget)(pkg, { force: options.force });
79
82
  });
80
83
  // --- COMMAND: SEARCH ---
81
84
  program
@@ -89,6 +92,23 @@ program
89
92
  const size = options.size ? Number(options.size) : undefined;
90
93
  await (0, search_1.searchWidgets)({ keyword: options.keyword, size, githubList: options.githubList });
91
94
  });
95
+ // --- COMMAND: FORMAT ---
96
+ program
97
+ .command('format <widget>')
98
+ .alias('fmt')
99
+ .description('Format a widget package according to community standards, based on the manifest.json configuration.')
100
+ .option('-f, --force', 'Skip prompts and overwrite package.json if present')
101
+ .action(async (widget, options) => {
102
+ await (0, format_1.formatWidget)(widget, { force: options.force });
103
+ });
104
+ // --- COMMAND: SCAFFOLD ---
105
+ program
106
+ .command('scaffold <name>')
107
+ .alias('new')
108
+ .description('Scaffold a new widget package with a manifest.json and package.json, using a template from the arcgis-experience-builder-sdk-resources repository.')
109
+ .action(async (name, options) => {
110
+ await (0, scaffold_1.scaffoldWidget)(name);
111
+ });
92
112
  // Parse the arguments passed by the user in the terminal
93
113
  program.parse(process.argv);
94
114
  // If the user runs the CLI with no arguments, show the help menu
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.collectManifests = collectManifests;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ /**
10
+ * Recursively find all manifest.json files under `rootDir` and return a map
11
+ * from absolute manifest file path -> parsed ExBManifest object.
12
+ *
13
+ * - Skips files that cannot be parsed.
14
+ * - Ignores node_modules and .git directories by default.
15
+ */
16
+ async function collectManifests(rootDir) {
17
+ const result = {};
18
+ async function walk(dir) {
19
+ const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
20
+ for (const e of entries) {
21
+ // skip common noisy folders
22
+ if (e.name === 'node_modules' || e.name === '.git')
23
+ continue;
24
+ const full = path_1.default.join(dir, e.name);
25
+ if (e.isDirectory()) {
26
+ await walk(full);
27
+ continue;
28
+ }
29
+ if (e.isFile() && e.name.toLowerCase() === 'manifest.json') {
30
+ try {
31
+ const json = await fs_extra_1.default.readJson(full);
32
+ // Only include objects that have at least a name or type field (best-effort)
33
+ if (json && typeof json === 'object') {
34
+ const manifest = {
35
+ name: json.name,
36
+ label: json.label,
37
+ description: json.description,
38
+ author: json.author,
39
+ license: json.license
40
+ };
41
+ result[full] = manifest;
42
+ }
43
+ }
44
+ catch (err) {
45
+ // silently skip invalid JSON
46
+ }
47
+ }
48
+ }
49
+ }
50
+ await walk(rootDir);
51
+ return result;
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exb-community-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A lightweight command line interface to allow Experience Builder developers to quickly import widgets published as NPM pacakges.",
5
5
  "keywords": [
6
6
  "exb-community",
@@ -16,7 +16,8 @@
16
16
  "author": "Lucius Creamer",
17
17
  "scripts": {
18
18
  "build": "tsc",
19
- "prepublishOnly": "npm run build"
19
+ "prepublishOnly": "npm run build",
20
+ "test": "jest --colors --runInBand"
20
21
  },
21
22
  "files": [
22
23
  "dist/**/*"
@@ -25,13 +26,20 @@
25
26
  "commander": "^14.0.3",
26
27
  "fs-extra": "^11.3.3",
27
28
  "pacote": "^21.3.1",
28
- "semver": "^7.6.3"
29
+ "semver": "^7.7.4",
30
+ "validate-npm-package-name": "^7.0.2",
31
+ "degit": "^2.8.0",
32
+ "enquirer": "^2.3.6"
29
33
  },
30
34
  "devDependencies": {
31
35
  "@types/fs-extra": "^11.0.4",
36
+ "@types/jest": "^30.0.0",
32
37
  "@types/node": "^25.2.3",
33
38
  "@types/pacote": "^11.1.8",
34
39
  "@types/semver": "^7.5.8",
40
+ "@types/validate-npm-package-name": "^4.0.2",
41
+ "jest": "^29.7.0",
42
+ "ts-jest": "^29.1.0",
35
43
  "typescript": "^5.9.3"
36
44
  }
37
45
  }