@lwrjs/gate-module-provider 0.22.9
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 +10 -0
- package/README.md +180 -0
- package/build/cjs/index.cjs +87 -0
- package/build/cjs/utils.cjs +96 -0
- package/build/es/index.d.ts +25 -0
- package/build/es/index.js +61 -0
- package/build/es/utils.d.ts +58 -0
- package/build/es/utils.js +89 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
MIT LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020, Salesforce.com, Inc.
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
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:
|
|
7
|
+
|
|
8
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
9
|
+
|
|
10
|
+
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
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# @lwrjs/gate-module-provider
|
|
2
|
+
|
|
3
|
+
A module provider for LWR that exposes `@salesforce/gate` for feature gating. Use it to control feature rollouts, experiment toggles, and gradual releases within your LWC modules.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The gate module provider resolves imports like `@salesforce/gate/<gateName>` into virtual modules that expose a standard API: `isOpen(map)` and `hasError()`. Each gate is resolved at build/runtime from a configurable source—by default, a `gates.json` file in your project.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
The provider is included in the default LWR config. To customize it, add it to `moduleProviders` in your `lwr.config.json`:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"moduleProviders": [
|
|
16
|
+
[
|
|
17
|
+
"@lwrjs/gate-module-provider",
|
|
18
|
+
{
|
|
19
|
+
"gateConfigPath": "$rootDir/src/gates.json"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Options
|
|
27
|
+
|
|
28
|
+
| Option | Type | Default | Description |
|
|
29
|
+
| ---------------- | -------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `gateConfigPath` | `string` | `$rootDir/src/gates.json` | Path to your gates config file. Supports `$rootDir` placeholder. |
|
|
31
|
+
| `resolver` | `GateResolver` | — | Custom resolver function. Overrides `gateConfigPath` when provided. Use for container-specific resolution (e.g., Core, metadata). |
|
|
32
|
+
|
|
33
|
+
## gates.json Format
|
|
34
|
+
|
|
35
|
+
Create a `gates.json` file (typically in `src/`) with an array of gate entries:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
[
|
|
39
|
+
{
|
|
40
|
+
"name": "myFeatureGate",
|
|
41
|
+
"enabled": true,
|
|
42
|
+
"description": "Controls access to the new feature"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "experimentPreview",
|
|
46
|
+
"enabled": false,
|
|
47
|
+
"description": "Preview mode for A/B test"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Gate Entry Fields
|
|
53
|
+
|
|
54
|
+
| Field | Type | Required | Description |
|
|
55
|
+
| ------------- | --------- | -------- | ---------------------------------------------------------------------------------------------------- |
|
|
56
|
+
| `name` | `string` | Yes | Gate identifier. Must match the specifier (e.g. `myFeatureGate` → `@salesforce/gate/myFeatureGate`). |
|
|
57
|
+
| `enabled` | `boolean` | Yes | Open/closed state. |
|
|
58
|
+
| `description` | `string` | No | Human-readable description. |
|
|
59
|
+
|
|
60
|
+
Gate names may use alphanumeric characters, dots, underscores, and hyphens (e.g. `myFeatureGate`, `namespace.gateName`).
|
|
61
|
+
|
|
62
|
+
### Unknown Gates
|
|
63
|
+
|
|
64
|
+
If a gate name is not found in `gates.json`, the resolver returns `{ isOpen: false, hasError: false }`. Unknown gates are indistinguishable from known closed gates—both resolve as closed with no error. Ensure gate names in your imports match the `name` field in `gates.json` exactly.
|
|
65
|
+
|
|
66
|
+
## Client API
|
|
67
|
+
|
|
68
|
+
Each gate module exports two functions:
|
|
69
|
+
|
|
70
|
+
### `isOpen(map)`
|
|
71
|
+
|
|
72
|
+
Returns whether the gate is open.
|
|
73
|
+
|
|
74
|
+
- **Resolved**: Returns `true` or `false` based on the gate config.
|
|
75
|
+
- **Error**: Returns `map.fallback` when `hasError()` is true. If `map` is undefined or lacks `fallback`, returns `false`.
|
|
76
|
+
- **Defensive**: Safe to call `isOpen()` with no args; returns `false` when resolution failed and no fallback was provided.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
isOpen({ fallback: false }); // Use false when resolution fails
|
|
80
|
+
isOpen({ fallback: true }); // Use true when resolution fails (opt-in to feature on error)
|
|
81
|
+
isOpen(); // Safe: returns false when hasError() and no fallback provided
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `hasError()`
|
|
85
|
+
|
|
86
|
+
Returns `true` when gate resolution failed (e.g. config missing, network error). Use this to handle error states explicitly.
|
|
87
|
+
|
|
88
|
+
## Usage in LWC Modules
|
|
89
|
+
|
|
90
|
+
### Basic Gating
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// myComponent.ts
|
|
94
|
+
import { LightningElement } from 'lwc';
|
|
95
|
+
import { isOpen, hasError } from '@salesforce/gate/myFeatureGate';
|
|
96
|
+
|
|
97
|
+
export default class MyComponent extends LightningElement {
|
|
98
|
+
get featureEnabled(): boolean {
|
|
99
|
+
return isOpen({ fallback: false });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get showError(): boolean {
|
|
103
|
+
return hasError();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```html
|
|
109
|
+
<!-- myComponent.html -->
|
|
110
|
+
<template>
|
|
111
|
+
<template lwc:if="{showError}">
|
|
112
|
+
<p class="error">Unable to check feature availability.</p>
|
|
113
|
+
</template>
|
|
114
|
+
<template lwc:elseif="{featureEnabled}">
|
|
115
|
+
<p>New feature content</p>
|
|
116
|
+
</template>
|
|
117
|
+
<template lwc:else>
|
|
118
|
+
<p>Feature not available</p>
|
|
119
|
+
</template>
|
|
120
|
+
</template>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Conditional Rendering
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { isOpen } from '@salesforce/gate/experimentPreview';
|
|
127
|
+
|
|
128
|
+
export default class Dashboard extends LightningElement {
|
|
129
|
+
get showNewDashboard(): boolean {
|
|
130
|
+
return isOpen({ fallback: false });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Safe Defaults
|
|
136
|
+
|
|
137
|
+
When resolution fails, `isOpen` returns your `fallback` value. Choose based on your use case:
|
|
138
|
+
|
|
139
|
+
- `fallback: false` — Feature off when resolution fails (safer for new features).
|
|
140
|
+
- `fallback: true` — Feature on when resolution fails (use when the gate protects removal of legacy behavior).
|
|
141
|
+
|
|
142
|
+
## TypeScript Support
|
|
143
|
+
|
|
144
|
+
Add a declaration file (e.g. `src/types/scoped-modules.d.ts`) so TypeScript recognizes the gate imports:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
declare module '@salesforce/gate/*' {
|
|
148
|
+
export function isOpen(map: { fallback?: boolean }): boolean;
|
|
149
|
+
export function hasError(): boolean;
|
|
150
|
+
const gate: { isOpen: (map: { fallback?: boolean }) => boolean; hasError: () => boolean };
|
|
151
|
+
export default gate;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Custom Resolver
|
|
156
|
+
|
|
157
|
+
For container-specific resolution (e.g. Core Gater, metadata), provide a custom `resolver`:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import type { GateResolver } from '@lwrjs/gate-module-provider';
|
|
161
|
+
|
|
162
|
+
const myResolver: GateResolver = async (gateName, runtimeParams) => {
|
|
163
|
+
// Resolve from Core, metadata API, etc.
|
|
164
|
+
const enabled = await fetchGateState(gateName);
|
|
165
|
+
return { isOpen: enabled, hasError: false };
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// In lwr.config.json moduleProviders:
|
|
169
|
+
// ["@lwrjs/gate-module-provider", { resolver: myResolver }]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The resolver receives `(gateName: string, runtimeParams: RuntimeParams)` and returns `{ isOpen: boolean, hasError?: boolean }` (or a Promise of that).
|
|
173
|
+
|
|
174
|
+
## Static Builds (MRT)
|
|
175
|
+
|
|
176
|
+
Gate modules are compiled into static bundles at build time. The gate provider and `gates.json` are not required in the SSR lambda runtime; resolution happens during the build.
|
|
177
|
+
|
|
178
|
+
## gates.json Caching (Dev Mode)
|
|
179
|
+
|
|
180
|
+
The JSON resolver caches the parsed `gates.json` for the lifetime of the process. When editing `gates.json` during development, **restart the dev server** for changes to take effect. Resolution is effectively build-time for both dev and production.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, {get: all[name], enumerable: true});
|
|
11
|
+
};
|
|
12
|
+
var __exportStar = (target, module2, desc) => {
|
|
13
|
+
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(module2))
|
|
15
|
+
if (!__hasOwnProp.call(target, key) && key !== "default")
|
|
16
|
+
__defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
|
|
17
|
+
}
|
|
18
|
+
return target;
|
|
19
|
+
};
|
|
20
|
+
var __toModule = (module2) => {
|
|
21
|
+
return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// packages/@lwrjs/gate-module-provider/src/index.ts
|
|
25
|
+
__markAsModule(exports);
|
|
26
|
+
__export(exports, {
|
|
27
|
+
default: () => src_default
|
|
28
|
+
});
|
|
29
|
+
var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
|
|
30
|
+
var import_utils = __toModule(require("./utils.cjs"));
|
|
31
|
+
var GateModuleProvider = class {
|
|
32
|
+
constructor(options = {}, {config, runtimeEnvironment: {lwrVersion}}) {
|
|
33
|
+
this.name = "gate-module-provider";
|
|
34
|
+
this.version = lwrVersion;
|
|
35
|
+
const gateConfigPath = options.gateConfigPath ?? "$rootDir/src/gates.json";
|
|
36
|
+
if (options.resolver) {
|
|
37
|
+
this.resolver = options.resolver;
|
|
38
|
+
} else {
|
|
39
|
+
this.resolver = (0, import_utils.createJsonGateResolver)({
|
|
40
|
+
gateConfigPath,
|
|
41
|
+
rootDir: config.rootDir
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async getModuleEntry({specifier}, _runtimeParams = {}) {
|
|
46
|
+
const gateInfo = (0, import_utils.parseSpecifier)(specifier);
|
|
47
|
+
if (!gateInfo) {
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
id: `${specifier}|gate`,
|
|
52
|
+
virtual: true,
|
|
53
|
+
entry: `<virtual>/${gateInfo.package}/${gateInfo.gateName}.js`,
|
|
54
|
+
specifier,
|
|
55
|
+
version: this.version
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async getModule({specifier, namespace, name = specifier}, runtimeParams = {}) {
|
|
59
|
+
const gateInfo = (0, import_utils.parseSpecifier)(specifier);
|
|
60
|
+
if (!gateInfo) {
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
let compiledSource;
|
|
64
|
+
try {
|
|
65
|
+
const resolution = await Promise.resolve(this.resolver(gateInfo.gateName, runtimeParams));
|
|
66
|
+
compiledSource = (0, import_utils.generateModule)(resolution);
|
|
67
|
+
} catch {
|
|
68
|
+
compiledSource = (0, import_utils.generateModule)({isOpen: false, hasError: true});
|
|
69
|
+
}
|
|
70
|
+
const moduleEntry = await this.getModuleEntry({specifier}, runtimeParams);
|
|
71
|
+
if (!moduleEntry) {
|
|
72
|
+
return void 0;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
id: moduleEntry.id,
|
|
76
|
+
specifier,
|
|
77
|
+
namespace,
|
|
78
|
+
name,
|
|
79
|
+
version: this.version,
|
|
80
|
+
originalSource: compiledSource,
|
|
81
|
+
moduleEntry,
|
|
82
|
+
ownHash: (0, import_shared_utils.hashContent)(compiledSource),
|
|
83
|
+
compiledSource
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var src_default = GateModuleProvider;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, {get: all[name], enumerable: true});
|
|
11
|
+
};
|
|
12
|
+
var __exportStar = (target, module2, desc) => {
|
|
13
|
+
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(module2))
|
|
15
|
+
if (!__hasOwnProp.call(target, key) && key !== "default")
|
|
16
|
+
__defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
|
|
17
|
+
}
|
|
18
|
+
return target;
|
|
19
|
+
};
|
|
20
|
+
var __toModule = (module2) => {
|
|
21
|
+
return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// packages/@lwrjs/gate-module-provider/src/utils.ts
|
|
25
|
+
__markAsModule(exports);
|
|
26
|
+
__export(exports, {
|
|
27
|
+
GATE_PACKAGE: () => GATE_PACKAGE,
|
|
28
|
+
createJsonGateResolver: () => createJsonGateResolver,
|
|
29
|
+
generateModule: () => generateModule,
|
|
30
|
+
parseSpecifier: () => parseSpecifier,
|
|
31
|
+
stubGateResolver: () => stubGateResolver
|
|
32
|
+
});
|
|
33
|
+
var import_fs = __toModule(require("fs"));
|
|
34
|
+
var import_path = __toModule(require("path"));
|
|
35
|
+
var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
|
|
36
|
+
var GATE_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
37
|
+
var GATE_PACKAGE = "@salesforce/gate";
|
|
38
|
+
var stubGateResolver = async () => ({
|
|
39
|
+
isOpen: false,
|
|
40
|
+
hasError: false
|
|
41
|
+
});
|
|
42
|
+
function createJsonGateResolver(options) {
|
|
43
|
+
const {gateConfigPath, rootDir} = options;
|
|
44
|
+
const resolvedPath = (0, import_path.resolve)((0, import_shared_utils.normalizeDirectory)(gateConfigPath, rootDir));
|
|
45
|
+
let cache = null;
|
|
46
|
+
const loadGates = () => {
|
|
47
|
+
if (cache)
|
|
48
|
+
return cache;
|
|
49
|
+
if (!(0, import_fs.existsSync)(resolvedPath)) {
|
|
50
|
+
cache = [];
|
|
51
|
+
return cache;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const content = (0, import_shared_utils.readFile)(resolvedPath);
|
|
55
|
+
const parsed = JSON.parse(content);
|
|
56
|
+
cache = Array.isArray(parsed) ? parsed : [];
|
|
57
|
+
return cache;
|
|
58
|
+
} catch {
|
|
59
|
+
cache = [];
|
|
60
|
+
return cache;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
return (gateName) => {
|
|
64
|
+
const gates = loadGates();
|
|
65
|
+
const entry = gates.find((g) => g.name === gateName);
|
|
66
|
+
if (!entry) {
|
|
67
|
+
return {isOpen: false, hasError: false};
|
|
68
|
+
}
|
|
69
|
+
return {isOpen: entry.enabled, hasError: false};
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function parseSpecifier(specifier) {
|
|
73
|
+
if (!specifier.startsWith(`${GATE_PACKAGE}/`)) {
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
const gateName = specifier.replace(`${GATE_PACKAGE}/`, "");
|
|
77
|
+
if (!gateName || !GATE_NAME_PATTERN.test(gateName)) {
|
|
78
|
+
return void 0;
|
|
79
|
+
}
|
|
80
|
+
return {gateName, package: GATE_PACKAGE};
|
|
81
|
+
}
|
|
82
|
+
function generateModule(resolution) {
|
|
83
|
+
const {isOpen, hasError = false} = resolution;
|
|
84
|
+
if (hasError) {
|
|
85
|
+
return `export function isOpen(map) {
|
|
86
|
+
return map != null && 'fallback' in map ? map.fallback : false;
|
|
87
|
+
}
|
|
88
|
+
export function hasError() { return true; }
|
|
89
|
+
export default { isOpen, hasError: hasError };
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
return `export function isOpen(map) { return ${isOpen}; }
|
|
93
|
+
export function hasError() { return false; }
|
|
94
|
+
export default { isOpen, hasError: hasError };
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AbstractModuleId, ModuleCompiled, ModuleEntry, ModuleProvider, ProviderContext, RuntimeParams } from '@lwrjs/types';
|
|
2
|
+
import type { GateResolver, GateConfigEntry, GateResolverOptions } from './utils.js';
|
|
3
|
+
export type { GateResolver, GateConfigEntry, GateResolverOptions };
|
|
4
|
+
export interface GateProviderOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Path to gates.json in the user's project (e.g. '$rootDir/src/gates.json').
|
|
7
|
+
* When provided, gates are resolved from this file. Format matches Core Gater:
|
|
8
|
+
* [{ name, enabled, description? }]
|
|
9
|
+
*/
|
|
10
|
+
gateConfigPath?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Custom gate resolver. Overrides gateConfigPath when provided.
|
|
13
|
+
* Each container (webruntime, MRT, etc.) can supply its own resolver.
|
|
14
|
+
*/
|
|
15
|
+
resolver?: GateResolver;
|
|
16
|
+
}
|
|
17
|
+
export default class GateModuleProvider implements ModuleProvider {
|
|
18
|
+
name: string;
|
|
19
|
+
version: string;
|
|
20
|
+
private resolver;
|
|
21
|
+
constructor(options: GateProviderOptions | undefined, { config, runtimeEnvironment: { lwrVersion } }: ProviderContext);
|
|
22
|
+
getModuleEntry({ specifier }: AbstractModuleId, _runtimeParams?: RuntimeParams): Promise<ModuleEntry | undefined>;
|
|
23
|
+
getModule({ specifier, namespace, name }: AbstractModuleId, runtimeParams?: RuntimeParams): Promise<ModuleCompiled | undefined>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { hashContent } from '@lwrjs/shared-utils';
|
|
2
|
+
import { parseSpecifier, generateModule, createJsonGateResolver } from './utils.js';
|
|
3
|
+
export default class GateModuleProvider {
|
|
4
|
+
constructor(options = {}, { config, runtimeEnvironment: { lwrVersion } }) {
|
|
5
|
+
this.name = 'gate-module-provider';
|
|
6
|
+
this.version = lwrVersion;
|
|
7
|
+
const gateConfigPath = options.gateConfigPath ?? '$rootDir/src/gates.json';
|
|
8
|
+
if (options.resolver) {
|
|
9
|
+
this.resolver = options.resolver;
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
this.resolver = createJsonGateResolver({
|
|
13
|
+
gateConfigPath,
|
|
14
|
+
rootDir: config.rootDir,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async getModuleEntry({ specifier }, _runtimeParams = {}) {
|
|
19
|
+
const gateInfo = parseSpecifier(specifier);
|
|
20
|
+
if (!gateInfo) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
id: `${specifier}|gate`,
|
|
25
|
+
virtual: true,
|
|
26
|
+
entry: `<virtual>/${gateInfo.package}/${gateInfo.gateName}.js`,
|
|
27
|
+
specifier,
|
|
28
|
+
version: this.version,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async getModule({ specifier, namespace, name = specifier }, runtimeParams = {}) {
|
|
32
|
+
const gateInfo = parseSpecifier(specifier);
|
|
33
|
+
if (!gateInfo) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
let compiledSource;
|
|
37
|
+
try {
|
|
38
|
+
const resolution = await Promise.resolve(this.resolver(gateInfo.gateName, runtimeParams));
|
|
39
|
+
compiledSource = generateModule(resolution);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
compiledSource = generateModule({ isOpen: false, hasError: true });
|
|
43
|
+
}
|
|
44
|
+
const moduleEntry = await this.getModuleEntry({ specifier }, runtimeParams);
|
|
45
|
+
if (!moduleEntry) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
id: moduleEntry.id,
|
|
50
|
+
specifier,
|
|
51
|
+
namespace,
|
|
52
|
+
name,
|
|
53
|
+
version: this.version,
|
|
54
|
+
originalSource: compiledSource,
|
|
55
|
+
moduleEntry,
|
|
56
|
+
ownHash: hashContent(compiledSource),
|
|
57
|
+
compiledSource,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { RuntimeParams } from '@lwrjs/types';
|
|
2
|
+
export declare const GATE_PACKAGE = "@salesforce/gate";
|
|
3
|
+
export interface GateInfo {
|
|
4
|
+
gateName: string;
|
|
5
|
+
package: string;
|
|
6
|
+
}
|
|
7
|
+
export interface GateResolution {
|
|
8
|
+
isOpen: boolean;
|
|
9
|
+
hasError?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Gate config entry from gates.json (matches Core Gater format).
|
|
13
|
+
*/
|
|
14
|
+
export interface GateConfigEntry {
|
|
15
|
+
name: string;
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
description?: string;
|
|
18
|
+
teamId?: string;
|
|
19
|
+
tags?: string[];
|
|
20
|
+
version?: number;
|
|
21
|
+
responseType?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface GateResolverOptions {
|
|
24
|
+
gateConfigPath: string;
|
|
25
|
+
rootDir: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolver function that each container (webruntime, MRT, etc.) can implement
|
|
29
|
+
* to provide gate resolution from their own data source (Core, metadata, etc.)
|
|
30
|
+
*/
|
|
31
|
+
export type GateResolver = (gateName: string, runtimeParams: RuntimeParams) => Promise<GateResolution> | GateResolution;
|
|
32
|
+
/**
|
|
33
|
+
* Default stub resolver for development and testing.
|
|
34
|
+
* Returns closed gate with no error until real resolution is wired per container.
|
|
35
|
+
*/
|
|
36
|
+
export declare const stubGateResolver: GateResolver;
|
|
37
|
+
/**
|
|
38
|
+
* Create a resolver that reads gates from a JSON file in the user's project.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createJsonGateResolver(options: GateResolverOptions): GateResolver;
|
|
41
|
+
/**
|
|
42
|
+
* Parse a specifier into gate info.
|
|
43
|
+
* Returns undefined if the specifier is not for @salesforce/gate.
|
|
44
|
+
*
|
|
45
|
+
* @param specifier - e.g. '@salesforce/gate/myFeatureGate'
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseSpecifier(specifier: string): GateInfo | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Generate ESM module source for a gate with the standard @salesforce/gate contract.
|
|
50
|
+
*
|
|
51
|
+
* Contract (matches Core GateSourceProvider):
|
|
52
|
+
* - isOpen(map) - function that returns true/false when resolved, or map.fallback when hasError
|
|
53
|
+
* - hasError() - function that returns true when gate resolution failed
|
|
54
|
+
*
|
|
55
|
+
* @see https://confluence.internal.salesforce.com/display/gater/Gater+Overview
|
|
56
|
+
*/
|
|
57
|
+
export declare function generateModule(resolution: GateResolution): string;
|
|
58
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { readFile, normalizeDirectory } from '@lwrjs/shared-utils';
|
|
4
|
+
/** Gate name format: alphanumeric with dots/underscores (e.g. myFeatureGate, namespace.gateName) */
|
|
5
|
+
const GATE_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
6
|
+
export const GATE_PACKAGE = '@salesforce/gate';
|
|
7
|
+
/**
|
|
8
|
+
* Default stub resolver for development and testing.
|
|
9
|
+
* Returns closed gate with no error until real resolution is wired per container.
|
|
10
|
+
*/
|
|
11
|
+
export const stubGateResolver = async () => ({
|
|
12
|
+
isOpen: false,
|
|
13
|
+
hasError: false,
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Create a resolver that reads gates from a JSON file in the user's project.
|
|
17
|
+
*/
|
|
18
|
+
export function createJsonGateResolver(options) {
|
|
19
|
+
const { gateConfigPath, rootDir } = options;
|
|
20
|
+
const resolvedPath = resolve(normalizeDirectory(gateConfigPath, rootDir));
|
|
21
|
+
let cache = null;
|
|
22
|
+
const loadGates = () => {
|
|
23
|
+
if (cache)
|
|
24
|
+
return cache;
|
|
25
|
+
if (!existsSync(resolvedPath)) {
|
|
26
|
+
cache = [];
|
|
27
|
+
return cache;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const content = readFile(resolvedPath);
|
|
31
|
+
const parsed = JSON.parse(content);
|
|
32
|
+
cache = Array.isArray(parsed) ? parsed : [];
|
|
33
|
+
return cache;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
cache = [];
|
|
37
|
+
return cache;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
return (gateName) => {
|
|
41
|
+
const gates = loadGates();
|
|
42
|
+
const entry = gates.find((g) => g.name === gateName);
|
|
43
|
+
if (!entry) {
|
|
44
|
+
return { isOpen: false, hasError: false };
|
|
45
|
+
}
|
|
46
|
+
return { isOpen: entry.enabled, hasError: false };
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Parse a specifier into gate info.
|
|
51
|
+
* Returns undefined if the specifier is not for @salesforce/gate.
|
|
52
|
+
*
|
|
53
|
+
* @param specifier - e.g. '@salesforce/gate/myFeatureGate'
|
|
54
|
+
*/
|
|
55
|
+
export function parseSpecifier(specifier) {
|
|
56
|
+
if (!specifier.startsWith(`${GATE_PACKAGE}/`)) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const gateName = specifier.replace(`${GATE_PACKAGE}/`, '');
|
|
60
|
+
if (!gateName || !GATE_NAME_PATTERN.test(gateName)) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
return { gateName, package: GATE_PACKAGE };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generate ESM module source for a gate with the standard @salesforce/gate contract.
|
|
67
|
+
*
|
|
68
|
+
* Contract (matches Core GateSourceProvider):
|
|
69
|
+
* - isOpen(map) - function that returns true/false when resolved, or map.fallback when hasError
|
|
70
|
+
* - hasError() - function that returns true when gate resolution failed
|
|
71
|
+
*
|
|
72
|
+
* @see https://confluence.internal.salesforce.com/display/gater/Gater+Overview
|
|
73
|
+
*/
|
|
74
|
+
export function generateModule(resolution) {
|
|
75
|
+
const { isOpen, hasError = false } = resolution;
|
|
76
|
+
if (hasError) {
|
|
77
|
+
return `export function isOpen(map) {
|
|
78
|
+
return map != null && 'fallback' in map ? map.fallback : false;
|
|
79
|
+
}
|
|
80
|
+
export function hasError() { return true; }
|
|
81
|
+
export default { isOpen, hasError: hasError };
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
return `export function isOpen(map) { return ${isOpen}; }
|
|
85
|
+
export function hasError() { return false; }
|
|
86
|
+
export default { isOpen, hasError: hasError };
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=utils.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lwrjs/gate-module-provider",
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.22.9",
|
|
8
|
+
"homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/salesforce-experience-platform-emu/lwr.git",
|
|
12
|
+
"directory": "packages/@lwrjs/gate-module-provider"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/salesforce-experience-platform-emu/lwr/issues"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"types": "build/es/index.d.ts",
|
|
19
|
+
"main": "build/cjs/index.cjs",
|
|
20
|
+
"module": "build/es/index.js",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": "./build/es/index.js",
|
|
24
|
+
"require": "./build/cjs/index.cjs"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"build/**/*.js",
|
|
29
|
+
"build/**/*.cjs",
|
|
30
|
+
"build/**/*.d.ts"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -b"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@lwrjs/diagnostics": "0.22.9",
|
|
37
|
+
"@lwrjs/shared-utils": "0.22.9"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@lwrjs/types": "0.22.9"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=22.0.0"
|
|
44
|
+
},
|
|
45
|
+
"gitHead": "d0c112cd7b6eb940d6e554d0bbd93fb535465fdc"
|
|
46
|
+
}
|