@conduction/nextcloud-vue 1.0.0-beta.21 → 1.0.0-beta.22
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/nextcloud-vue.cjs.js +404 -38
- package/dist/nextcloud-vue.cjs.js.map +1 -1
- package/dist/nextcloud-vue.esm.js +403 -39
- package/dist/nextcloud-vue.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/composables/useAppManifest.js +37 -9
- package/src/index.js +1 -0
- package/src/utils/resolveManifestSentinels.js +268 -0
- package/src/utils/validateManifest.js +70 -2
|
@@ -43033,7 +43033,7 @@ var isObject = function isObject(obj) {
|
|
|
43033
43033
|
}; // Strict object type check
|
|
43034
43034
|
// Only returns true for plain JavaScript objects
|
|
43035
43035
|
|
|
43036
|
-
var isPlainObject$
|
|
43036
|
+
var isPlainObject$3 = function isPlainObject(obj) {
|
|
43037
43037
|
return Object.prototype.toString.call(obj) === '[object Object]';
|
|
43038
43038
|
};
|
|
43039
43039
|
var isDate = function isDate(value) {
|
|
@@ -43115,7 +43115,7 @@ var cloneDeep = function cloneDeep(obj) {
|
|
|
43115
43115
|
}, []);
|
|
43116
43116
|
}
|
|
43117
43117
|
|
|
43118
|
-
if (isPlainObject$
|
|
43118
|
+
if (isPlainObject$3(obj)) {
|
|
43119
43119
|
return keys(obj).reduce(function (result, key) {
|
|
43120
43120
|
return _objectSpread$8(_objectSpread$8({}, result), {}, _defineProperty$b({}, key, cloneDeep(obj[key], obj[key])));
|
|
43121
43121
|
}, {});
|
|
@@ -43323,7 +43323,7 @@ var kebabCase = function kebabCase(str) {
|
|
|
43323
43323
|
|
|
43324
43324
|
var toString = function toString(val) {
|
|
43325
43325
|
var spaces = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2;
|
|
43326
|
-
return isUndefinedOrNull(val) ? '' : isArray(val) || isPlainObject$
|
|
43326
|
+
return isUndefinedOrNull(val) ? '' : isArray(val) || isPlainObject$3(val) && val.toString === Object.prototype.toString ? JSON.stringify(val, null, spaces) : String(val);
|
|
43327
43327
|
}; // Remove leading white space from a string
|
|
43328
43328
|
|
|
43329
43329
|
var ELEMENT_PROTO = Element$1.prototype;
|
|
@@ -43591,7 +43591,7 @@ var BVTransition = /*#__PURE__*/extend({
|
|
|
43591
43591
|
props = _ref.props;
|
|
43592
43592
|
var transProps = props.transProps;
|
|
43593
43593
|
|
|
43594
|
-
if (!isPlainObject$
|
|
43594
|
+
if (!isPlainObject$3(transProps)) {
|
|
43595
43595
|
transProps = props.noFade ? NO_FADE_PROPS : FADE_PROPS;
|
|
43596
43596
|
|
|
43597
43597
|
if (props.appear) {
|
|
@@ -43652,7 +43652,7 @@ var encode = function encode(str) {
|
|
|
43652
43652
|
// See: https://github.com/vuejs/vue-router/blob/dev/src/util/query.js
|
|
43653
43653
|
|
|
43654
43654
|
var stringifyQueryObj = function stringifyQueryObj(obj) {
|
|
43655
|
-
if (!isPlainObject$
|
|
43655
|
+
if (!isPlainObject$3(obj)) {
|
|
43656
43656
|
return '';
|
|
43657
43657
|
}
|
|
43658
43658
|
|
|
@@ -43743,7 +43743,7 @@ var computeHref = function computeHref() {
|
|
|
43743
43743
|
} // Fallback to `to.path' + `to.query` + `to.hash` prop (if `to` is an object)
|
|
43744
43744
|
|
|
43745
43745
|
|
|
43746
|
-
if (isPlainObject$
|
|
43746
|
+
if (isPlainObject$3(to) && (to.path || to.query || to.hash)) {
|
|
43747
43747
|
var path = toString(to.path);
|
|
43748
43748
|
var query = stringifyQueryObj(to.query);
|
|
43749
43749
|
var hash = toString(to.hash);
|
|
@@ -55925,6 +55925,29 @@ function useContextMenu() {
|
|
|
55925
55925
|
}
|
|
55926
55926
|
}
|
|
55927
55927
|
|
|
55928
|
+
/**
|
|
55929
|
+
* Pattern matching the `manifest-resolve-sentinel` capability's
|
|
55930
|
+
* sentinel — `@resolve:<key>` where `<key>` is lowercase alphanumeric
|
|
55931
|
+
* with `_` / `-` separators. The full string IS the sentinel; partial
|
|
55932
|
+
* substitution like `prefix-@resolve:foo` is NOT supported and is left
|
|
55933
|
+
* as a plain string for downstream renderers.
|
|
55934
|
+
*
|
|
55935
|
+
* Build-time validation accepts this pattern as a valid `string` for
|
|
55936
|
+
* any `string`-typed field UNDER `pages[].config`, regardless of any
|
|
55937
|
+
* narrower per-field constraint. Other paths reject it explicitly.
|
|
55938
|
+
*/
|
|
55939
|
+
const SENTINEL_PATTERN$1 = /^@resolve:[a-z][a-z0-9_-]*$/;
|
|
55940
|
+
|
|
55941
|
+
/**
|
|
55942
|
+
* Test whether a string is a manifest `@resolve:` sentinel.
|
|
55943
|
+
*
|
|
55944
|
+
* @param {*} value Candidate value.
|
|
55945
|
+
* @return {boolean} True when the value is a fully-matched sentinel.
|
|
55946
|
+
*/
|
|
55947
|
+
function isSentinel(value) {
|
|
55948
|
+
return typeof value === 'string' && SENTINEL_PATTERN$1.test(value)
|
|
55949
|
+
}
|
|
55950
|
+
|
|
55928
55951
|
/**
|
|
55929
55952
|
* Validate a manifest object against the manifest JSON Schema.
|
|
55930
55953
|
*
|
|
@@ -55942,6 +55965,13 @@ function useContextMenu() {
|
|
|
55942
55965
|
* - `pages[].component` is required when `type` is "custom".
|
|
55943
55966
|
* - Per-type `config` shape rules for the built-in types `logs`,
|
|
55944
55967
|
* `settings`, `chat`, `files` (REQ from manifest-page-type-extensions).
|
|
55968
|
+
* - The `manifest-resolve-sentinel` sentinel `@resolve:<key>` is
|
|
55969
|
+
* permissively accepted under `pages[].config.*` and explicitly
|
|
55970
|
+
* REJECTED in `version`, `dependencies[]`, `menu[].route`,
|
|
55971
|
+
* `menu[].id`, `pages[].id`, `pages[].route`, `pages[].component`,
|
|
55972
|
+
* `pages[].headerComponent`, `pages[].actionsComponent`,
|
|
55973
|
+
* `pages[].slots.*` — those are router invariants or registry
|
|
55974
|
+
* keys.
|
|
55945
55975
|
*
|
|
55946
55976
|
* The richer schema constraints (`additionalProperties: false`, `format`
|
|
55947
55977
|
* URI, etc.) are enforced by the BE / hydra CI validators that consume
|
|
@@ -55961,7 +55991,7 @@ function useContextMenu() {
|
|
|
55961
55991
|
function validateManifest(manifest, options = {}) {
|
|
55962
55992
|
const errors = [];
|
|
55963
55993
|
|
|
55964
|
-
if (!isPlainObject$
|
|
55994
|
+
if (!isPlainObject$2(manifest)) {
|
|
55965
55995
|
return { valid: false, errors: ['manifest must be an object'] }
|
|
55966
55996
|
}
|
|
55967
55997
|
|
|
@@ -55969,6 +55999,10 @@ function validateManifest(manifest, options = {}) {
|
|
|
55969
55999
|
|
|
55970
56000
|
if (typeof manifest.version !== 'string') {
|
|
55971
56001
|
errors.push('/version must be a string');
|
|
56002
|
+
} else if (isSentinel(manifest.version)) {
|
|
56003
|
+
// `manifest-resolve-sentinel` REQ-MRS-004: sentinel is a router /
|
|
56004
|
+
// registry invariant violation when used here.
|
|
56005
|
+
errors.push(`/version "${manifest.version}" must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
|
|
55972
56006
|
} else if (!versionPattern.test(manifest.version)) {
|
|
55973
56007
|
errors.push(`/version "${manifest.version}" must match semver pattern`);
|
|
55974
56008
|
}
|
|
@@ -55977,12 +56011,19 @@ function validateManifest(manifest, options = {}) {
|
|
|
55977
56011
|
errors.push('/menu must be an array');
|
|
55978
56012
|
} else {
|
|
55979
56013
|
manifest.menu.forEach((item, index) => {
|
|
55980
|
-
if (!isPlainObject$
|
|
56014
|
+
if (!isPlainObject$2(item)) {
|
|
55981
56015
|
errors.push(`/menu/${index} must be an object`);
|
|
55982
56016
|
return
|
|
55983
56017
|
}
|
|
55984
|
-
if (typeof item.id !== 'string')
|
|
56018
|
+
if (typeof item.id !== 'string') {
|
|
56019
|
+
errors.push(`/menu/${index}/id must be a string`);
|
|
56020
|
+
} else if (isSentinel(item.id)) {
|
|
56021
|
+
errors.push(`/menu/${index}/id must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
|
|
56022
|
+
}
|
|
55985
56023
|
if (typeof item.label !== 'string') errors.push(`/menu/${index}/label must be a string`);
|
|
56024
|
+
if (item.route !== undefined && isSentinel(item.route)) {
|
|
56025
|
+
errors.push(`/menu/${index}/route must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
|
|
56026
|
+
}
|
|
55986
56027
|
if (item.children !== undefined && !Array.isArray(item.children)) {
|
|
55987
56028
|
errors.push(`/menu/${index}/children must be an array`);
|
|
55988
56029
|
}
|
|
@@ -55998,18 +56039,24 @@ function validateManifest(manifest, options = {}) {
|
|
|
55998
56039
|
} else {
|
|
55999
56040
|
const seenIds = new Set();
|
|
56000
56041
|
manifest.pages.forEach((page, index) => {
|
|
56001
|
-
if (!isPlainObject$
|
|
56042
|
+
if (!isPlainObject$2(page)) {
|
|
56002
56043
|
errors.push(`/pages/${index} must be an object`);
|
|
56003
56044
|
return
|
|
56004
56045
|
}
|
|
56005
56046
|
if (typeof page.id !== 'string') {
|
|
56006
56047
|
errors.push(`/pages/${index}/id must be a string`);
|
|
56048
|
+
} else if (isSentinel(page.id)) {
|
|
56049
|
+
errors.push(`/pages/${index}/id must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
|
|
56007
56050
|
} else if (seenIds.has(page.id)) {
|
|
56008
56051
|
errors.push(`/pages/${index}/id "${page.id}" must be unique within pages[]`);
|
|
56009
56052
|
} else {
|
|
56010
56053
|
seenIds.add(page.id);
|
|
56011
56054
|
}
|
|
56012
|
-
if (typeof page.route !== 'string')
|
|
56055
|
+
if (typeof page.route !== 'string') {
|
|
56056
|
+
errors.push(`/pages/${index}/route must be a string`);
|
|
56057
|
+
} else if (isSentinel(page.route)) {
|
|
56058
|
+
errors.push(`/pages/${index}/route must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
|
|
56059
|
+
}
|
|
56013
56060
|
if (typeof page.title !== 'string') errors.push(`/pages/${index}/title must be a string`);
|
|
56014
56061
|
if (typeof page.type !== 'string' || page.type.length === 0) {
|
|
56015
56062
|
errors.push(`/pages/${index}/type must be a non-empty string`);
|
|
@@ -56019,6 +56066,25 @@ function validateManifest(manifest, options = {}) {
|
|
|
56019
56066
|
if (page.type === 'custom' && typeof page.component !== 'string') {
|
|
56020
56067
|
errors.push(`/pages/${index}/component is required when type is "custom"`);
|
|
56021
56068
|
}
|
|
56069
|
+
// `manifest-resolve-sentinel` REQ-MRS-004: registry-key
|
|
56070
|
+
// fields cannot be dynamic — they resolve at module-load
|
|
56071
|
+
// time against `customComponents`, before the loader runs.
|
|
56072
|
+
if (page.component !== undefined && isSentinel(page.component)) {
|
|
56073
|
+
errors.push(`/pages/${index}/component must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
|
|
56074
|
+
}
|
|
56075
|
+
if (page.headerComponent !== undefined && isSentinel(page.headerComponent)) {
|
|
56076
|
+
errors.push(`/pages/${index}/headerComponent must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
|
|
56077
|
+
}
|
|
56078
|
+
if (page.actionsComponent !== undefined && isSentinel(page.actionsComponent)) {
|
|
56079
|
+
errors.push(`/pages/${index}/actionsComponent must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
|
|
56080
|
+
}
|
|
56081
|
+
if (isPlainObject$2(page.slots)) {
|
|
56082
|
+
for (const [slotName, slotValue] of Object.entries(page.slots)) {
|
|
56083
|
+
if (isSentinel(slotValue)) {
|
|
56084
|
+
errors.push(`/pages/${index}/slots/${slotName} must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
|
|
56085
|
+
}
|
|
56086
|
+
}
|
|
56087
|
+
}
|
|
56022
56088
|
|
|
56023
56089
|
// Per-type config-shape validation for built-in extended types.
|
|
56024
56090
|
// (`manifest-page-type-extensions` spec — covers logs/settings/chat/files.)
|
|
@@ -56042,6 +56108,8 @@ function validateManifest(manifest, options = {}) {
|
|
|
56042
56108
|
manifest.dependencies.forEach((dep, index) => {
|
|
56043
56109
|
if (typeof dep !== 'string') {
|
|
56044
56110
|
errors.push(`/dependencies/${index} must be a string`);
|
|
56111
|
+
} else if (isSentinel(dep)) {
|
|
56112
|
+
errors.push(`/dependencies/${index} must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
|
|
56045
56113
|
}
|
|
56046
56114
|
});
|
|
56047
56115
|
}
|
|
@@ -56050,7 +56118,7 @@ function validateManifest(manifest, options = {}) {
|
|
|
56050
56118
|
return { valid: errors.length === 0, errors }
|
|
56051
56119
|
}
|
|
56052
56120
|
|
|
56053
|
-
function isPlainObject$
|
|
56121
|
+
function isPlainObject$2(value) {
|
|
56054
56122
|
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
56055
56123
|
}
|
|
56056
56124
|
|
|
@@ -56073,7 +56141,7 @@ function isPlainObject$1(value) {
|
|
|
56073
56141
|
*/
|
|
56074
56142
|
function validateTypeConfig(page, index, errors) {
|
|
56075
56143
|
if (!page || typeof page.type !== 'string') return
|
|
56076
|
-
const cfg = isPlainObject$
|
|
56144
|
+
const cfg = isPlainObject$2(page.config) ? page.config : null;
|
|
56077
56145
|
const pathBracket = `pages[${index}].config`;
|
|
56078
56146
|
const pathSlash = `/pages/${index}/config`;
|
|
56079
56147
|
|
|
@@ -56127,7 +56195,7 @@ function validateTypeConfig(page, index, errors) {
|
|
|
56127
56195
|
}
|
|
56128
56196
|
const seenTabIds = Object.create(null);
|
|
56129
56197
|
cfg.tabs.forEach((tab, tIndex) => {
|
|
56130
|
-
if (!isPlainObject$
|
|
56198
|
+
if (!isPlainObject$2(tab)) {
|
|
56131
56199
|
errors.push(`${pathSlash}/tabs/${tIndex}: must be an object`);
|
|
56132
56200
|
return
|
|
56133
56201
|
}
|
|
@@ -56270,12 +56338,12 @@ function validateTypeConfig(page, index, errors) {
|
|
|
56270
56338
|
*/
|
|
56271
56339
|
function validateSidebarConfig(page, pageIndex, errors) {
|
|
56272
56340
|
const config = page.config;
|
|
56273
|
-
if (!isPlainObject$
|
|
56341
|
+
if (!isPlainObject$2(config)) return
|
|
56274
56342
|
|
|
56275
56343
|
// --- Index sidebar ---
|
|
56276
56344
|
if (page.type === 'index' && config.sidebar !== undefined) {
|
|
56277
56345
|
const path = `/pages/${pageIndex}/config/sidebar`;
|
|
56278
|
-
if (!isPlainObject$
|
|
56346
|
+
if (!isPlainObject$2(config.sidebar)) {
|
|
56279
56347
|
errors.push(`${path} must be an object`);
|
|
56280
56348
|
} else {
|
|
56281
56349
|
if (config.sidebar.enabled !== undefined && typeof config.sidebar.enabled !== 'boolean') {
|
|
@@ -56288,13 +56356,13 @@ function validateSidebarConfig(page, pageIndex, errors) {
|
|
|
56288
56356
|
if (config.sidebar.columnGroups !== undefined && !Array.isArray(config.sidebar.columnGroups)) {
|
|
56289
56357
|
errors.push(`${path}/columnGroups must be an array`);
|
|
56290
56358
|
}
|
|
56291
|
-
if (config.sidebar.facets !== undefined && !isPlainObject$
|
|
56359
|
+
if (config.sidebar.facets !== undefined && !isPlainObject$2(config.sidebar.facets)) {
|
|
56292
56360
|
errors.push(`${path}/facets must be an object`);
|
|
56293
56361
|
}
|
|
56294
56362
|
if (config.sidebar.showMetadata !== undefined && typeof config.sidebar.showMetadata !== 'boolean') {
|
|
56295
56363
|
errors.push(`${path}/showMetadata must be a boolean`);
|
|
56296
56364
|
}
|
|
56297
|
-
if (config.sidebar.search !== undefined && !isPlainObject$
|
|
56365
|
+
if (config.sidebar.search !== undefined && !isPlainObject$2(config.sidebar.search)) {
|
|
56298
56366
|
errors.push(`${path}/search must be an object`);
|
|
56299
56367
|
}
|
|
56300
56368
|
}
|
|
@@ -56309,7 +56377,7 @@ function validateSidebarConfig(page, pageIndex, errors) {
|
|
|
56309
56377
|
const path = `/pages/${pageIndex}/config/sidebar`;
|
|
56310
56378
|
const sb = config.sidebar;
|
|
56311
56379
|
const isBool = typeof sb === 'boolean';
|
|
56312
|
-
const isObj = isPlainObject$
|
|
56380
|
+
const isObj = isPlainObject$2(sb);
|
|
56313
56381
|
if (!isBool && !isObj) {
|
|
56314
56382
|
errors.push(`${path} must be a boolean (legacy) or object`);
|
|
56315
56383
|
} else if (isObj) {
|
|
@@ -56349,7 +56417,7 @@ function validateSidebarConfig(page, pageIndex, errors) {
|
|
|
56349
56417
|
}
|
|
56350
56418
|
|
|
56351
56419
|
// --- Detail sidebar tabs (legacy sidebarProps.tabs path) ---
|
|
56352
|
-
if (page.type === 'detail' && isPlainObject$
|
|
56420
|
+
if (page.type === 'detail' && isPlainObject$2(config.sidebarProps) && config.sidebarProps.tabs !== undefined) {
|
|
56353
56421
|
const tabsPath = `/pages/${pageIndex}/config/sidebarProps/tabs`;
|
|
56354
56422
|
validateDetailTabsArray(config.sidebarProps.tabs, tabsPath, errors);
|
|
56355
56423
|
}
|
|
@@ -56373,7 +56441,7 @@ function validateDetailTabsArray(tabs, tabsPath, errors) {
|
|
|
56373
56441
|
const seenIds = new Set();
|
|
56374
56442
|
tabs.forEach((tab, tabIndex) => {
|
|
56375
56443
|
const tabPath = `${tabsPath}/${tabIndex}`;
|
|
56376
|
-
if (!isPlainObject$
|
|
56444
|
+
if (!isPlainObject$2(tab)) {
|
|
56377
56445
|
errors.push(`${tabPath} must be an object`);
|
|
56378
56446
|
return
|
|
56379
56447
|
}
|
|
@@ -56424,7 +56492,7 @@ function validateDetailTabsArray(tabs, tabsPath, errors) {
|
|
|
56424
56492
|
function validatePageSidebar(page, pageIndex, errors) {
|
|
56425
56493
|
if (page.sidebar === undefined) return
|
|
56426
56494
|
const path = `/pages/${pageIndex}/sidebar`;
|
|
56427
|
-
if (!isPlainObject$
|
|
56495
|
+
if (!isPlainObject$2(page.sidebar)) {
|
|
56428
56496
|
errors.push(`${path} must be an object`);
|
|
56429
56497
|
return
|
|
56430
56498
|
}
|
|
@@ -56462,7 +56530,7 @@ function validateColumnsArray(cfg, pathSlash, pathBracket, errors) {
|
|
|
56462
56530
|
// Legacy shorthand — accepted as-is.
|
|
56463
56531
|
return
|
|
56464
56532
|
}
|
|
56465
|
-
if (!isPlainObject$
|
|
56533
|
+
if (!isPlainObject$2(col)) {
|
|
56466
56534
|
errors.push(`${colPath}: must be a string (legacy shorthand) or object`);
|
|
56467
56535
|
return
|
|
56468
56536
|
}
|
|
@@ -56494,7 +56562,7 @@ function validateActionsArray(cfg, pathSlash, pathBracket, errors) {
|
|
|
56494
56562
|
}
|
|
56495
56563
|
cfg.actions.forEach((action, aIndex) => {
|
|
56496
56564
|
const actionPath = `${pathSlash}/actions/${aIndex}`;
|
|
56497
|
-
if (!isPlainObject$
|
|
56565
|
+
if (!isPlainObject$2(action)) {
|
|
56498
56566
|
errors.push(`${actionPath}: must be an object`);
|
|
56499
56567
|
return
|
|
56500
56568
|
}
|
|
@@ -56551,7 +56619,7 @@ function validateWidgetsArray(cfg, pathSlash, pathBracket, errors) {
|
|
|
56551
56619
|
}
|
|
56552
56620
|
cfg.widgets.forEach((widget, wIndex) => {
|
|
56553
56621
|
const widgetPath = `${pathSlash}/widgets/${wIndex}`;
|
|
56554
|
-
if (!isPlainObject$
|
|
56622
|
+
if (!isPlainObject$2(widget)) {
|
|
56555
56623
|
errors.push(`${widgetPath}: must be an object`);
|
|
56556
56624
|
return
|
|
56557
56625
|
}
|
|
@@ -56586,7 +56654,7 @@ function validateLayoutArray(cfg, pathSlash, pathBracket, errors) {
|
|
|
56586
56654
|
}
|
|
56587
56655
|
cfg.layout.forEach((item, lIndex) => {
|
|
56588
56656
|
const layoutPath = `${pathSlash}/layout/${lIndex}`;
|
|
56589
|
-
if (!isPlainObject$
|
|
56657
|
+
if (!isPlainObject$2(item)) {
|
|
56590
56658
|
errors.push(`${layoutPath}: must be an object`);
|
|
56591
56659
|
return
|
|
56592
56660
|
}
|
|
@@ -56627,7 +56695,7 @@ function validateLayoutArray(cfg, pathSlash, pathBracket, errors) {
|
|
|
56627
56695
|
* @param {string[]} errors Accumulator
|
|
56628
56696
|
*/
|
|
56629
56697
|
function validateSettingsSection(section, pathSlash, pathBracket, errors) {
|
|
56630
|
-
if (!isPlainObject$
|
|
56698
|
+
if (!isPlainObject$2(section)) {
|
|
56631
56699
|
errors.push(`${pathSlash}: must be an object`);
|
|
56632
56700
|
return
|
|
56633
56701
|
}
|
|
@@ -56659,7 +56727,7 @@ function validateSettingsSection(section, pathSlash, pathBracket, errors) {
|
|
|
56659
56727
|
// Per-widget shape rules.
|
|
56660
56728
|
if (hasWidgets) {
|
|
56661
56729
|
section.widgets.forEach((widget, wIndex) => {
|
|
56662
|
-
if (!isPlainObject$
|
|
56730
|
+
if (!isPlainObject$2(widget)) {
|
|
56663
56731
|
errors.push(`${pathSlash}/widgets/${wIndex}: must be an object`);
|
|
56664
56732
|
return
|
|
56665
56733
|
}
|
|
@@ -56702,7 +56770,7 @@ function validateFieldsArray(fields, fieldsPath, errors) {
|
|
|
56702
56770
|
if (!Array.isArray(fields)) return
|
|
56703
56771
|
fields.forEach((field, fIndex) => {
|
|
56704
56772
|
const fieldPath = `${fieldsPath}/${fIndex}`;
|
|
56705
|
-
if (!isPlainObject$
|
|
56773
|
+
if (!isPlainObject$2(field)) {
|
|
56706
56774
|
errors.push(`${fieldPath}: must be an object`);
|
|
56707
56775
|
return
|
|
56708
56776
|
}
|
|
@@ -56721,17 +56789,292 @@ function validateFieldsArray(fields, fieldsPath, errors) {
|
|
|
56721
56789
|
}
|
|
56722
56790
|
|
|
56723
56791
|
/**
|
|
56724
|
-
*
|
|
56792
|
+
* Manifest `@resolve:` sentinel resolver.
|
|
56793
|
+
*
|
|
56794
|
+
* Implements the `manifest-resolve-sentinel` capability: walks the
|
|
56795
|
+
* `pages[].config` subtrees of an assembled manifest and replaces every
|
|
56796
|
+
* fully-matched `@resolve:<key>` string with the result of the consuming
|
|
56797
|
+
* app's `IAppConfig` lookup for `<key>`. Other manifest paths
|
|
56798
|
+
* (`route`, `id`, top-level fields, `menu[]`, `pages[].component` etc.)
|
|
56799
|
+
* are intentionally untouched — sentinels there are router / registry
|
|
56800
|
+
* invariants and the schema validator rejects them.
|
|
56801
|
+
*
|
|
56802
|
+
* Resolution source (canonical, per spec):
|
|
56725
56803
|
*
|
|
56726
|
-
*
|
|
56727
|
-
*
|
|
56804
|
+
* 1. `@nextcloud/initial-state` provisioned key
|
|
56805
|
+
* `app-{appId}-{key}` — zero-network, preferred.
|
|
56806
|
+
* 2. Runtime `GET /index.php/apps/{appId}/api/configs/{key}` — falls
|
|
56807
|
+
* through silently on 4xx / network error.
|
|
56808
|
+
* 3. `null` — unresolved.
|
|
56809
|
+
*
|
|
56810
|
+
* Empty-state semantics: an unset / empty value substitutes `null` (not
|
|
56811
|
+
* empty string) and is accumulated into the returned `unresolved` array
|
|
56812
|
+
* so consumers can render an admin warning. A `console.warn` is emitted
|
|
56813
|
+
* once per unresolved key with the offending sentinel.
|
|
56814
|
+
*
|
|
56815
|
+
* The resolver is intentionally split into a synchronous walk + an
|
|
56816
|
+
* asynchronous batch resolution: every sentinel is collected first, then
|
|
56817
|
+
* each unique `(appId, key)` is resolved exactly once (initial-state
|
|
56818
|
+
* lookup is synchronous; runtime fetch is per-key cached for the page
|
|
56819
|
+
* lifetime), and finally the manifest is rewritten in a second walk
|
|
56820
|
+
* with the resolved values. This makes the substitution deterministic
|
|
56821
|
+
* — five `@resolve:foo` references in five pages share one fetch.
|
|
56822
|
+
*
|
|
56823
|
+
* @module utils/resolveManifestSentinels
|
|
56824
|
+
*/
|
|
56825
|
+
|
|
56826
|
+
const SENTINEL_PATTERN = /^@resolve:([a-z][a-z0-9_-]*)$/;
|
|
56827
|
+
|
|
56828
|
+
/**
|
|
56829
|
+
* Process-wide cache of resolved IAppConfig values, keyed by
|
|
56830
|
+
* `${appId}::${key}`. Cleared via `clearResolveCache()` (test-only).
|
|
56831
|
+
*
|
|
56832
|
+
* @type {Map<string, Promise<*>>}
|
|
56833
|
+
*/
|
|
56834
|
+
const resolveCache = new Map();
|
|
56835
|
+
|
|
56836
|
+
/**
|
|
56837
|
+
* Test-only helper to reset the per-page resolve cache between runs.
|
|
56838
|
+
* Production callers do not need this — the cache is page-lifetime by
|
|
56839
|
+
* design, consistent with the manifest's load-once model.
|
|
56840
|
+
*
|
|
56841
|
+
* @return {void}
|
|
56842
|
+
*/
|
|
56843
|
+
function clearResolveCache() {
|
|
56844
|
+
resolveCache.clear();
|
|
56845
|
+
}
|
|
56846
|
+
|
|
56847
|
+
/**
|
|
56848
|
+
* Walk the manifest's `pages[].config` subtrees and replace every
|
|
56849
|
+
* `@resolve:<key>` sentinel with the resolved IAppConfig value.
|
|
56850
|
+
*
|
|
56851
|
+
* Returns a Promise resolving to `{ manifest, unresolved }`:
|
|
56852
|
+
* - `manifest` — a NEW manifest object with sentinels substituted; the
|
|
56853
|
+
* input is NOT mutated.
|
|
56854
|
+
* - `unresolved` — array of IAppConfig keys whose sentinels resolved
|
|
56855
|
+
* to `null` (unset / empty / fetch failure). Useful for surfacing
|
|
56856
|
+
* "n settings unconfigured" admin warnings.
|
|
56857
|
+
*
|
|
56858
|
+
* Sentinels OUTSIDE `pages[].config` are left intact; the schema
|
|
56859
|
+
* validator rejects them downstream so consumers see a clear error
|
|
56860
|
+
* rather than a silent substitution that breaks routing or registry
|
|
56861
|
+
* lookups.
|
|
56862
|
+
*
|
|
56863
|
+
* @param {object} manifest The merged (bundled + backend) manifest.
|
|
56864
|
+
* Walked but not mutated.
|
|
56865
|
+
* @param {string} appId Nextcloud app ID. Used to scope the
|
|
56866
|
+
* IAppConfig lookup namespace.
|
|
56867
|
+
* @param {object} [options] Resolver overrides.
|
|
56868
|
+
* @param {Function} [options.getAppConfigValue] Async (appId, key) =>
|
|
56869
|
+
* value resolver. Override for tests; defaults to the
|
|
56870
|
+
* initial-state-then-fetch chain documented above.
|
|
56871
|
+
* @param {Function} [options.warn] Override for `console.warn`. Used in
|
|
56872
|
+
* tests to capture warning calls without polluting test output.
|
|
56873
|
+
* @return {Promise<{ manifest: object, unresolved: string[] }>}
|
|
56874
|
+
*/
|
|
56875
|
+
async function resolveManifestSentinels(manifest, appId, options = {}) {
|
|
56876
|
+
if (!isPlainObject$1(manifest)) {
|
|
56877
|
+
return { manifest, unresolved: [] }
|
|
56878
|
+
}
|
|
56879
|
+
|
|
56880
|
+
const getAppConfigValue = options.getAppConfigValue ?? defaultGetAppConfigValue;
|
|
56881
|
+
const warn = options.warn ?? ((...args) => {
|
|
56882
|
+
// eslint-disable-next-line no-console
|
|
56883
|
+
console.warn(...args);
|
|
56884
|
+
});
|
|
56885
|
+
|
|
56886
|
+
// Phase 1: scan for sentinels under pages[].config only. We only
|
|
56887
|
+
// need the unique key set — the second walk does the substitution.
|
|
56888
|
+
const keys = new Set();
|
|
56889
|
+
const pages = Array.isArray(manifest.pages) ? manifest.pages : [];
|
|
56890
|
+
for (const page of pages) {
|
|
56891
|
+
if (!isPlainObject$1(page) || !isPlainObject$1(page.config)) continue
|
|
56892
|
+
collectSentinelKeys(page.config, keys);
|
|
56893
|
+
}
|
|
56894
|
+
|
|
56895
|
+
if (keys.size === 0) {
|
|
56896
|
+
return { manifest, unresolved: [] }
|
|
56897
|
+
}
|
|
56898
|
+
|
|
56899
|
+
// Phase 2: resolve each unique (appId, key) exactly once.
|
|
56900
|
+
const resolved = new Map();
|
|
56901
|
+
const unresolved = [];
|
|
56902
|
+
await Promise.all(Array.from(keys).map(async (key) => {
|
|
56903
|
+
const value = await getAppConfigValue(appId, key);
|
|
56904
|
+
if (value === undefined || value === null || value === '') {
|
|
56905
|
+
resolved.set(key, null);
|
|
56906
|
+
unresolved.push(key);
|
|
56907
|
+
warn(`[resolveManifestSentinels] Manifest sentinel '@resolve:${key}' resolved to null (key unset)`);
|
|
56908
|
+
} else {
|
|
56909
|
+
resolved.set(key, value);
|
|
56910
|
+
}
|
|
56911
|
+
}));
|
|
56912
|
+
|
|
56913
|
+
// Phase 3: rebuild the manifest immutably, substituting sentinels in
|
|
56914
|
+
// pages[].config only. Other fields are passed through by reference.
|
|
56915
|
+
const out = { ...manifest };
|
|
56916
|
+
out.pages = pages.map((page) => {
|
|
56917
|
+
if (!isPlainObject$1(page) || !isPlainObject$1(page.config)) return page
|
|
56918
|
+
return { ...page, config: substituteInTree(page.config, resolved) }
|
|
56919
|
+
});
|
|
56920
|
+
|
|
56921
|
+
return { manifest: out, unresolved }
|
|
56922
|
+
}
|
|
56923
|
+
|
|
56924
|
+
/**
|
|
56925
|
+
* Recursively scan a tree, accumulating every sentinel key into the
|
|
56926
|
+
* provided `keys` Set. Plain objects + arrays are descended; primitive
|
|
56927
|
+
* leaves are checked against the sentinel pattern.
|
|
56928
|
+
*
|
|
56929
|
+
* @param {*} node Current tree node (object, array, or primitive).
|
|
56930
|
+
* @param {Set<string>} keys Accumulator for unique sentinel keys.
|
|
56931
|
+
* @return {void}
|
|
56932
|
+
*/
|
|
56933
|
+
function collectSentinelKeys(node, keys) {
|
|
56934
|
+
if (typeof node === 'string') {
|
|
56935
|
+
const match = node.match(SENTINEL_PATTERN);
|
|
56936
|
+
if (match) keys.add(match[1]);
|
|
56937
|
+
return
|
|
56938
|
+
}
|
|
56939
|
+
if (Array.isArray(node)) {
|
|
56940
|
+
for (const item of node) collectSentinelKeys(item, keys);
|
|
56941
|
+
return
|
|
56942
|
+
}
|
|
56943
|
+
if (isPlainObject$1(node)) {
|
|
56944
|
+
for (const value of Object.values(node)) collectSentinelKeys(value, keys);
|
|
56945
|
+
}
|
|
56946
|
+
}
|
|
56947
|
+
|
|
56948
|
+
/**
|
|
56949
|
+
* Recursively rebuild a tree, replacing each fully-matched sentinel
|
|
56950
|
+
* with its resolved value. Returns a NEW tree; input is unchanged.
|
|
56951
|
+
*
|
|
56952
|
+
* @param {*} node Current tree node.
|
|
56953
|
+
* @param {Map<string,*>} resolved Map of key → resolved value (or null).
|
|
56954
|
+
* @return {*} New tree with sentinels substituted.
|
|
56955
|
+
*/
|
|
56956
|
+
function substituteInTree(node, resolved) {
|
|
56957
|
+
if (typeof node === 'string') {
|
|
56958
|
+
const match = node.match(SENTINEL_PATTERN);
|
|
56959
|
+
if (match && resolved.has(match[1])) {
|
|
56960
|
+
return resolved.get(match[1])
|
|
56961
|
+
}
|
|
56962
|
+
return node
|
|
56963
|
+
}
|
|
56964
|
+
if (Array.isArray(node)) {
|
|
56965
|
+
return node.map((item) => substituteInTree(item, resolved))
|
|
56966
|
+
}
|
|
56967
|
+
if (isPlainObject$1(node)) {
|
|
56968
|
+
const out = {};
|
|
56969
|
+
for (const [key, value] of Object.entries(node)) {
|
|
56970
|
+
out[key] = substituteInTree(value, resolved);
|
|
56971
|
+
}
|
|
56972
|
+
return out
|
|
56973
|
+
}
|
|
56974
|
+
return node
|
|
56975
|
+
}
|
|
56976
|
+
|
|
56977
|
+
/**
|
|
56978
|
+
* Default `getAppConfigValue` implementation: consult
|
|
56979
|
+
* `@nextcloud/initial-state` first (zero-network), fall back to a
|
|
56980
|
+
* runtime fetch with per-(appId, key) caching for the page lifetime.
|
|
56981
|
+
*
|
|
56982
|
+
* Returns `null` when neither source resolves a value. Network / 4xx
|
|
56983
|
+
* errors are swallowed silently — the resolver downgrades to "key
|
|
56984
|
+
* unset" in that case (consistent with the silent-fallback pattern in
|
|
56985
|
+
* `useAppManifest`'s backend-merge step).
|
|
56986
|
+
*
|
|
56987
|
+
* @param {string} appId Nextcloud app ID.
|
|
56988
|
+
* @param {string} key IAppConfig key (already validated as
|
|
56989
|
+
* lowercase + alphanumeric + `_-` by the sentinel regex).
|
|
56990
|
+
* @return {Promise<*>} Resolved value or `null` when unset.
|
|
56991
|
+
*/
|
|
56992
|
+
async function defaultGetAppConfigValue(appId, key) {
|
|
56993
|
+
const cacheKey = `${appId}::${key}`;
|
|
56994
|
+
if (resolveCache.has(cacheKey)) {
|
|
56995
|
+
return resolveCache.get(cacheKey)
|
|
56996
|
+
}
|
|
56997
|
+
const promise = (async () => {
|
|
56998
|
+
// Step 1: initial-state — synchronous, zero-network.
|
|
56999
|
+
const initial = readInitialState(appId, key);
|
|
57000
|
+
if (initial !== undefined && initial !== null && initial !== '') {
|
|
57001
|
+
return initial
|
|
57002
|
+
}
|
|
57003
|
+
// Step 2: runtime fetch — silent fallback on any error.
|
|
57004
|
+
try {
|
|
57005
|
+
const { default: axios } = await import('@nextcloud/axios');
|
|
57006
|
+
const { generateUrl } = await import('@nextcloud/router');
|
|
57007
|
+
const url = generateUrl(`/apps/${appId}/api/configs/${key}`);
|
|
57008
|
+
const response = await axios.get(url);
|
|
57009
|
+
if (response && response.status === 200 && response.data !== undefined) {
|
|
57010
|
+
const data = response.data;
|
|
57011
|
+
// API may return either a raw scalar or `{ value: ... }`.
|
|
57012
|
+
if (isPlainObject$1(data) && 'value' in data) return data.value
|
|
57013
|
+
return data
|
|
57014
|
+
}
|
|
57015
|
+
} catch (e) {
|
|
57016
|
+
// Silent — caller treats as "unset".
|
|
57017
|
+
}
|
|
57018
|
+
return null
|
|
57019
|
+
})();
|
|
57020
|
+
resolveCache.set(cacheKey, promise);
|
|
57021
|
+
return promise
|
|
57022
|
+
}
|
|
57023
|
+
|
|
57024
|
+
/**
|
|
57025
|
+
* Read a key from `@nextcloud/initial-state`. Looks up the conventional
|
|
57026
|
+
* `app-{appId}-{key}` slot. Returns `undefined` when the package is not
|
|
57027
|
+
* installed (e.g. older host) or the key is not provisioned.
|
|
57028
|
+
*
|
|
57029
|
+
* @param {string} appId Nextcloud app ID.
|
|
57030
|
+
* @param {string} key IAppConfig key.
|
|
57031
|
+
* @return {*} Provisioned value or `undefined`.
|
|
57032
|
+
*/
|
|
57033
|
+
function readInitialState(appId, key) {
|
|
57034
|
+
try {
|
|
57035
|
+
// `@nextcloud/initial-state` is an optional peer; the host page
|
|
57036
|
+
// may not provision the slot at all. We resolve via require so
|
|
57037
|
+
// jest mocks the import; bundle-side, the package is treeshaken
|
|
57038
|
+
// when no caller pulls it in.
|
|
57039
|
+
// eslint-disable-next-line global-require, import/no-unresolved, n/no-extraneous-require
|
|
57040
|
+
const mod = require('@nextcloud/initial-state');
|
|
57041
|
+
if (typeof mod.loadState === 'function') {
|
|
57042
|
+
return mod.loadState(appId, key, undefined)
|
|
57043
|
+
}
|
|
57044
|
+
} catch (e) {
|
|
57045
|
+
// Package not installed or no slot provisioned — fall through.
|
|
57046
|
+
}
|
|
57047
|
+
return undefined
|
|
57048
|
+
}
|
|
57049
|
+
|
|
57050
|
+
/**
|
|
57051
|
+
* Type guard — true when value is a plain (non-array, non-null) object.
|
|
57052
|
+
*
|
|
57053
|
+
* @param {*} value Candidate.
|
|
57054
|
+
* @return {boolean} True when value is a plain object.
|
|
57055
|
+
*/
|
|
57056
|
+
function isPlainObject$1(value) {
|
|
57057
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
57058
|
+
}
|
|
57059
|
+
|
|
57060
|
+
/**
|
|
57061
|
+
* Composable that loads, resolves, and validates a Conduction app manifest.
|
|
57062
|
+
*
|
|
57063
|
+
* The composable implements the four-phase flow specified in
|
|
57064
|
+
* REQ-JMR-002 of the json-manifest-renderer capability + the
|
|
57065
|
+
* substitution step from the `manifest-resolve-sentinel` capability:
|
|
56728
57066
|
*
|
|
56729
57067
|
* 1. Synchronous bundled load — `bundledManifest` is the immediate value.
|
|
56730
57068
|
* 2. Async backend merge — fetches `/index.php/apps/{appId}/api/manifest`
|
|
56731
57069
|
* and deep-merges any 200 response over the bundled manifest. 4xx /
|
|
56732
57070
|
* 5xx / network errors are silently ignored so apps work without a
|
|
56733
57071
|
* backend endpoint.
|
|
56734
|
-
* 3.
|
|
57072
|
+
* 3. Sentinel resolution — `@resolve:<key>` strings under
|
|
57073
|
+
* `pages[].config` are substituted with `IAppConfig` values via the
|
|
57074
|
+
* `resolveManifestSentinels` utility (see its module docs for the
|
|
57075
|
+
* resolution source chain). Unresolved keys surface on the
|
|
57076
|
+
* returned `unresolvedSentinels` ref.
|
|
57077
|
+
* 4. Validation — the resolved result is validated against
|
|
56735
57078
|
* `app-manifest.schema.json`. On failure, the bundled manifest is
|
|
56736
57079
|
* kept and a `console.warn` is emitted with the error list.
|
|
56737
57080
|
*
|
|
@@ -56739,7 +57082,8 @@ function validateFieldsArray(fields, fieldsPath, errors) {
|
|
|
56739
57082
|
* can hot-swap the manifest without a page reload.
|
|
56740
57083
|
*
|
|
56741
57084
|
* @param {string} appId Nextcloud app ID. Used to build the default
|
|
56742
|
-
* backend endpoint URL via `@nextcloud/router
|
|
57085
|
+
* backend endpoint URL via `@nextcloud/router` and to scope
|
|
57086
|
+
* IAppConfig lookups for `@resolve:<key>` sentinels.
|
|
56743
57087
|
* @param {object} bundledManifest The manifest shipped with the app (the
|
|
56744
57088
|
* default value, available synchronously).
|
|
56745
57089
|
* @param {object} [options] Configuration options.
|
|
@@ -56749,7 +57093,10 @@ function validateFieldsArray(fields, fieldsPath, errors) {
|
|
|
56749
57093
|
* return a promise resolving to `{ status: number, data: object }`.
|
|
56750
57094
|
* Defaults to `axios.get` from `@nextcloud/axios` (which inherits the
|
|
56751
57095
|
* Nextcloud CSRF token automatically).
|
|
56752
|
-
* @
|
|
57096
|
+
* @param {Function} [options.getAppConfigValue] Override the
|
|
57097
|
+
* IAppConfig resolver consumed by `resolveManifestSentinels`. Useful
|
|
57098
|
+
* for tests that want to mount a fixture-driven config map.
|
|
57099
|
+
* @return {{ manifest: import('vue').Ref<object>, isLoading: import('vue').Ref<boolean>, validationErrors: import('vue').Ref<string[]|null>, unresolvedSentinels: import('vue').Ref<string[]> }}
|
|
56753
57100
|
*
|
|
56754
57101
|
* @example Basic usage (Composition API)
|
|
56755
57102
|
* const { manifest, isLoading } = useAppManifest('decidesk', bundled)
|
|
@@ -56766,11 +57113,17 @@ function validateFieldsArray(fields, fieldsPath, errors) {
|
|
|
56766
57113
|
* endpoint: '/custom/manifest/url',
|
|
56767
57114
|
* fetcher: (url) => Promise.resolve({ status: 200, data: { ... } }),
|
|
56768
57115
|
* })
|
|
57116
|
+
*
|
|
57117
|
+
* @example With sentinel resolution + admin warning surface
|
|
57118
|
+
* const { manifest, unresolvedSentinels } = useAppManifest('softwarecatalog', bundled)
|
|
57119
|
+
* // unresolvedSentinels.value is e.g. ['voorzieningen_register']
|
|
57120
|
+
* // when that IAppConfig key is unset on the tenant.
|
|
56769
57121
|
*/
|
|
56770
57122
|
function useAppManifest(appId, bundledManifest, options = {}) {
|
|
56771
57123
|
const manifest = Vue.ref(bundledManifest);
|
|
56772
57124
|
const isLoading = Vue.ref(true);
|
|
56773
57125
|
const validationErrors = Vue.ref(null);
|
|
57126
|
+
const unresolvedSentinels = Vue.ref([]);
|
|
56774
57127
|
|
|
56775
57128
|
const endpoint = options.endpoint ?? router.generateUrl(`/apps/${appId}/api/manifest`);
|
|
56776
57129
|
const fetcher = options.fetcher ?? ((url) => axios.get(url))
|
|
@@ -56782,7 +57135,18 @@ function useAppManifest(appId, bundledManifest, options = {}) {
|
|
|
56782
57135
|
return
|
|
56783
57136
|
}
|
|
56784
57137
|
const merged = deepMerge(bundledManifest, response.data);
|
|
56785
|
-
|
|
57138
|
+
|
|
57139
|
+
// Sentinel resolution runs BEFORE validation per
|
|
57140
|
+
// REQ-MRS-002: the validator MUST NEVER observe an
|
|
57141
|
+
// unresolved sentinel at runtime. Resolution failures
|
|
57142
|
+
// (unset IAppConfig keys) substitute null and accumulate
|
|
57143
|
+
// on `unresolvedSentinels`; they do NOT block validation.
|
|
57144
|
+
const { manifest: resolved, unresolved } = await resolveManifestSentinels(merged, appId, {
|
|
57145
|
+
getAppConfigValue: options.getAppConfigValue,
|
|
57146
|
+
});
|
|
57147
|
+
unresolvedSentinels.value = unresolved;
|
|
57148
|
+
|
|
57149
|
+
const result = validateManifest(resolved);
|
|
56786
57150
|
if (!result.valid) {
|
|
56787
57151
|
validationErrors.value = result.errors;
|
|
56788
57152
|
// eslint-disable-next-line no-console
|
|
@@ -56792,7 +57156,7 @@ function useAppManifest(appId, bundledManifest, options = {}) {
|
|
|
56792
57156
|
);
|
|
56793
57157
|
return
|
|
56794
57158
|
}
|
|
56795
|
-
manifest.value =
|
|
57159
|
+
manifest.value = resolved;
|
|
56796
57160
|
} catch (err) {
|
|
56797
57161
|
// Silent fallback on 404, network errors, non-200 responses.
|
|
56798
57162
|
// Apps without a backend endpoint should keep working.
|
|
@@ -56801,7 +57165,7 @@ function useAppManifest(appId, bundledManifest, options = {}) {
|
|
|
56801
57165
|
}
|
|
56802
57166
|
})();
|
|
56803
57167
|
|
|
56804
|
-
return { manifest, isLoading, validationErrors }
|
|
57168
|
+
return { manifest, isLoading, validationErrors, unresolvedSentinels }
|
|
56805
57169
|
}
|
|
56806
57170
|
|
|
56807
57171
|
/**
|
|
@@ -112612,6 +112976,7 @@ exports.SEARCH_TYPE = SEARCH_TYPE;
|
|
|
112612
112976
|
exports.auditTrailsPlugin = auditTrailsPlugin;
|
|
112613
112977
|
exports.buildHeaders = buildHeaders;
|
|
112614
112978
|
exports.buildQueryString = buildQueryString;
|
|
112979
|
+
exports.clearResolveCache = clearResolveCache;
|
|
112615
112980
|
exports.columnsFromSchema = columnsFromSchema;
|
|
112616
112981
|
exports.createCrudStore = createCrudStore;
|
|
112617
112982
|
exports.createObjectStore = createObjectStore;
|
|
@@ -112639,6 +113004,7 @@ exports.registerMappingPlugin = registerMappingPlugin;
|
|
|
112639
113004
|
exports.registerTranslations = registerTranslations;
|
|
112640
113005
|
exports.relationsPlugin = relationsPlugin;
|
|
112641
113006
|
exports.resetVisibilityCache = resetVisibilityCache;
|
|
113007
|
+
exports.resolveManifestSentinels = resolveManifestSentinels;
|
|
112642
113008
|
exports.searchPlugin = searchPlugin;
|
|
112643
113009
|
exports.selectionPlugin = selectionPlugin;
|
|
112644
113010
|
exports.useAppManifest = useAppManifest;
|