@ichicraft/widgets-widget-base 1.19.0 → 1.20.0

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
@@ -9,6 +9,15 @@ All notable changes to this project will be documented here.
9
9
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
10
10
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
11
11
 
12
+ ## 1.20.0 - 2026-08-25
13
+
14
+ - Added `createWidgetDevServerConfig` and `findAvailablePort`, exported from `@ichicraft/widgets-widget-base/lib/dev` for use in a widget's `webpack.dev.ts`. The helper resolves a free port before it builds the debug url, so the manifest url handed to the board always matches the port the dev server binds to. This is what allows several widgets to be served and debugged on the same board at the same time.
15
+ - Added `cssModuleLocalIdentName` to the result of `createWidgetDevServerConfig`. It is keyed by port, so css module class names stay unique between widgets that are served at the same time.
16
+ - Added optional `debugPort` property to `WidgetDebugServeConfig`, pinning a widget's dev server to a fixed port. When omitted, the first free port from 8080 upwards is used.
17
+ - Added optional `openBrowser` property to `WidgetDebugServeConfig`. Set it to `false` for the second and further widgets in a multi-widget debug session, so they serve their bundle without opening a browser window of their own.
18
+ - Added `webpack` and `webpack-dev-server` as optional peer dependencies. They are only needed when using the `lib/dev` helpers, which every widget project already has installed.
19
+ - **Breaking:** removed the `DebugComponentType` enum and the `debugComponentType` property of `WidgetDebugServeConfig`. Opening the board on one specific component no longer makes sense now that several widgets can be debugged at once. Remove both from your `config/serve.config.ts`.
20
+
12
21
  ## 1.19.0 - 2026-08-18
13
22
 
14
23
  - Added optional `bloomGroups` provider to `WidgetContext`, exposing a `fetchGroups()` function that supplies the tenant's Bloom Group definitions. Widgets can pass it through to their people pickers so Bloom Groups can be offered as suggestions.
@@ -0,0 +1,31 @@
1
+ import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
2
+ import type { WidgetDebugServeConfig } from '../types';
3
+ /** Port the first widget dev server claims. Later ones move up from here. */
4
+ export declare const DEFAULT_WIDGET_DEBUG_PORT = 8080;
5
+ export interface CreateWidgetDevServerConfigOptions {
6
+ /** Root folder of the widget project, normally `__dirname` from webpack.dev.ts. */
7
+ projectPath: string;
8
+ /** The widget's `config/serve.config.ts` export. */
9
+ serveConfig: WidgetDebugServeConfig;
10
+ }
11
+ export interface WidgetDevServerConfigResult {
12
+ /** The port the dev server will listen on. */
13
+ port: number;
14
+ devServer: DevServerConfiguration;
15
+ /**
16
+ * Value for the css-loader `localIdentName` option. Keyed by port so two widgets served at
17
+ * the same time -- and the CDN build of the same widget -- never share a class name.
18
+ */
19
+ cssModuleLocalIdentName: string;
20
+ /** Where the board fetches this widget's manifest from. */
21
+ manifestUrl: string;
22
+ /** The board url that gets opened, or null when no debug page is configured. */
23
+ debugPageUrl: string | null;
24
+ }
25
+ /**
26
+ * Builds the dev server configuration for debugging a widget against a live widget board.
27
+ *
28
+ * The port is resolved at config time rather than hardcoded, so the manifest url handed to the
29
+ * board always matches the port the server actually binds to.
30
+ */
31
+ export declare function createWidgetDevServerConfig(options: CreateWidgetDevServerConfigOptions): Promise<WidgetDevServerConfigResult>;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.createWidgetDevServerConfig = exports.DEFAULT_WIDGET_DEBUG_PORT = void 0;
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const os_1 = __importDefault(require("os"));
18
+ const path_1 = __importDefault(require("path"));
19
+ const url_1 = require("url");
20
+ const findAvailablePort_1 = require("./findAvailablePort");
21
+ /** Port the first widget dev server claims. Later ones move up from here. */
22
+ exports.DEFAULT_WIDGET_DEBUG_PORT = 8080;
23
+ /**
24
+ * Builds the dev server configuration for debugging a widget against a live widget board.
25
+ *
26
+ * The port is resolved at config time rather than hardcoded, so the manifest url handed to the
27
+ * board always matches the port the server actually binds to.
28
+ */
29
+ function createWidgetDevServerConfig(options) {
30
+ var _a;
31
+ return __awaiter(this, void 0, void 0, function* () {
32
+ const { projectPath, serveConfig } = options;
33
+ const port = (_a = serveConfig.debugPort) !== null && _a !== void 0 ? _a : (yield (0, findAvailablePort_1.findAvailablePort)(exports.DEFAULT_WIDGET_DEBUG_PORT));
34
+ const manifestUrl = `https://localhost:${port}/manifest.json`;
35
+ let debugPageUrl = null;
36
+ if (serveConfig.widgetsDebugPageUrl && serveConfig.openBrowser !== false) {
37
+ const serveUrl = new url_1.URL(serveConfig.widgetsDebugPageUrl);
38
+ serveUrl.searchParams.append('debugWidgetManifest', manifestUrl);
39
+ debugPageUrl = serveUrl.href;
40
+ }
41
+ return {
42
+ port,
43
+ manifestUrl,
44
+ debugPageUrl,
45
+ cssModuleLocalIdentName: `[local]_${port}`,
46
+ devServer: {
47
+ static: path_1.default.join(projectPath, './dist'),
48
+ port,
49
+ host: 'localhost',
50
+ allowedHosts: 'all',
51
+ headers: {
52
+ 'Access-Control-Allow-Origin': '*',
53
+ },
54
+ client: {
55
+ overlay: false,
56
+ },
57
+ server: {
58
+ type: 'https',
59
+ options: resolveDevCertificate(),
60
+ },
61
+ open: debugPageUrl
62
+ ? {
63
+ target: [debugPageUrl],
64
+ app: {
65
+ name: 'google-chrome',
66
+ arguments: ['--incognito', '--new-window'],
67
+ },
68
+ }
69
+ : false,
70
+ devMiddleware: {
71
+ writeToDisk: true,
72
+ },
73
+ },
74
+ };
75
+ });
76
+ }
77
+ exports.createWidgetDevServerConfig = createWidgetDevServerConfig;
78
+ /**
79
+ * Uses the SPFx localhost certificate that was trusted with `gulp trust-dev-cert`,
80
+ * falling back to the rushstack certificate on newer toolchains.
81
+ */
82
+ function resolveDevCertificate() {
83
+ const gcbCert = path_1.default.join(os_1.default.homedir(), '.gcb-serve-data', 'gcb-serve.cer');
84
+ const gcbKey = path_1.default.join(os_1.default.homedir(), '.gcb-serve-data', 'gcb-serve.key');
85
+ if (fs_1.default.existsSync(gcbCert) && fs_1.default.existsSync(gcbKey)) {
86
+ return { cert: fs_1.default.readFileSync(gcbCert), key: fs_1.default.readFileSync(gcbKey) };
87
+ }
88
+ return {
89
+ cert: fs_1.default.readFileSync(path_1.default.join(os_1.default.homedir(), '.rushstack', 'rushstack-serve.pem')),
90
+ key: fs_1.default.readFileSync(path_1.default.join(os_1.default.homedir(), '.rushstack', 'rushstack-serve.key')),
91
+ };
92
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Resolves the first free TCP port at or above `startPort`, so several widget dev servers
3
+ * can run side by side without anyone having to hand out port numbers.
4
+ */
5
+ export declare function findAvailablePort(startPort: number, attempts?: number): Promise<number>;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.findAvailablePort = void 0;
16
+ const net_1 = __importDefault(require("net"));
17
+ /** Both loopback stacks are checked: a dev server on one of them still owns the port. */
18
+ const LOOPBACK_HOSTS = ['127.0.0.1', '::1'];
19
+ /**
20
+ * Resolves the first free TCP port at or above `startPort`, so several widget dev servers
21
+ * can run side by side without anyone having to hand out port numbers.
22
+ */
23
+ function findAvailablePort(startPort, attempts = 50) {
24
+ return __awaiter(this, void 0, void 0, function* () {
25
+ for (let port = startPort; port < startPort + attempts; port++) {
26
+ if (yield isPortAvailable(port)) {
27
+ return port;
28
+ }
29
+ }
30
+ throw new Error(`[widgets-widget-base] No free port found between ${startPort} and ${startPort + attempts - 1}. Stop a running dev server or set "debugPort" in config/serve.config.ts.`);
31
+ });
32
+ }
33
+ exports.findAvailablePort = findAvailablePort;
34
+ function isPortAvailable(port) {
35
+ return __awaiter(this, void 0, void 0, function* () {
36
+ for (const host of LOOPBACK_HOSTS) {
37
+ if (!(yield isPortAvailableOnHost(port, host))) {
38
+ return false;
39
+ }
40
+ }
41
+ return true;
42
+ });
43
+ }
44
+ function isPortAvailableOnHost(port, host) {
45
+ return new Promise((resolve) => {
46
+ const server = net_1.default.createServer();
47
+ server.once('error', (err) => {
48
+ // Anything other than "taken" means we can't bind this stack at all (no IPv6, for
49
+ // example), which says nothing about whether the port is free.
50
+ resolve(err.code !== 'EADDRINUSE');
51
+ });
52
+ server.once('listening', () => server.close(() => resolve(true)));
53
+ server.listen(port, host);
54
+ });
55
+ }
@@ -0,0 +1,2 @@
1
+ export { createWidgetDevServerConfig, DEFAULT_WIDGET_DEBUG_PORT, type CreateWidgetDevServerConfigOptions, type WidgetDevServerConfigResult, } from './createWidgetDevServerConfig';
2
+ export { findAvailablePort } from './findAvailablePort';
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findAvailablePort = exports.DEFAULT_WIDGET_DEBUG_PORT = exports.createWidgetDevServerConfig = void 0;
4
+ var createWidgetDevServerConfig_1 = require("./createWidgetDevServerConfig");
5
+ Object.defineProperty(exports, "createWidgetDevServerConfig", { enumerable: true, get: function () { return createWidgetDevServerConfig_1.createWidgetDevServerConfig; } });
6
+ Object.defineProperty(exports, "DEFAULT_WIDGET_DEBUG_PORT", { enumerable: true, get: function () { return createWidgetDevServerConfig_1.DEFAULT_WIDGET_DEBUG_PORT; } });
7
+ var findAvailablePort_1 = require("./findAvailablePort");
8
+ Object.defineProperty(exports, "findAvailablePort", { enumerable: true, get: function () { return findAvailablePort_1.findAvailablePort; } });
@@ -705,17 +705,6 @@ export interface WidgetManifestConfig {
705
705
  }[];
706
706
  };
707
707
  }
708
- export declare enum DebugComponentType {
709
- /**
710
- * This is the default component type and just opens the default widget board.
711
- */
712
- Default = "Default",
713
- /**
714
- * This automatically opens the widget board administration panel and opens the widget admin config
715
- * dialog of the widget you're working on
716
- */
717
- AdminConfig = "AdminConfig"
718
- }
719
708
  /**
720
709
  * The widget debug serve config contains settings for debugging a widget in development
721
710
  */
@@ -726,11 +715,16 @@ export interface WidgetDebugServeConfig {
726
715
  */
727
716
  widgetsDebugPageUrl: string;
728
717
  /**
729
- * Provide a component type to open the debugging widget board with that specific component.
730
- * This speeds up your development cycle: [npm run start] > save changes > builds automatically >
731
- * refresh browser > automatically open updated component.
718
+ * Pin the dev server to a specific port. When omitted, the first free port from 8080 upwards
719
+ * is used, so several widgets can be debugged side by side without configuration.
720
+ */
721
+ debugPort?: number;
722
+ /**
723
+ * Open the widget board in a browser when the dev server starts. Defaults to true.
724
+ * Set this to false for the second and further widgets in a multi-widget debug session:
725
+ * they only need to serve their bundle and are registered from the board that is already open.
732
726
  */
733
- debugComponentType?: DebugComponentType;
727
+ openBrowser?: boolean;
734
728
  }
735
729
  /**
736
730
  * Tells the severity of the command bar item, resulting in
@@ -1,18 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CommandBarItemType = exports.CustomCommandBarItemSeverity = exports.DebugComponentType = void 0;
4
- var DebugComponentType;
5
- (function (DebugComponentType) {
6
- /**
7
- * This is the default component type and just opens the default widget board.
8
- */
9
- DebugComponentType["Default"] = "Default";
10
- /**
11
- * This automatically opens the widget board administration panel and opens the widget admin config
12
- * dialog of the widget you're working on
13
- */
14
- DebugComponentType["AdminConfig"] = "AdminConfig";
15
- })(DebugComponentType || (exports.DebugComponentType = DebugComponentType = {}));
3
+ exports.CommandBarItemType = exports.CustomCommandBarItemSeverity = void 0;
16
4
  /**
17
5
  * Tells the severity of the command bar item, resulting in
18
6
  * distinguishable presentation of the item
package/package.json CHANGED
@@ -1,45 +1,61 @@
1
- {
2
- "name": "@ichicraft/widgets-widget-base",
3
- "version": "1.19.0",
4
- "description": "Part of the Widget Development Kit for building widgets for Bloom Intranet",
5
- "main": "lib/index.js",
6
- "types": "lib/index.d.ts",
7
- "scripts": {
8
- "build": "tsc --build --force --verbose",
9
- "build:watch": "tsc --build --force --verbose --watch",
10
- "clean": "rimraf lib",
11
- "pretty": "prettier --write \"./**/*.{js,jsx,mjs,cjs,ts,tsx,json}\"",
12
- "prepublishOnly": "npm run clean && npm run build"
13
- },
14
- "keywords": [
15
- "ichicraft",
16
- "widget",
17
- "widgets"
18
- ],
19
- "author": "Ichicraft",
20
- "license": "UNLICENSED",
21
- "files": [
22
- "lib/**/*"
23
- ],
24
- "overrides": {
25
- "requirejs": "2.3.7",
26
- "validator@<13.15.26": "13.15.26",
27
- "z-schema@<5.0.5": "5.0.5"
28
- },
29
- "dependencies": {
30
- "@ichicraft/caching": "~1.0.2",
31
- "@microsoft/microsoft-graph-client": "3.0.2",
32
- "@microsoft/sp-http": "1.18.2",
33
- "@pnp/sp": "4.0.1"
34
- },
35
- "devDependencies": {
36
- "@ianvs/prettier-plugin-sort-imports": "4.4.1",
37
- "eslint": "8.57.0",
38
- "eslint-config-prettier": "9.1.0",
39
- "eslint-plugin-prettier": "5.2.1",
40
- "eslint-plugin-react": "7.35.2",
41
- "eslint-plugin-react-hooks": "4.6.2",
42
- "prettier": "3.3.3",
43
- "typescript": "~5.3.3"
44
- }
45
- }
1
+ {
2
+ "name": "@ichicraft/widgets-widget-base",
3
+ "version": "1.20.0",
4
+ "description": "Part of the Widget Development Kit for building widgets for Bloom Intranet",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc --build --force --verbose tsconfig.json tsconfig.dev.json",
9
+ "build:watch": "tsc --build --force --verbose --watch tsconfig.json tsconfig.dev.json",
10
+ "clean": "rimraf lib",
11
+ "pretty": "prettier --write \"./**/*.{js,jsx,mjs,cjs,ts,tsx,json}\"",
12
+ "prepublishOnly": "npm run clean && npm run build"
13
+ },
14
+ "keywords": [
15
+ "ichicraft",
16
+ "widget",
17
+ "widgets"
18
+ ],
19
+ "author": "Ichicraft",
20
+ "license": "UNLICENSED",
21
+ "files": [
22
+ "lib/**/*",
23
+ "!lib/**/*.tsbuildinfo"
24
+ ],
25
+ "overrides": {
26
+ "requirejs": "2.3.7",
27
+ "validator@<13.15.26": "13.15.26",
28
+ "z-schema@<5.0.5": "5.0.5"
29
+ },
30
+ "dependencies": {
31
+ "@ichicraft/caching": "~1.0.2",
32
+ "@microsoft/microsoft-graph-client": "3.0.2",
33
+ "@microsoft/sp-http": "1.18.2",
34
+ "@pnp/sp": "4.0.1"
35
+ },
36
+ "devDependencies": {
37
+ "@ianvs/prettier-plugin-sort-imports": "4.4.1",
38
+ "@types/node": "^18.19.0",
39
+ "eslint": "8.57.0",
40
+ "eslint-config-prettier": "9.1.0",
41
+ "eslint-plugin-prettier": "5.2.1",
42
+ "eslint-plugin-react": "7.35.2",
43
+ "eslint-plugin-react-hooks": "4.6.2",
44
+ "prettier": "3.3.3",
45
+ "typescript": "~5.3.3",
46
+ "webpack": "^5.109.2",
47
+ "webpack-dev-server": "^5.2.6"
48
+ },
49
+ "peerDependencies": {
50
+ "webpack": "^5.0.0",
51
+ "webpack-dev-server": "^5.0.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "webpack": {
55
+ "optional": true
56
+ },
57
+ "webpack-dev-server": {
58
+ "optional": true
59
+ }
60
+ }
61
+ }