@genesislcap/foundation-auth 15.22.1 → 15.23.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/dist/dts/store/authFormEntry.d.ts.map +1 -1
- package/dist/dts/store/readStoredCredentials.d.ts +47 -0
- package/dist/dts/store/readStoredCredentials.d.ts.map +1 -0
- package/dist/dts/store/readStoredCredentials.test.d.ts +2 -0
- package/dist/dts/store/readStoredCredentials.test.d.ts.map +1 -0
- package/dist/esm/store/authFormEntry.js +49 -9
- package/dist/esm/store/readStoredCredentials.js +48 -0
- package/dist/esm/store/readStoredCredentials.test.js +100 -0
- package/package.json +18 -18
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authFormEntry.d.ts","sourceRoot":"","sources":["../../../src/store/authFormEntry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,aAAa,EAAiB,KAAK,EAAE,MAAM,+BAA+B,CAAC;AAOpF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAuB,MAAM,eAAe,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"authFormEntry.d.ts","sourceRoot":"","sources":["../../../src/store/authFormEntry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { CredentialData, GetCredentialOptions } from '../credential/types';
|
|
2
|
+
/**
|
|
3
|
+
* The subset of `CredentialManager` this helper needs. Structural on purpose: it keeps this module
|
|
4
|
+
* free of the store/config import chain (which reaches image and translation assets and so cannot
|
|
5
|
+
* be loaded by the unit-test runner), so the behaviour below is directly testable.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export interface StoredCredentialSources {
|
|
10
|
+
getCredentialsFromContainer(options?: GetCredentialOptions): Promise<CredentialData | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Note the asymmetry with the container: this one is SYNCHRONOUS and yields `undefined`, not a
|
|
13
|
+
* promise of `null`. Mirrored exactly from `CredentialManager` so a real manager is assignable
|
|
14
|
+
* without a cast, and so the `?? null` normalisation below is visibly necessary rather than
|
|
15
|
+
* defensive noise.
|
|
16
|
+
*/
|
|
17
|
+
getCredentialsFromCookie(): CredentialData | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Read any stored credential, tolerating sources that are unavailable.
|
|
21
|
+
*
|
|
22
|
+
* GENC-1581. `getCredentialsFromContainer` calls `navigator.credentials.get()`, which REJECTS
|
|
23
|
+
* outright where the Credential Management API is not permitted — most commonly a cross-origin
|
|
24
|
+
* iframe, where it throws:
|
|
25
|
+
*
|
|
26
|
+
* ```
|
|
27
|
+
* NotAllowedError: The following credential operations can only occur in a document which is
|
|
28
|
+
* same-origin with all of its ancestors: storage/retrieval of 'PasswordCredential' …
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* That rejection used to propagate out of `autofill`, whose single `try` also wrapped the
|
|
32
|
+
* `DEFAULT_USER` / `DEFAULT_PASSWORD` fallback — so the defaults were skipped and the login form
|
|
33
|
+
* was left blank with nothing but a logged error. Any cross-origin embed of a Genesis app (the
|
|
34
|
+
* Genesis Create preview being the case that surfaced it) could therefore never use the configured
|
|
35
|
+
* defaults.
|
|
36
|
+
*
|
|
37
|
+
* Failing to READ a stored credential is not a reason to withhold the defaults: it means we do not
|
|
38
|
+
* know of one, which is precisely the case the fallback exists to cover. Each source is isolated so
|
|
39
|
+
* one throwing does not lose the other — a usable cookie still survives an unavailable container,
|
|
40
|
+
* which matters for the Neptune cookie upgrade path.
|
|
41
|
+
*
|
|
42
|
+
* @param sources - the credential manager
|
|
43
|
+
* @returns `[containerData, cookieData]`, either slot `null` when that source threw or held nothing
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
export declare function readStoredCredentials(sources: StoredCredentialSources, onError?: (message: string, err: unknown) => void): Promise<[CredentialData | null, CredentialData | null]>;
|
|
47
|
+
//# sourceMappingURL=readStoredCredentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readStoredCredentials.d.ts","sourceRoot":"","sources":["../../../src/store/readStoredCredentials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB;IACtC,2BAA2B,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC5F;;;;;OAKG;IACH,wBAAwB,IAAI,cAAc,GAAG,SAAS,CAAC;CACxD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,uBAAuB,EAChC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,GAChD,OAAO,CAAC,CAAC,cAAc,GAAG,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC,CAAC,CAiBzD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readStoredCredentials.test.d.ts","sourceRoot":"","sources":["../../../src/store/readStoredCredentials.test.ts"],"names":[],"mappings":""}
|
|
@@ -6,6 +6,7 @@ import { AuthConfig } from '../config/config';
|
|
|
6
6
|
import { CredentialManager, credentialSeparator } from '../credential';
|
|
7
7
|
import { IDPService } from '../services';
|
|
8
8
|
import { logger } from '../utils';
|
|
9
|
+
import { readStoredCredentials } from './readStoredCredentials';
|
|
9
10
|
/**
|
|
10
11
|
* Default AuthFormEntryStore.
|
|
11
12
|
* @public
|
|
@@ -120,8 +121,40 @@ export class DefaultAuthFormEntryStore extends AbstractStore {
|
|
|
120
121
|
* from cookies if the container is empty. These cookies become irrelevant after first login using container.
|
|
121
122
|
*/
|
|
122
123
|
this.autofilling = true;
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Stored-credential lookup is BEST EFFORT and must not take the DEFAULT_* fallback below down
|
|
126
|
+
* with it. `getCredentialsFromContainer` calls `navigator.credentials.get()`, which REJECTS
|
|
127
|
+
* outright in contexts where the Credential Management API is unavailable — most commonly a
|
|
128
|
+
* cross-origin iframe, where it throws
|
|
129
|
+
*
|
|
130
|
+
* NotAllowedError: The following credential operations can only occur in a document which
|
|
131
|
+
* is same-origin with all of its ancestors: storage/retrieval of 'PasswordCredential' …
|
|
132
|
+
*
|
|
133
|
+
* Previously one `try` wrapped both this lookup and the DEFAULT_USER/DEFAULT_PASSWORD
|
|
134
|
+
* fallback, so that rejection skipped the fallback entirely and the form was left blank with
|
|
135
|
+
* only a logged error to show for it. Anything that embeds a Genesis app cross-origin — the
|
|
136
|
+
* Genesis Create preview being the case that surfaced it — could therefore never use the
|
|
137
|
+
* configured defaults.
|
|
138
|
+
*
|
|
139
|
+
* Failing to READ a stored credential is not a reason to withhold the defaults: it means we
|
|
140
|
+
* do not know of one, which is exactly the `!credentialData` case the fallback exists for.
|
|
141
|
+
*/
|
|
142
|
+
const [containerData, cookieData] = yield readStoredCredentials(this.credentialManager,
|
|
143
|
+
/**
|
|
144
|
+
* An unavailable credential store is EXPECTED in a cross-origin embed, so it logs at
|
|
145
|
+
* `debug` — but a genuine malfunction should still be audible. `getCredentialsFromCookie`
|
|
146
|
+
* does `JSON.parse(cookie)`, so a malformed cookie throws a SyntaxError through here, and
|
|
147
|
+
* that wants saying out loud. `credentialManager` already uses `warn` for the adjacent
|
|
148
|
+
* "not available in this browser" case.
|
|
149
|
+
*/
|
|
150
|
+
(message, err) => {
|
|
151
|
+
const name = err instanceof Error ? err.name : '';
|
|
152
|
+
const expected = name === 'NotAllowedError' || name === 'SecurityError';
|
|
153
|
+
if (expected)
|
|
154
|
+
logger.debug(message, err);
|
|
155
|
+
else
|
|
156
|
+
logger.warn(message, err);
|
|
157
|
+
});
|
|
125
158
|
const [normalisedData, canRemoveCookie] = this.credentialManager.normaliseLegacyCredentialData(containerData, cookieData);
|
|
126
159
|
if (cookieData && canRemoveCookie) {
|
|
127
160
|
/**
|
|
@@ -130,13 +163,6 @@ export class DefaultAuthFormEntryStore extends AbstractStore {
|
|
|
130
163
|
this.credentialManager.removeCookie();
|
|
131
164
|
}
|
|
132
165
|
credentialData = normalisedData;
|
|
133
|
-
if (!credentialData && (DEFAULT_USER || DEFAULT_PASSWORD || DEFAULT_ORGANISATION)) {
|
|
134
|
-
this.username = DEFAULT_USER !== null && DEFAULT_USER !== void 0 ? DEFAULT_USER : '';
|
|
135
|
-
this.password = DEFAULT_PASSWORD !== null && DEFAULT_PASSWORD !== void 0 ? DEFAULT_PASSWORD : '';
|
|
136
|
-
if (this.config.fields.organisation && DEFAULT_ORGANISATION) {
|
|
137
|
-
this.organisation = DEFAULT_ORGANISATION;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
166
|
if (credentialData) {
|
|
141
167
|
const passwordCredentials = credentialData;
|
|
142
168
|
this.username = (_a = passwordCredentials.name) !== null && _a !== void 0 ? _a : '';
|
|
@@ -176,6 +202,20 @@ export class DefaultAuthFormEntryStore extends AbstractStore {
|
|
|
176
202
|
catch (err) {
|
|
177
203
|
logger.error('An error occurred attempting to get and autofill from stored credentials.', err);
|
|
178
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* OUTSIDE the try on purpose. The defaults are the fallback for "we have no stored credential",
|
|
207
|
+
* and every way of arriving there — none stored, an unreadable store, or an unexpected throw
|
|
208
|
+
* anywhere above — must reach them. Keeping this inside the try is what made a single rejecting
|
|
209
|
+
* call silently skip it, and leaving it there would preserve that shape while relying on
|
|
210
|
+
* nothing above it ever throwing again.
|
|
211
|
+
*/
|
|
212
|
+
if (!credentialData && (DEFAULT_USER || DEFAULT_PASSWORD || DEFAULT_ORGANISATION)) {
|
|
213
|
+
this.username = DEFAULT_USER !== null && DEFAULT_USER !== void 0 ? DEFAULT_USER : '';
|
|
214
|
+
this.password = DEFAULT_PASSWORD !== null && DEFAULT_PASSWORD !== void 0 ? DEFAULT_PASSWORD : '';
|
|
215
|
+
if (this.config.fields.organisation && DEFAULT_ORGANISATION) {
|
|
216
|
+
this.organisation = DEFAULT_ORGANISATION;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
179
219
|
this.autofilling = false;
|
|
180
220
|
return Promise.resolve(!!credentialData);
|
|
181
221
|
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { __awaiter } from "tslib";
|
|
2
|
+
/**
|
|
3
|
+
* Read any stored credential, tolerating sources that are unavailable.
|
|
4
|
+
*
|
|
5
|
+
* GENC-1581. `getCredentialsFromContainer` calls `navigator.credentials.get()`, which REJECTS
|
|
6
|
+
* outright where the Credential Management API is not permitted — most commonly a cross-origin
|
|
7
|
+
* iframe, where it throws:
|
|
8
|
+
*
|
|
9
|
+
* ```
|
|
10
|
+
* NotAllowedError: The following credential operations can only occur in a document which is
|
|
11
|
+
* same-origin with all of its ancestors: storage/retrieval of 'PasswordCredential' …
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* That rejection used to propagate out of `autofill`, whose single `try` also wrapped the
|
|
15
|
+
* `DEFAULT_USER` / `DEFAULT_PASSWORD` fallback — so the defaults were skipped and the login form
|
|
16
|
+
* was left blank with nothing but a logged error. Any cross-origin embed of a Genesis app (the
|
|
17
|
+
* Genesis Create preview being the case that surfaced it) could therefore never use the configured
|
|
18
|
+
* defaults.
|
|
19
|
+
*
|
|
20
|
+
* Failing to READ a stored credential is not a reason to withhold the defaults: it means we do not
|
|
21
|
+
* know of one, which is precisely the case the fallback exists to cover. Each source is isolated so
|
|
22
|
+
* one throwing does not lose the other — a usable cookie still survives an unavailable container,
|
|
23
|
+
* which matters for the Neptune cookie upgrade path.
|
|
24
|
+
*
|
|
25
|
+
* @param sources - the credential manager
|
|
26
|
+
* @returns `[containerData, cookieData]`, either slot `null` when that source threw or held nothing
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export function readStoredCredentials(sources, onError) {
|
|
30
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
31
|
+
var _a, _b;
|
|
32
|
+
let containerData = null;
|
|
33
|
+
let cookieData = null;
|
|
34
|
+
try {
|
|
35
|
+
containerData = (_a = (yield sources.getCredentialsFromContainer())) !== null && _a !== void 0 ? _a : null;
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
onError === null || onError === void 0 ? void 0 : onError('Credential container unavailable; continuing without a stored credential.', err);
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
cookieData = (_b = (yield sources.getCredentialsFromCookie())) !== null && _b !== void 0 ? _b : null;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
onError === null || onError === void 0 ? void 0 : onError('Credential cookie unavailable; continuing without a stored credential.', err);
|
|
45
|
+
}
|
|
46
|
+
return [containerData, cookieData];
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { __awaiter } from "tslib";
|
|
2
|
+
import { assert, suite } from '@genesislcap/foundation-testing';
|
|
3
|
+
import { readStoredCredentials } from './readStoredCredentials';
|
|
4
|
+
/**
|
|
5
|
+
* GENC-1581 — the DEFAULT_USER / DEFAULT_PASSWORD fallback must survive a Credential Management
|
|
6
|
+
* API that rejects.
|
|
7
|
+
*
|
|
8
|
+
* `getCredentialsFromContainer()` calls `navigator.credentials.get()`, which throws outright where
|
|
9
|
+
* the API is unavailable — most commonly a cross-origin iframe:
|
|
10
|
+
*
|
|
11
|
+
* NotAllowedError: The following credential operations can only occur in a document which is
|
|
12
|
+
* same-origin with all of its ancestors: storage/retrieval of 'PasswordCredential' …
|
|
13
|
+
*
|
|
14
|
+
* One `try` used to wrap both that lookup and the DEFAULT_* fallback, so the rejection skipped the
|
|
15
|
+
* fallback and left the form blank. `readStoredCredentials` now isolates the lookup.
|
|
16
|
+
*
|
|
17
|
+
* These tests drive `readStoredCredentials` directly rather than `autofill`, because `autofill`
|
|
18
|
+
* reads the DEFAULT_* values from build-time defines that a unit test cannot set. What is testable
|
|
19
|
+
* — and what actually broke — is that a throwing source degrades to `null` instead of propagating.
|
|
20
|
+
*/
|
|
21
|
+
const s = suite('readStoredCredentials (GENC-1581)');
|
|
22
|
+
/**
|
|
23
|
+
* The helper lives in its own module precisely so it can be tested: `authFormEntry` transitively
|
|
24
|
+
* imports image and translation assets through `config/config.ts`, which the unit-test runner
|
|
25
|
+
* cannot load.
|
|
26
|
+
*/
|
|
27
|
+
const read = (manager) => readStoredCredentials(manager);
|
|
28
|
+
const NOT_ALLOWED = () => {
|
|
29
|
+
const err = new Error("The following credential operations can only occur in a document which is same-origin with all of its ancestors: storage/retrieval of 'PasswordCredential' and 'FederatedCredential', storage of 'PublicKeyCredential'.");
|
|
30
|
+
err.name = 'NotAllowedError';
|
|
31
|
+
throw err;
|
|
32
|
+
};
|
|
33
|
+
s('a rejecting credential container degrades to null instead of throwing', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
const result = read({
|
|
35
|
+
getCredentialsFromContainer: NOT_ALLOWED,
|
|
36
|
+
getCredentialsFromCookie: () => undefined,
|
|
37
|
+
});
|
|
38
|
+
const [containerData, cookieData] = yield result;
|
|
39
|
+
assert.is(containerData, null, 'a NotAllowedError must not escape');
|
|
40
|
+
assert.is(cookieData, null);
|
|
41
|
+
}));
|
|
42
|
+
s('a rejecting cookie read degrades to null too', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
43
|
+
const result = read({
|
|
44
|
+
getCredentialsFromContainer: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
|
|
45
|
+
getCredentialsFromCookie: NOT_ALLOWED,
|
|
46
|
+
});
|
|
47
|
+
const [containerData, cookieData] = yield result;
|
|
48
|
+
assert.is(containerData, null);
|
|
49
|
+
assert.is(cookieData, null);
|
|
50
|
+
}));
|
|
51
|
+
s('both rejecting still resolves — the caller can reach its fallback', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
52
|
+
// The exact cross-origin-iframe shape: neither source is usable. Before the fix this rejected
|
|
53
|
+
// and the DEFAULT_* fallback below the call site never ran.
|
|
54
|
+
const result = read({
|
|
55
|
+
getCredentialsFromContainer: NOT_ALLOWED,
|
|
56
|
+
getCredentialsFromCookie: NOT_ALLOWED,
|
|
57
|
+
});
|
|
58
|
+
assert.equal(yield result, [null, null]);
|
|
59
|
+
}));
|
|
60
|
+
s('a real stored credential is still returned untouched', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
61
|
+
// The guard must not swallow success: a working container still wins over the defaults.
|
|
62
|
+
const stored = { id: 'JohnDoe', name: 'JohnDoe', password: 'hunter2' };
|
|
63
|
+
const result = read({
|
|
64
|
+
getCredentialsFromContainer: () => __awaiter(void 0, void 0, void 0, function* () { return stored; }),
|
|
65
|
+
getCredentialsFromCookie: () => undefined,
|
|
66
|
+
});
|
|
67
|
+
const [containerData] = yield result;
|
|
68
|
+
assert.is(containerData, stored);
|
|
69
|
+
}));
|
|
70
|
+
s('a cookie credential survives a throwing container (the legacy upgrade path)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
71
|
+
// Neptune upgrade: container unavailable, cookie present. Isolating the two reads means the
|
|
72
|
+
// cookie is still found — previously the container throw lost it as well.
|
|
73
|
+
const cookie = { id: 'JaneDoe', name: 'JaneDoe', password: 'hunter2' };
|
|
74
|
+
const result = read({
|
|
75
|
+
getCredentialsFromContainer: NOT_ALLOWED,
|
|
76
|
+
getCredentialsFromCookie: () => cookie,
|
|
77
|
+
});
|
|
78
|
+
const [containerData, cookieData] = yield result;
|
|
79
|
+
assert.is(containerData, null);
|
|
80
|
+
assert.is(cookieData, cookie, 'a usable cookie must not be lost to an unrelated container error');
|
|
81
|
+
}));
|
|
82
|
+
s('reports each failure through onError so the caller can choose a log level', () => {
|
|
83
|
+
// The helper deliberately does not decide severity: an unavailable store is expected in a
|
|
84
|
+
// cross-origin embed (debug), while a malformed cookie hitting JSON.parse is a real malfunction
|
|
85
|
+
// (warn). authFormEntry discriminates on error name, which it can only do if BOTH the message
|
|
86
|
+
// and the original error reach it.
|
|
87
|
+
const seen = [];
|
|
88
|
+
const bad = () => {
|
|
89
|
+
const e = new Error('Unexpected token in JSON');
|
|
90
|
+
e.name = 'SyntaxError';
|
|
91
|
+
throw e;
|
|
92
|
+
};
|
|
93
|
+
return readStoredCredentials({ getCredentialsFromContainer: NOT_ALLOWED, getCredentialsFromCookie: bad }, (message, err) => seen.push({ message, name: err instanceof Error ? err.name : '' })).then(() => {
|
|
94
|
+
assert.is(seen.length, 2, 'both sources report');
|
|
95
|
+
assert.equal(seen.map((x) => x.name), ['NotAllowedError', 'SyntaxError'], 'the original error is passed through, not just a message');
|
|
96
|
+
assert.ok(seen[0].message.includes('container'));
|
|
97
|
+
assert.ok(seen[1].message.includes('cookie'));
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
s.run();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genesislcap/foundation-auth",
|
|
3
3
|
"description": "Genesis Foundation Auth",
|
|
4
|
-
"version": "15.
|
|
4
|
+
"version": "15.23.0",
|
|
5
5
|
"license": "SEE LICENSE IN license.txt",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/foundation-auth.d.ts",
|
|
@@ -134,25 +134,25 @@
|
|
|
134
134
|
}
|
|
135
135
|
},
|
|
136
136
|
"devDependencies": {
|
|
137
|
-
"@genesislcap/foundation-testing": "15.
|
|
138
|
-
"@genesislcap/genx": "15.
|
|
139
|
-
"@genesislcap/rollup-builder": "15.
|
|
140
|
-
"@genesislcap/ts-builder": "15.
|
|
141
|
-
"@genesislcap/uvu-playwright-builder": "15.
|
|
142
|
-
"@genesislcap/vite-builder": "15.
|
|
143
|
-
"@genesislcap/webpack-builder": "15.
|
|
137
|
+
"@genesislcap/foundation-testing": "15.23.0",
|
|
138
|
+
"@genesislcap/genx": "15.23.0",
|
|
139
|
+
"@genesislcap/rollup-builder": "15.23.0",
|
|
140
|
+
"@genesislcap/ts-builder": "15.23.0",
|
|
141
|
+
"@genesislcap/uvu-playwright-builder": "15.23.0",
|
|
142
|
+
"@genesislcap/vite-builder": "15.23.0",
|
|
143
|
+
"@genesislcap/webpack-builder": "15.23.0"
|
|
144
144
|
},
|
|
145
145
|
"dependencies": {
|
|
146
|
-
"@genesislcap/foundation-comms": "15.
|
|
147
|
-
"@genesislcap/foundation-events": "15.
|
|
148
|
-
"@genesislcap/foundation-i18n": "15.
|
|
149
|
-
"@genesislcap/foundation-logger": "15.
|
|
150
|
-
"@genesislcap/foundation-state-machine": "15.
|
|
151
|
-
"@genesislcap/foundation-store": "15.
|
|
152
|
-
"@genesislcap/foundation-ui": "15.
|
|
153
|
-
"@genesislcap/foundation-user": "15.
|
|
154
|
-
"@genesislcap/foundation-utils": "15.
|
|
155
|
-
"@genesislcap/foundation-zero": "15.
|
|
146
|
+
"@genesislcap/foundation-comms": "15.23.0",
|
|
147
|
+
"@genesislcap/foundation-events": "15.23.0",
|
|
148
|
+
"@genesislcap/foundation-i18n": "15.23.0",
|
|
149
|
+
"@genesislcap/foundation-logger": "15.23.0",
|
|
150
|
+
"@genesislcap/foundation-state-machine": "15.23.0",
|
|
151
|
+
"@genesislcap/foundation-store": "15.23.0",
|
|
152
|
+
"@genesislcap/foundation-ui": "15.23.0",
|
|
153
|
+
"@genesislcap/foundation-user": "15.23.0",
|
|
154
|
+
"@genesislcap/foundation-utils": "15.23.0",
|
|
155
|
+
"@genesislcap/foundation-zero": "15.23.0",
|
|
156
156
|
"@microsoft/fast-components": "2.30.6",
|
|
157
157
|
"@microsoft/fast-element": "1.14.0",
|
|
158
158
|
"@microsoft/fast-foundation": "2.50.0",
|