exb-community-cli 1.0.1 → 1.0.3
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/LICENSE.md +7 -0
- package/README.md +5 -3
- package/dist/commands/dev-setup.js +214 -0
- package/dist/commands/format.js +1 -1
- package/dist/commands/scaffold.js +12 -2
- package/dist/commands/start.js +18 -0
- package/dist/index.js +52 -32
- package/dist/utils/startDevServer.js +60 -0
- package/package.json +15 -7
package/LICENSE.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Lucius Creamer
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -4,9 +4,11 @@ A simple, lightweight command line interface to make using Experience Builder De
|
|
|
4
4
|
|
|
5
5
|
## Using this CLI
|
|
6
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
|
|
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 install exb-community-cli` in your client directory of your Experience Builder application. To then run commands, the format is `npx exb [command]`. If you don't want to prefix every command with npx, you can install the exb-community-cli globally, by running `npm install -g exb-community-cli` (Recommended). For information on the commands that exist, run `npx exb help`, and commands, arguments, and descriptions will be listed.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Additionally, this CLI makes it easy to get up and running with Experience Builder, requiring a single command to install and run Experience Builder Developer Edition, making it easier than ever to get started. Just install the CLI globally `npm install -g exb-community-cli` and then run `exb dev-setup`. The tool will give you a few prompts to get started, and you'll end up with a fully functional install.
|
|
10
|
+
|
|
11
|
+
When using any of the ExB Community CLI commands, the command needs to be run in the client directory of your Experience builder application (excluding dev-setup). If you don't, you will get the error `Error: Run this from the ExB client folder.`.
|
|
10
12
|
|
|
11
13
|
## Formatting widgets for NPM (And Best Practices)
|
|
12
14
|
|
|
@@ -23,4 +25,4 @@ To prepare your widget to be easily ingested by the ExB Community CLI, there are
|
|
|
23
25
|
|
|
24
26
|
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
27
|
|
|
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
|
|
28
|
+
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 install 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,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.devSetup = devSetup;
|
|
40
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
41
|
+
const path_1 = __importDefault(require("path"));
|
|
42
|
+
const promises_1 = require("stream/promises");
|
|
43
|
+
const fs_1 = require("fs");
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const readline_1 = require("readline");
|
|
46
|
+
const enquirer_1 = __importDefault(require("enquirer"));
|
|
47
|
+
const os_1 = __importDefault(require("os"));
|
|
48
|
+
async function devSetup(options) {
|
|
49
|
+
let version = options.version;
|
|
50
|
+
const response = await fetch("https://developers.arcgis.com/experience-builder/guide/downloads/");
|
|
51
|
+
const html = await response.text();
|
|
52
|
+
// Extract version numbers from the page using a regex
|
|
53
|
+
const versions = [...html.matchAll(/py-4 pr-3">v(\d+\.\d+)/g)].map((m) => m[1]);
|
|
54
|
+
if (options.version && options.version == "latest") {
|
|
55
|
+
console.log("Setting up development environment for latest version...");
|
|
56
|
+
const latestVersion = versions[0];
|
|
57
|
+
version = latestVersion;
|
|
58
|
+
}
|
|
59
|
+
else if (options.version === undefined) {
|
|
60
|
+
const prompt = new enquirer_1.default.Select({
|
|
61
|
+
name: "version",
|
|
62
|
+
message: "Choose an Experience Builder version",
|
|
63
|
+
choices: versions,
|
|
64
|
+
limit: 5
|
|
65
|
+
});
|
|
66
|
+
const chosenVersion = await prompt.run();
|
|
67
|
+
version = chosenVersion;
|
|
68
|
+
}
|
|
69
|
+
// Actually install and set up the chosen version of Experience Builder
|
|
70
|
+
await installExperienceBuilder(version);
|
|
71
|
+
// Ask user if they want to start the development servers now
|
|
72
|
+
const approveStart = await new enquirer_1.default.Confirm({
|
|
73
|
+
name: "start",
|
|
74
|
+
message: "Do you want to start the development server now?"
|
|
75
|
+
}).run();
|
|
76
|
+
if (!approveStart) {
|
|
77
|
+
console.log("You can start the development server later by running `npm start` in the client and server folders.");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const clientDir = path_1.default.join(process.cwd(), `arcgis-experience-builder-${version}`, "client");
|
|
81
|
+
const serverDir = path_1.default.join(process.cwd(), `arcgis-experience-builder-${version}`, "server");
|
|
82
|
+
console.log("Starting client and server development servers...");
|
|
83
|
+
// Start dev servers using util
|
|
84
|
+
const { startDevServer } = await Promise.resolve().then(() => __importStar(require("../utils/startDevServer")));
|
|
85
|
+
startDevServer(clientDir, serverDir);
|
|
86
|
+
}
|
|
87
|
+
const ciWithSpinner = (message, cwd) => {
|
|
88
|
+
const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
89
|
+
let frameIndex = 0;
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
const interval = setInterval(() => {
|
|
92
|
+
frameIndex = (frameIndex + 1) % spinnerFrames.length;
|
|
93
|
+
process.stdout.write(`\r ${spinnerFrames[frameIndex]} ${message}`);
|
|
94
|
+
}, 80);
|
|
95
|
+
process.stdout.write(` ${spinnerFrames[0]} ${message}`);
|
|
96
|
+
const proc = (0, child_process_1.spawn)("npm", ["ci"], { cwd, stdio: "pipe" });
|
|
97
|
+
proc.on("close", (code) => {
|
|
98
|
+
clearInterval(interval);
|
|
99
|
+
if (code === 0) {
|
|
100
|
+
process.stdout.write(`\r ✔ ${message} done.\n`);
|
|
101
|
+
resolve();
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
process.stdout.write(`\r ✖ ${message} failed.\n`);
|
|
105
|
+
reject(new Error(`npm ci exited with code ${code}`));
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
proc.on("error", (err) => {
|
|
109
|
+
clearInterval(interval);
|
|
110
|
+
reject(err);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
// ...existing code...
|
|
115
|
+
const installExperienceBuilder = async (version) => {
|
|
116
|
+
const url = `https://downloads.arcgis.com/dms/rest/download/secured/arcgis-experience-builder-${version}.zip?f=json&folder=software%2FExperienceBuilder%2F${version}`;
|
|
117
|
+
const res = await fetch(url);
|
|
118
|
+
if (!res.ok) {
|
|
119
|
+
throw new Error(`Failed to fetch download info: ${res.status} ${res.statusText}`);
|
|
120
|
+
}
|
|
121
|
+
const json = (await res.json());
|
|
122
|
+
const downloadUrl = json.url;
|
|
123
|
+
if (!downloadUrl) {
|
|
124
|
+
throw new Error("No download URL found in response");
|
|
125
|
+
}
|
|
126
|
+
// Download the zip file to a temporary location
|
|
127
|
+
const zipPath = path_1.default.join(os_1.default.tmpdir(), `exb-install-${Date.now()}`);
|
|
128
|
+
const extractDir = path_1.default.resolve(process.cwd(), `arcgis-experience-builder-${version}`);
|
|
129
|
+
try {
|
|
130
|
+
console.log(`Downloading zip`);
|
|
131
|
+
const zipRes = await fetch(downloadUrl);
|
|
132
|
+
if (!zipRes.ok || !zipRes.body) {
|
|
133
|
+
throw new Error(`Failed to download zip: ${zipRes.status} ${zipRes.statusText}`);
|
|
134
|
+
}
|
|
135
|
+
await (0, promises_1.pipeline)(zipRes.body, (0, fs_1.createWriteStream)(zipPath));
|
|
136
|
+
// Extract the zip file to a folder in the current directory
|
|
137
|
+
console.log(`Extracting to ${extractDir}...`);
|
|
138
|
+
await fs_extra_1.default.ensureDir(extractDir);
|
|
139
|
+
// Get total file count for progress bar (pipe through tail to avoid buffer overflow on large zips)
|
|
140
|
+
const listSummary = (0, child_process_1.execSync)(`unzip -l "${zipPath}" | tail -1`, {
|
|
141
|
+
encoding: "utf-8"
|
|
142
|
+
});
|
|
143
|
+
const countMatch = listSummary.match(/(\d+)\s+files?/);
|
|
144
|
+
const totalFiles = countMatch ? parseInt(countMatch[1], 10) : 1;
|
|
145
|
+
await new Promise((resolve, reject) => {
|
|
146
|
+
let extracted = 0;
|
|
147
|
+
const proc = (0, child_process_1.spawn)("unzip", ["-o", zipPath, "-d", extractDir]);
|
|
148
|
+
const rl = (0, readline_1.createInterface)({ input: proc.stdout });
|
|
149
|
+
rl.on("line", (line) => {
|
|
150
|
+
if (/^\s*(extracting|inflating|creating):/.test(line)) {
|
|
151
|
+
extracted++;
|
|
152
|
+
const pct = Math.min(100, Math.round((extracted / totalFiles) * 100));
|
|
153
|
+
const filled = Math.round(pct / 4);
|
|
154
|
+
const bar = "█".repeat(filled) + "░".repeat(25 - filled);
|
|
155
|
+
process.stdout.write(`\r [${bar}] ${pct}% (${extracted}/${totalFiles})`);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
proc.stderr.on("data", () => { }); // suppress stderr
|
|
159
|
+
proc.on("close", (code) => {
|
|
160
|
+
process.stdout.write("\n");
|
|
161
|
+
if (code === 0)
|
|
162
|
+
resolve();
|
|
163
|
+
else
|
|
164
|
+
reject(new Error(`unzip exited with code ${code}`));
|
|
165
|
+
});
|
|
166
|
+
proc.on("error", reject);
|
|
167
|
+
});
|
|
168
|
+
// Remove any directory nesting (some zip versions include an extra top-level folder)
|
|
169
|
+
const entries = await fs_extra_1.default.readdir(extractDir);
|
|
170
|
+
const hasClient = entries.includes("client");
|
|
171
|
+
const hasServer = entries.includes("server");
|
|
172
|
+
if (!hasClient || !hasServer) {
|
|
173
|
+
// Check if there's a single nested directory containing client/ and server/
|
|
174
|
+
const dirs = [];
|
|
175
|
+
for (const entry of entries) {
|
|
176
|
+
const stat = await fs_extra_1.default.stat(path_1.default.join(extractDir, entry));
|
|
177
|
+
if (stat.isDirectory())
|
|
178
|
+
dirs.push(entry);
|
|
179
|
+
}
|
|
180
|
+
if (dirs.length === 1) {
|
|
181
|
+
const nestedDir = path_1.default.join(extractDir, dirs[0]);
|
|
182
|
+
const nestedEntries = await fs_extra_1.default.readdir(nestedDir);
|
|
183
|
+
if (nestedEntries.includes("client") &&
|
|
184
|
+
nestedEntries.includes("server")) {
|
|
185
|
+
// Move all contents from the nested dir up to extractDir
|
|
186
|
+
for (const item of nestedEntries) {
|
|
187
|
+
await fs_extra_1.default.move(path_1.default.join(nestedDir, item), path_1.default.join(extractDir, item), { overwrite: true });
|
|
188
|
+
}
|
|
189
|
+
await fs_extra_1.default.remove(nestedDir);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
throw new Error("Extracted zip does not contain expected client/ and server/ folders");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
throw new Error("Extracted zip does not contain expected client/ and server/ folders");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Install dependencies in client and server folders
|
|
200
|
+
const clientDir = path_1.default.join(extractDir, "client");
|
|
201
|
+
const serverDir = path_1.default.join(extractDir, "server");
|
|
202
|
+
await ciWithSpinner("Installing client dependencies...", clientDir);
|
|
203
|
+
await ciWithSpinner("Installing server dependencies...", serverDir);
|
|
204
|
+
return extractDir;
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
208
|
+
return "";
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
// Clean up the zip file
|
|
212
|
+
await fs_extra_1.default.remove(zipPath);
|
|
213
|
+
}
|
|
214
|
+
};
|
package/dist/commands/format.js
CHANGED
|
@@ -60,7 +60,7 @@ async function formatWidget(widgetNameOrFolder, options = {}) {
|
|
|
60
60
|
// Collect package.json fields from manifest or prompt for missing values
|
|
61
61
|
const pkg = {};
|
|
62
62
|
const initialName = (manifest.name ?? (await askForValue('package name (npm-safe)'))) || '';
|
|
63
|
-
pkg.name = await validateAndChooseName(initialName, options.force);
|
|
63
|
+
pkg.name = await validateAndChooseName(initialName.toLowerCase(), options.force);
|
|
64
64
|
pkg.version = (await askForValue('version', '1.0.0')) || '1.0.0';
|
|
65
65
|
pkg.description = manifest.description ?? (await askForValue('description')) ?? '';
|
|
66
66
|
pkg.author = manifest.author ?? (await askForValue('author')) ?? '';
|
|
@@ -10,8 +10,17 @@ const os_1 = __importDefault(require("os"));
|
|
|
10
10
|
const degit_1 = __importDefault(require("degit"));
|
|
11
11
|
const enquirer_1 = __importDefault(require("enquirer"));
|
|
12
12
|
const manifestCollector_1 = require("../utils/manifestCollector");
|
|
13
|
-
const format_1 = require("./format");
|
|
14
13
|
async function scaffoldWidget(name) {
|
|
14
|
+
const rootDir = process.cwd();
|
|
15
|
+
const widgetsDir = path_1.default.join(rootDir, 'your-extensions', 'widgets');
|
|
16
|
+
if (!fs_extra_1.default.existsSync(path_1.default.join(rootDir, 'your-extensions'))) {
|
|
17
|
+
console.error('Error: Run this from the ExB client folder.');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!(await fs_extra_1.default.pathExists(widgetsDir))) {
|
|
21
|
+
console.error('Error: No widgets folder found (your-extensions/widgets).');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
15
24
|
const repoSource = "Esri/arcgis-experience-builder-sdk-resources/widgets";
|
|
16
25
|
const tmpDir = path_1.default.join(os_1.default.tmpdir(), `exb-scaffold-${Date.now()}`);
|
|
17
26
|
await fs_extra_1.default.ensureDir(tmpDir);
|
|
@@ -45,7 +54,8 @@ async function scaffoldWidget(name) {
|
|
|
45
54
|
await fs_extra_1.default.copy(widgetFolder, targetPath);
|
|
46
55
|
const manifestPath = path_1.default.join(targetPath, 'manifest.json');
|
|
47
56
|
const manifest = await fs_extra_1.default.readJson(manifestPath);
|
|
48
|
-
manifest.name = await
|
|
57
|
+
//manifest.name = await validateAndChooseName(name);
|
|
58
|
+
manifest.name = name;
|
|
49
59
|
await fs_extra_1.default.writeJson(manifestPath, manifest, { spaces: 2 });
|
|
50
60
|
console.log(`${chosen.manifest.label ?? chosen.manifest.name} scaffolded to ${targetPath}`);
|
|
51
61
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
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.start = start;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const startDevServer_1 = require("../utils/startDevServer");
|
|
10
|
+
function start() {
|
|
11
|
+
const clientDir = process.cwd();
|
|
12
|
+
const serverDir = path_1.default.join(process.cwd(), "..", "server");
|
|
13
|
+
if (!fs_extra_1.default.existsSync(path_1.default.join(clientDir, "your-extensions"))) {
|
|
14
|
+
console.error("Error: Run this from the ExB client folder.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
(0, startDevServer_1.startDevServer)(clientDir, serverDir);
|
|
18
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -43,72 +43,92 @@ const search_1 = require("./commands/search");
|
|
|
43
43
|
const remove_1 = require("./commands/remove");
|
|
44
44
|
const format_1 = require("./commands/format");
|
|
45
45
|
const scaffold_1 = require("./commands/scaffold");
|
|
46
|
+
const dev_setup_1 = require("./commands/dev-setup");
|
|
47
|
+
const start_1 = require("./commands/start");
|
|
46
48
|
const fs = __importStar(require("fs-extra"));
|
|
47
49
|
const path = __importStar(require("path"));
|
|
48
|
-
const packageJsonPath = path.join(__dirname,
|
|
49
|
-
const packageJson = fs.readJsonSync(packageJsonPath, { throws: false }) || {
|
|
50
|
+
const packageJsonPath = path.join(__dirname, "../package.json");
|
|
51
|
+
const packageJson = fs.readJsonSync(packageJsonPath, { throws: false }) || {
|
|
52
|
+
version: "1.0.0"
|
|
53
|
+
};
|
|
50
54
|
const program = new commander_1.Command();
|
|
51
55
|
program
|
|
52
|
-
.name(
|
|
53
|
-
.description(
|
|
56
|
+
.name("exb")
|
|
57
|
+
.description("The community-led widget manager for ArcGIS Experience Builder.")
|
|
54
58
|
.version(packageJson.version);
|
|
55
59
|
// --- COMMAND: INSTALL ---
|
|
56
60
|
program
|
|
57
|
-
.command(
|
|
58
|
-
.alias(
|
|
59
|
-
.description(
|
|
60
|
-
.option(
|
|
61
|
+
.command("install <package>")
|
|
62
|
+
.alias("i") // Allows users to type `exb i widget-name`
|
|
63
|
+
.description("Install a widget from NPM into your Experience Builder project")
|
|
64
|
+
.option("--widget-only", "Skip running npm ci in the installed widget directory")
|
|
61
65
|
.action(async (pkg, options) => {
|
|
62
66
|
await (0, install_1.installWidget)(pkg, { widgetOnly: options.widgetOnly });
|
|
63
67
|
});
|
|
64
68
|
// --- COMMAND: UPDATE ---
|
|
65
69
|
program
|
|
66
|
-
.command(
|
|
67
|
-
.
|
|
68
|
-
.
|
|
69
|
-
.option(
|
|
70
|
-
.option('--version <version>', 'Specify a version or dist-tag to update to')
|
|
70
|
+
.command("update <package>")
|
|
71
|
+
.description("Update an installed widget to the latest version (or a specified version)")
|
|
72
|
+
.option("--widget-only", "Skip running npm ci in the updated widget directory")
|
|
73
|
+
.option("--version <version>", "Specify a version or dist-tag to update to")
|
|
71
74
|
.action(async (pkg, options) => {
|
|
72
|
-
await (0, update_1.updateWidget)(pkg, {
|
|
75
|
+
await (0, update_1.updateWidget)(pkg, {
|
|
76
|
+
widgetOnly: options.widgetOnly,
|
|
77
|
+
version: options.version
|
|
78
|
+
});
|
|
73
79
|
});
|
|
74
80
|
// --- COMMAND: REMOVE ---
|
|
75
81
|
program
|
|
76
|
-
.command(
|
|
77
|
-
.
|
|
78
|
-
.
|
|
79
|
-
.option('-f, --force', 'Skip confirmation prompt')
|
|
82
|
+
.command("remove <package>")
|
|
83
|
+
.description("Remove an installed widget from your Experience Builder project")
|
|
84
|
+
.option("-f, --force", "Skip confirmation prompt")
|
|
80
85
|
.action(async (pkg, options) => {
|
|
81
86
|
await (0, remove_1.removeWidget)(pkg, { force: options.force });
|
|
82
87
|
});
|
|
83
88
|
// --- COMMAND: SEARCH ---
|
|
84
89
|
program
|
|
85
|
-
.command(
|
|
86
|
-
.
|
|
87
|
-
.
|
|
88
|
-
.option(
|
|
89
|
-
.option(
|
|
90
|
-
.option('--github-list', 'Include results from a curated GitHub list when available')
|
|
90
|
+
.command("search")
|
|
91
|
+
.description("Search npm for Experience Builder widgets (by keyword)")
|
|
92
|
+
.option("-k, --keyword <keyword>", "Additional keyword to include in search")
|
|
93
|
+
.option("-n, --size <size>", "Maximum number of npm results to fetch (default: 15)")
|
|
94
|
+
.option("--github-list", "Include results from a curated GitHub list when available")
|
|
91
95
|
.action(async (options) => {
|
|
92
96
|
const size = options.size ? Number(options.size) : undefined;
|
|
93
|
-
await (0, search_1.searchWidgets)({
|
|
97
|
+
await (0, search_1.searchWidgets)({
|
|
98
|
+
keyword: options.keyword,
|
|
99
|
+
size,
|
|
100
|
+
githubList: options.githubList
|
|
101
|
+
});
|
|
94
102
|
});
|
|
95
103
|
// --- COMMAND: FORMAT ---
|
|
96
104
|
program
|
|
97
|
-
.command(
|
|
98
|
-
.
|
|
99
|
-
.
|
|
100
|
-
.option('-f, --force', 'Skip prompts and overwrite package.json if present')
|
|
105
|
+
.command("format <widget>")
|
|
106
|
+
.description("Format a widget package according to community standards, based on the manifest.json configuration.")
|
|
107
|
+
.option("-f, --force", "Skip prompts and overwrite package.json if present")
|
|
101
108
|
.action(async (widget, options) => {
|
|
102
109
|
await (0, format_1.formatWidget)(widget, { force: options.force });
|
|
103
110
|
});
|
|
104
111
|
// --- COMMAND: SCAFFOLD ---
|
|
105
112
|
program
|
|
106
|
-
.command(
|
|
107
|
-
.
|
|
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.')
|
|
113
|
+
.command("scaffold <name>")
|
|
114
|
+
.description("Scaffold a new widget package with a manifest.json, using an esri template")
|
|
109
115
|
.action(async (name, options) => {
|
|
110
116
|
await (0, scaffold_1.scaffoldWidget)(name);
|
|
111
117
|
});
|
|
118
|
+
program
|
|
119
|
+
.command("dev-setup")
|
|
120
|
+
.description("Download and set up the local development environment for Experience Builder widget development")
|
|
121
|
+
.option("--latest", "Download the latest version of Experience Builder Developer Edition")
|
|
122
|
+
.option("-v, --version <version>", "Specify a version of Experience Builder Developer Edition to download")
|
|
123
|
+
.action(async (options) => {
|
|
124
|
+
const version = options.version || (options.latest ? "latest" : undefined);
|
|
125
|
+
(0, dev_setup_1.devSetup)({ version });
|
|
126
|
+
//await scaffoldWidget('dev-environment', { version });
|
|
127
|
+
});
|
|
128
|
+
program
|
|
129
|
+
.command("start")
|
|
130
|
+
.description("Start the client and server development servers for the current Experience Builder Developer Edition environment")
|
|
131
|
+
.action(start_1.start);
|
|
112
132
|
// Parse the arguments passed by the user in the terminal
|
|
113
133
|
program.parse(process.argv);
|
|
114
134
|
// If the user runs the CLI with no arguments, show the help menu
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startDevServer = startDevServer;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
function startDevServer(clientDir, serverDir) {
|
|
6
|
+
console.log("Starting client and server development servers...");
|
|
7
|
+
// Save terminal settings before spawning child processes (Unix/macOS only)
|
|
8
|
+
let savedTermSettings = null;
|
|
9
|
+
if (process.platform !== "win32" && process.stdin.isTTY) {
|
|
10
|
+
try {
|
|
11
|
+
savedTermSettings = (0, child_process_1.execSync)("stty -g", {
|
|
12
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
13
|
+
})
|
|
14
|
+
.toString()
|
|
15
|
+
.trim();
|
|
16
|
+
}
|
|
17
|
+
catch { }
|
|
18
|
+
}
|
|
19
|
+
const restoreTerminal = () => {
|
|
20
|
+
if (savedTermSettings) {
|
|
21
|
+
try {
|
|
22
|
+
(0, child_process_1.execSync)(`stty ${savedTermSettings}`, { stdio: "inherit" });
|
|
23
|
+
}
|
|
24
|
+
catch { }
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const serverProc = (0, child_process_1.spawn)("npm", ["start"], {
|
|
28
|
+
cwd: serverDir,
|
|
29
|
+
stdio: "ignore",
|
|
30
|
+
detached: true
|
|
31
|
+
});
|
|
32
|
+
serverProc.unref();
|
|
33
|
+
const clientProc = (0, child_process_1.spawn)("npm", ["start"], {
|
|
34
|
+
cwd: clientDir,
|
|
35
|
+
stdio: "inherit"
|
|
36
|
+
});
|
|
37
|
+
let isShuttingDown = false;
|
|
38
|
+
const shutdown = () => {
|
|
39
|
+
if (isShuttingDown)
|
|
40
|
+
return;
|
|
41
|
+
isShuttingDown = true;
|
|
42
|
+
if (serverProc && !serverProc.killed && serverProc.pid) {
|
|
43
|
+
try {
|
|
44
|
+
process.kill(-serverProc.pid);
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
}
|
|
48
|
+
if (clientProc && !clientProc.killed) {
|
|
49
|
+
try {
|
|
50
|
+
clientProc.kill();
|
|
51
|
+
}
|
|
52
|
+
catch { }
|
|
53
|
+
}
|
|
54
|
+
restoreTerminal();
|
|
55
|
+
process.exit();
|
|
56
|
+
};
|
|
57
|
+
process.on("SIGINT", shutdown);
|
|
58
|
+
process.on("SIGTERM", shutdown);
|
|
59
|
+
clientProc.on("exit", shutdown);
|
|
60
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exb-community-cli",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "A lightweight command line interface to allow Experience Builder developers to quickly import widgets published as NPM pacakges.",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "A lightweight command line interface to allow Experience Builder developers to quickly import widgets published as NPM pacakges. Developed by the Esri Community.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"exb-community",
|
|
7
7
|
"cli",
|
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
"widget"
|
|
10
10
|
],
|
|
11
11
|
"bin": {
|
|
12
|
-
"exb
|
|
12
|
+
"exb": "dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/SunshineLuke90/exb-community-cli#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/SunshineLuke90/exb-community-cli/issues"
|
|
13
17
|
},
|
|
14
18
|
"preferGlobal": true,
|
|
15
19
|
"license": "MIT",
|
|
@@ -24,12 +28,12 @@
|
|
|
24
28
|
],
|
|
25
29
|
"dependencies": {
|
|
26
30
|
"commander": "^14.0.3",
|
|
31
|
+
"degit": "^2.8.0",
|
|
32
|
+
"enquirer": "^2.3.6",
|
|
27
33
|
"fs-extra": "^11.3.3",
|
|
28
34
|
"pacote": "^21.3.1",
|
|
29
35
|
"semver": "^7.7.4",
|
|
30
|
-
"validate-npm-package-name": "^7.0.2"
|
|
31
|
-
"degit": "^2.8.0",
|
|
32
|
-
"enquirer": "^2.3.6"
|
|
36
|
+
"validate-npm-package-name": "^7.0.2"
|
|
33
37
|
},
|
|
34
38
|
"devDependencies": {
|
|
35
39
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -41,5 +45,9 @@
|
|
|
41
45
|
"jest": "^29.7.0",
|
|
42
46
|
"ts-jest": "^29.1.0",
|
|
43
47
|
"typescript": "^5.9.3"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/SunshineLuke90/exb-community-cli.git"
|
|
44
52
|
}
|
|
45
|
-
}
|
|
53
|
+
}
|