@jskit-ai/kernel 0.1.245 → 0.1.247

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.
Files changed (102) hide show
  1. package/client/appConfig.js +2 -1
  2. package/client/asyncModuleRecovery.js +2 -4
  3. package/client/index.d.ts +13 -75
  4. package/client/index.js +1 -1
  5. package/client/logging.js +8 -2
  6. package/client/moduleBootstrap.js +41 -33
  7. package/client/moduleBootstrap.test.js +35 -0
  8. package/client/shellBootstrap.js +1 -8
  9. package/client/shellBootstrap.test.js +32 -5
  10. package/client/shellRouting.js +16 -61
  11. package/client/shellRouting.test.js +30 -1
  12. package/client/vite/clientBootstrapPlugin.js +0 -6
  13. package/client/vite/clientBootstrapPlugin.test.js +2 -3
  14. package/internal/node/installedPackages.js +19 -62
  15. package/internal/node/installedPackages.test.js +33 -0
  16. package/package.json +2 -4
  17. package/server/actions/actionCatalogue.js +13 -34
  18. package/{shared → server}/actions/actionContributorHelpers.js +0 -5
  19. package/{shared → server}/actions/actionDefinitions.js +24 -131
  20. package/{shared → server}/actions/actionDefinitions.test.js +75 -21
  21. package/server/actions/actionEvents.js +97 -2
  22. package/server/actions/actionEvents.test.js +79 -0
  23. package/{shared → server}/actions/executionContext.js +3 -13
  24. package/server/actions/idempotency.js +24 -0
  25. package/server/actions/pipeline.js +87 -0
  26. package/{shared → server}/actions/policies.js +20 -33
  27. package/{shared → server}/actions/policies.test.js +6 -6
  28. package/{shared → server}/actions/registry.js +3 -22
  29. package/{shared → server}/actions/registry.test.js +70 -32
  30. package/server/actions/requestMeta.js +17 -0
  31. package/server/http/capabilityHttpRuntime.js +1 -1
  32. package/server/http/capabilityHttpRuntime.test.js +56 -0
  33. package/server/http/errorHandling.js +291 -0
  34. package/server/{runtime/fastifyBootstrap.test.js → http/errorHandling.test.js} +72 -120
  35. package/server/http/lib/routeRegistration.js +36 -150
  36. package/server/http/lib/routeTransport.js +2 -0
  37. package/server/http/lib/routeValidator.js +5 -47
  38. package/server/http/lib/routeValidator.test.js +15 -17
  39. package/server/http/lib/router.js +10 -94
  40. package/server/platform/providerRuntime/capabilityProviderLoader.js +2 -1
  41. package/server/platform/providerRuntime/helpers.js +1 -34
  42. package/server/runtime/integers.js +1 -5
  43. package/server/runtime/serviceAuthorization.js +4 -9
  44. package/server/support/appConfig.js +2 -1
  45. package/server/support/appConfigFiles.js +1 -38
  46. package/server/support/appConfigFiles.test.js +3 -39
  47. package/server/support/index.js +1 -20
  48. package/shared/capabilities/defineProvider.js +1 -1
  49. package/shared/capabilities/index.js +1 -1
  50. package/shared/index.js +1 -1
  51. package/shared/support/index.js +1 -4
  52. package/shared/support/mobileConfig.js +99 -0
  53. package/shared/support/mobileConfig.test.js +113 -0
  54. package/shared/support/normalize.js +0 -108
  55. package/shared/support/normalize.test.js +0 -134
  56. package/shared/{actions/actionContributorHelpers.test.js → support/permissions.test.js} +1 -1
  57. package/shared/support/returnToPath.js +1 -20
  58. package/shared/support/returnToPath.test.js +1 -36
  59. package/shared/support/shellLayoutTargets.js +2 -140
  60. package/shared/support/shellLayoutTargets.test.js +1 -110
  61. package/shared/support/sorting.js +1 -9
  62. package/shared/surface/apiPaths.js +1 -82
  63. package/shared/surface/paths.js +38 -171
  64. package/shared/surface/registry.js +2 -1
  65. package/shared/surface/runtime.js +18 -109
  66. package/shared/surface/runtime.test.js +57 -0
  67. package/shared/validators/index.js +1 -4
  68. package/shared/validators/jsonRestSchemaSupport.js +0 -9
  69. package/shared/validators/recordIdParamsValidator.js +0 -6
  70. package/shared/validators/schemaDefinitions.js +4 -30
  71. package/shared/validators/schemaPayloadValidation.js +2 -6
  72. package/test/barrelExposure.test.js +3 -7
  73. package/test/exportsContract.test.js +1 -3
  74. package/client/mobileLaunchRouting.js +0 -231
  75. package/client/mobileLaunchRouting.test.js +0 -230
  76. package/server/http/lib/controller.js +0 -183
  77. package/server/http/lib/controller.test.js +0 -143
  78. package/server/http/lib/index.js +0 -5
  79. package/server/runtime/canonicalJson.js +0 -74
  80. package/server/runtime/entityChangeEvents.js +0 -307
  81. package/server/runtime/entityChangeEvents.test.js +0 -274
  82. package/server/runtime/fastifyBootstrap.js +0 -553
  83. package/server/runtime/moduleConfig.js +0 -223
  84. package/server/runtime/moduleConfig.test.js +0 -165
  85. package/server/runtime/pagination.js +0 -13
  86. package/server/runtime/realtimeNormalization.js +0 -21
  87. package/server/runtime/routeUtils.js +0 -20
  88. package/server/runtime/securityAudit.js +0 -269
  89. package/server/runtime/securityAudit.test.js +0 -41
  90. package/server/support/pageTargets.js +0 -772
  91. package/server/support/pageTargets.test.js +0 -659
  92. package/server/support/shellOutlets.js +0 -576
  93. package/server/support/shellOutlets.test.js +0 -336
  94. package/server/surface/index.js +0 -18
  95. package/shared/actions/audit.js +0 -7
  96. package/shared/actions/idempotency.js +0 -62
  97. package/shared/actions/observability.js +0 -10
  98. package/shared/actions/pipeline.js +0 -309
  99. package/shared/actions/requestMeta.js +0 -36
  100. package/shared/actions/textNormalization.js +0 -3
  101. package/shared/actions/withActionDefaults.js +0 -34
  102. /package/{shared → server}/actions/executionContext.test.js +0 -0
@@ -0,0 +1,113 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { normalizeMobileCallbackPath, normalizeMobileConfig } from "./mobileConfig.js";
4
+
5
+ test("normalizeMobileCallbackPath normalizes auth callback paths", () => {
6
+ assert.equal(normalizeMobileCallbackPath("auth/login"), "/auth/login");
7
+ assert.equal(normalizeMobileCallbackPath("/auth/login"), "/auth/login");
8
+ assert.equal(
9
+ normalizeMobileCallbackPath("", {
10
+ fallback: "/login/callback"
11
+ }),
12
+ "/login/callback"
13
+ );
14
+ });
15
+
16
+ test("normalizeMobileConfig normalizes the mobile config contract", () => {
17
+ const mobileConfig = normalizeMobileConfig({
18
+ enabled: true,
19
+ strategy: " Capacitor ",
20
+ appId: "com.example.convict",
21
+ appName: "Convict",
22
+ assetMode: " dev_server ",
23
+ devServerUrl: " http://192.168.1.10:5173 ",
24
+ apiBaseUrl: " https://api.example.com ",
25
+ auth: {
26
+ callbackPath: "auth/login",
27
+ customScheme: " Convict ",
28
+ appLinkDomains: ["App.Example.com", " app.example.com "]
29
+ },
30
+ android: {
31
+ packageName: "com.example.convict",
32
+ minSdk: "28",
33
+ targetSdk: "35",
34
+ versionCode: "4",
35
+ versionName: "2.0.0"
36
+ }
37
+ });
38
+
39
+ assert.deepEqual(mobileConfig, {
40
+ enabled: true,
41
+ strategy: "capacitor",
42
+ appId: "com.example.convict",
43
+ appName: "Convict",
44
+ assetMode: "dev_server",
45
+ devServerUrl: "http://192.168.1.10:5173",
46
+ apiBaseUrl: "https://api.example.com",
47
+ auth: {
48
+ callbackPath: "/auth/login",
49
+ customScheme: "convict",
50
+ appLinkDomains: ["app.example.com"]
51
+ },
52
+ android: {
53
+ packageName: "com.example.convict",
54
+ minSdk: 28,
55
+ targetSdk: 35,
56
+ versionCode: 4,
57
+ versionName: "2.0.0"
58
+ }
59
+ });
60
+ });
61
+
62
+ test("normalizeMobileConfig applies defaults for missing fields", () => {
63
+ const mobileConfig = normalizeMobileConfig({
64
+ auth: {
65
+ appLinkDomains: "app.example.com"
66
+ },
67
+ android: {
68
+ minSdk: "bad",
69
+ targetSdk: 0,
70
+ versionCode: "",
71
+ versionName: ""
72
+ }
73
+ });
74
+
75
+ assert.deepEqual(mobileConfig, {
76
+ enabled: false,
77
+ strategy: "",
78
+ appId: "",
79
+ appName: "",
80
+ assetMode: "bundled",
81
+ devServerUrl: "",
82
+ apiBaseUrl: "",
83
+ auth: {
84
+ callbackPath: "/auth/login",
85
+ customScheme: "",
86
+ appLinkDomains: ["app.example.com"]
87
+ },
88
+ android: {
89
+ packageName: "",
90
+ minSdk: 26,
91
+ targetSdk: 35,
92
+ versionCode: 1,
93
+ versionName: "1.0.0"
94
+ }
95
+ });
96
+ });
97
+
98
+ test("normalizeMobileConfig preserves explicit false values from string input", () => {
99
+ const mobileConfig = normalizeMobileConfig({
100
+ enabled: "false"
101
+ });
102
+
103
+ assert.equal(mobileConfig.enabled, false);
104
+ });
105
+
106
+ test("normalizeMobileConfig rejects invalid assetMode values", () => {
107
+ assert.throws(
108
+ () => normalizeMobileConfig({
109
+ assetMode: "weird"
110
+ }),
111
+ /config\.mobile\.assetMode must be "bundled" or "dev_server"\./u
112
+ );
113
+ });
@@ -134,20 +134,6 @@ function normalizeIfInSource(source, normalized, fieldName, normalizer = (value)
134
134
  return normalized;
135
135
  }
136
136
 
137
- function normalizeIfPresent(value, normalizer = (entry) => entry) {
138
- if (typeof normalizer !== "function") {
139
- throw new TypeError("normalizeIfPresent requires normalizer function.");
140
- }
141
- return value == null ? value : normalizer(value);
142
- }
143
-
144
- function normalizeOrNull(value, normalizer = (entry) => entry) {
145
- if (typeof normalizer !== "function") {
146
- throw new TypeError("normalizeOrNull requires normalizer function.");
147
- }
148
- return value == null ? null : normalizer(value);
149
- }
150
-
151
137
  function isRecord(value) {
152
138
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
153
139
  }
@@ -260,94 +246,6 @@ function normalizeOneOf(value, allowedValues = [], fallback = "") {
260
246
  return supported[0] || "";
261
247
  }
262
248
 
263
- const MOBILE_ASSET_MODES = Object.freeze(["bundled", "dev_server"]);
264
- const MOBILE_STRATEGIES = Object.freeze(["capacitor"]);
265
- const DEFAULT_MOBILE_AUTH_CONFIG = Object.freeze({
266
- callbackPath: "/auth/login",
267
- customScheme: "",
268
- appLinkDomains: Object.freeze([])
269
- });
270
- const DEFAULT_MOBILE_ANDROID_CONFIG = Object.freeze({
271
- packageName: "",
272
- minSdk: 26,
273
- targetSdk: 35,
274
- versionCode: 1,
275
- versionName: "1.0.0"
276
- });
277
-
278
- function normalizeMobileStrategy(value = "", { fallback = "" } = {}) {
279
- const normalized = normalizeLowerText(value);
280
- return MOBILE_STRATEGIES.includes(normalized) ? normalized : normalizeLowerText(fallback);
281
- }
282
-
283
- function normalizeMobileAssetMode(value = "", { fallback = "bundled" } = {}) {
284
- const normalized = normalizeLowerText(value);
285
- if (!normalized) {
286
- return normalizeLowerText(fallback, {
287
- fallback: "bundled"
288
- });
289
- }
290
- if (!MOBILE_ASSET_MODES.includes(normalized)) {
291
- throw new TypeError('config.mobile.assetMode must be "bundled" or "dev_server".');
292
- }
293
- return normalized;
294
- }
295
-
296
- function normalizeMobileCallbackPath(value = "", { fallback = "/auth/login" } = {}) {
297
- const normalized = normalizeText(value, {
298
- fallback
299
- });
300
- if (!normalized) {
301
- return "";
302
- }
303
-
304
- return normalized.startsWith("/") ? normalized : `/${normalized}`;
305
- }
306
-
307
- function normalizeMobileConfig(source = {}) {
308
- const mobile = normalizeObject(source);
309
- const auth = normalizeObject(mobile.auth);
310
- const android = normalizeObject(mobile.android);
311
-
312
- return Object.freeze({
313
- enabled: hasValue(mobile.enabled) ? normalizeBoolean(mobile.enabled) : false,
314
- strategy: normalizeMobileStrategy(mobile.strategy),
315
- appId: normalizeText(mobile.appId),
316
- appName: normalizeText(mobile.appName),
317
- assetMode: normalizeMobileAssetMode(mobile.assetMode),
318
- devServerUrl: normalizeText(mobile.devServerUrl),
319
- apiBaseUrl: normalizeText(mobile.apiBaseUrl),
320
- auth: Object.freeze({
321
- callbackPath: normalizeMobileCallbackPath(auth.callbackPath, {
322
- fallback: DEFAULT_MOBILE_AUTH_CONFIG.callbackPath
323
- }),
324
- customScheme: normalizeLowerText(auth.customScheme),
325
- appLinkDomains: Object.freeze([
326
- ...new Set(
327
- normalizeUniqueTextList(auth.appLinkDomains, {
328
- acceptSingle: true
329
- }).map((entry) => normalizeLowerText(entry))
330
- )
331
- ])
332
- }),
333
- android: Object.freeze({
334
- packageName: normalizeText(android.packageName),
335
- minSdk: normalizePositiveInteger(android.minSdk, {
336
- fallback: DEFAULT_MOBILE_ANDROID_CONFIG.minSdk
337
- }),
338
- targetSdk: normalizePositiveInteger(android.targetSdk, {
339
- fallback: DEFAULT_MOBILE_ANDROID_CONFIG.targetSdk
340
- }),
341
- versionCode: normalizePositiveInteger(android.versionCode, {
342
- fallback: DEFAULT_MOBILE_ANDROID_CONFIG.versionCode
343
- }),
344
- versionName: normalizeText(android.versionName, {
345
- fallback: DEFAULT_MOBILE_ANDROID_CONFIG.versionName
346
- })
347
- })
348
- });
349
- }
350
-
351
249
  function ensureNonEmptyText(value, label = "value") {
352
250
  const normalized = normalizeText(value);
353
251
  if (!normalized) {
@@ -368,8 +266,6 @@ export {
368
266
  normalizeQueryToken,
369
267
  normalizeObject,
370
268
  normalizeIfInSource,
371
- normalizeIfPresent,
372
- normalizeOrNull,
373
269
  isRecord,
374
270
  normalizeArray,
375
271
  normalizeUniqueTextList,
@@ -379,9 +275,5 @@ export {
379
275
  normalizeRecordId,
380
276
  normalizeOpaqueId,
381
277
  normalizeOneOf,
382
- normalizeMobileAssetMode,
383
- normalizeMobileCallbackPath,
384
- normalizeMobileConfig,
385
- normalizeMobileStrategy,
386
278
  ensureNonEmptyText
387
279
  };
@@ -7,10 +7,6 @@ import {
7
7
  normalizeFiniteInteger,
8
8
  normalizeFiniteNumber,
9
9
  normalizeIfInSource,
10
- normalizeIfPresent,
11
- normalizeMobileCallbackPath,
12
- normalizeMobileConfig,
13
- normalizeOrNull,
14
10
  normalizeRecordId,
15
11
  normalizeOpaqueId,
16
12
  normalizePositiveInteger,
@@ -166,26 +162,6 @@ test("normalizeIfInSource validates target and function arguments", () => {
166
162
  );
167
163
  });
168
164
 
169
- test("normalizeIfPresent normalizes non-nullish values and preserves nullish values", () => {
170
- assert.equal(normalizeIfPresent(" Ada ", normalizeText), "Ada");
171
- assert.equal(normalizeIfPresent(null, normalizeText), null);
172
- assert.equal(normalizeIfPresent(undefined, normalizeText), undefined);
173
- assert.throws(
174
- () => normalizeIfPresent("Ada", null),
175
- /requires normalizer function/
176
- );
177
- });
178
-
179
- test("normalizeOrNull normalizes non-nullish values and coerces nullish to null", () => {
180
- assert.equal(normalizeOrNull(" Ada ", normalizeText), "Ada");
181
- assert.equal(normalizeOrNull(null, normalizeText), null);
182
- assert.equal(normalizeOrNull(undefined, normalizeText), null);
183
- assert.throws(
184
- () => normalizeOrNull("Ada", null),
185
- /requires normalizer function/
186
- );
187
- });
188
-
189
165
  test("normalizeRecordId accepts canonical string, safe integer, and bigint identifiers", () => {
190
166
  const unsafeNumericId = Number(9007199254740993n);
191
167
  assert.equal(normalizeRecordId(" 7 "), "7");
@@ -224,113 +200,3 @@ test("normalizeUniqueTextList trims, dedupes, and supports optional single value
224
200
  ["one"]
225
201
  );
226
202
  });
227
-
228
- test("normalizeMobileCallbackPath normalizes auth callback paths", () => {
229
- assert.equal(normalizeMobileCallbackPath("auth/login"), "/auth/login");
230
- assert.equal(normalizeMobileCallbackPath("/auth/login"), "/auth/login");
231
- assert.equal(
232
- normalizeMobileCallbackPath("", {
233
- fallback: "/login/callback"
234
- }),
235
- "/login/callback"
236
- );
237
- });
238
-
239
- test("normalizeMobileConfig normalizes the mobile config contract", () => {
240
- const mobileConfig = normalizeMobileConfig({
241
- enabled: true,
242
- strategy: " Capacitor ",
243
- appId: "com.example.convict",
244
- appName: "Convict",
245
- assetMode: " dev_server ",
246
- devServerUrl: " http://192.168.1.10:5173 ",
247
- apiBaseUrl: " https://api.example.com ",
248
- auth: {
249
- callbackPath: "auth/login",
250
- customScheme: " Convict ",
251
- appLinkDomains: ["App.Example.com", " app.example.com "]
252
- },
253
- android: {
254
- packageName: "com.example.convict",
255
- minSdk: "28",
256
- targetSdk: "35",
257
- versionCode: "4",
258
- versionName: "2.0.0"
259
- }
260
- });
261
-
262
- assert.deepEqual(mobileConfig, {
263
- enabled: true,
264
- strategy: "capacitor",
265
- appId: "com.example.convict",
266
- appName: "Convict",
267
- assetMode: "dev_server",
268
- devServerUrl: "http://192.168.1.10:5173",
269
- apiBaseUrl: "https://api.example.com",
270
- auth: {
271
- callbackPath: "/auth/login",
272
- customScheme: "convict",
273
- appLinkDomains: ["app.example.com"]
274
- },
275
- android: {
276
- packageName: "com.example.convict",
277
- minSdk: 28,
278
- targetSdk: 35,
279
- versionCode: 4,
280
- versionName: "2.0.0"
281
- }
282
- });
283
- });
284
-
285
- test("normalizeMobileConfig applies defaults for missing fields", () => {
286
- const mobileConfig = normalizeMobileConfig({
287
- auth: {
288
- appLinkDomains: "app.example.com"
289
- },
290
- android: {
291
- minSdk: "bad",
292
- targetSdk: 0,
293
- versionCode: "",
294
- versionName: ""
295
- }
296
- });
297
-
298
- assert.deepEqual(mobileConfig, {
299
- enabled: false,
300
- strategy: "",
301
- appId: "",
302
- appName: "",
303
- assetMode: "bundled",
304
- devServerUrl: "",
305
- apiBaseUrl: "",
306
- auth: {
307
- callbackPath: "/auth/login",
308
- customScheme: "",
309
- appLinkDomains: ["app.example.com"]
310
- },
311
- android: {
312
- packageName: "",
313
- minSdk: 26,
314
- targetSdk: 35,
315
- versionCode: 1,
316
- versionName: "1.0.0"
317
- }
318
- });
319
- });
320
-
321
- test("normalizeMobileConfig preserves explicit false values from string input", () => {
322
- const mobileConfig = normalizeMobileConfig({
323
- enabled: "false"
324
- });
325
-
326
- assert.equal(mobileConfig.enabled, false);
327
- });
328
-
329
- test("normalizeMobileConfig rejects invalid assetMode values", () => {
330
- assert.throws(
331
- () => normalizeMobileConfig({
332
- assetMode: "weird"
333
- }),
334
- /config\.mobile\.assetMode must be "bundled" or "dev_server"\./u
335
- );
336
- });
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
 
4
- import { hasPermission } from "./actionContributorHelpers.js";
4
+ import { hasPermission } from "./permissions.js";
5
5
 
6
6
  test("hasPermission allows wildcard and direct matches", () => {
7
7
  assert.equal(hasPermission(["*"], "workspace.billing.manage"), true);
@@ -89,24 +89,6 @@ function normalizeBlockedPathnames(blockedPathnames = []) {
89
89
  return normalizedPathnames;
90
90
  }
91
91
 
92
- function resolveAllowedOriginsFromPlacementContext(contextValue = null, { includeWindowOrigin = true } = {}) {
93
- const seedOrigins = [];
94
-
95
- if (includeWindowOrigin && typeof window === "object" && window?.location?.origin) {
96
- appendNormalizedOrigin(seedOrigins, window.location.origin);
97
- }
98
-
99
- const surfaceConfig =
100
- contextValue && typeof contextValue === "object" && contextValue.surfaceConfig && typeof contextValue.surfaceConfig === "object"
101
- ? contextValue.surfaceConfig
102
- : {};
103
- const surfacesById =
104
- surfaceConfig.surfacesById && typeof surfaceConfig.surfacesById === "object" ? surfaceConfig.surfacesById : {};
105
- return resolveAllowedOriginsFromSurfaceDefinitions(surfacesById, {
106
- seedOrigins
107
- });
108
- }
109
-
110
92
  function normalizeReturnToPath(
111
93
  value,
112
94
  {
@@ -163,6 +145,5 @@ export {
163
145
  normalizeAllowedOrigins,
164
146
  normalizeHttpOrigin,
165
147
  normalizeReturnToPath,
166
- resolveAllowedOriginsFromSurfaceDefinitions,
167
- resolveAllowedOriginsFromPlacementContext
148
+ resolveAllowedOriginsFromSurfaceDefinitions
168
149
  };
@@ -5,8 +5,7 @@ import {
5
5
  normalizeAllowedOrigins,
6
6
  normalizeHttpOrigin,
7
7
  normalizeReturnToPath,
8
- resolveAllowedOriginsFromSurfaceDefinitions,
9
- resolveAllowedOriginsFromPlacementContext
8
+ resolveAllowedOriginsFromSurfaceDefinitions
10
9
  } from "./returnToPath.js";
11
10
 
12
11
  test("normalizeHttpOrigin keeps valid http origins only", () => {
@@ -88,40 +87,6 @@ test("resolveAllowedOriginsFromSurfaceDefinitions includes seed origins and surf
88
87
  ]);
89
88
  });
90
89
 
91
- test("resolveAllowedOriginsFromPlacementContext collects current and surface origins", () => {
92
- const originalWindow = globalThis.window;
93
- globalThis.window = {
94
- location: {
95
- origin: "https://current.example.com"
96
- }
97
- };
98
-
99
- try {
100
- const origins = resolveAllowedOriginsFromPlacementContext({
101
- surfaceConfig: {
102
- surfacesById: {
103
- home: { origin: "https://home.example.com" },
104
- app: { origin: "https://app.example.com" },
105
- duplicate: { origin: "https://home.example.com" },
106
- invalid: { origin: "mailto:hello@example.com" }
107
- }
108
- }
109
- });
110
-
111
- assert.deepEqual(origins, [
112
- "https://current.example.com",
113
- "https://home.example.com",
114
- "https://app.example.com"
115
- ]);
116
- } finally {
117
- if (typeof originalWindow === "undefined") {
118
- delete globalThis.window;
119
- } else {
120
- globalThis.window = originalWindow;
121
- }
122
- }
123
- });
124
-
125
90
  test("normalizeReturnToPath refuses paths a browser reads as another site", () => {
126
91
  for (const value of ["/\\evil.example", "/\\/evil.example/x", "/\t/evil.example", "/\n/evil.example", "\\\\evil.example"]) {
127
92
  assert.equal(normalizeReturnToPath(value, { fallback: "/home" }), "/home", JSON.stringify(value));
@@ -3,28 +3,9 @@ import {
3
3
  normalizeText
4
4
  } from "./normalize.js";
5
5
 
6
- const SHELL_OUTLET_TAG_PATTERN = /<ShellOutlet\b([^>]*)\/?>/g;
7
- const ATTRIBUTE_PATTERN = /([:@]?[A-Za-z_][A-Za-z0-9_-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'))?/g;
8
6
  const PLACEMENT_LAYOUT_CLASSES = Object.freeze(["compact", "medium", "expanded"]);
9
7
  const WEB_PLACEMENT_SURFACE_ANY = "*";
10
8
 
11
- function parseTagAttributes(attributesSource = "") {
12
- const attributes = {};
13
- const source = String(attributesSource || "");
14
- for (const match of source.matchAll(ATTRIBUTE_PATTERN)) {
15
- const attributeName = normalizeText(match[1]);
16
- if (!attributeName) {
17
- continue;
18
- }
19
-
20
- const hasValue = match[2] != null || match[3] != null;
21
- const attributeValue = hasValue ? String(match[2] ?? match[3] ?? "") : true;
22
- attributes[attributeName] = attributeValue;
23
- }
24
-
25
- return attributes;
26
- }
27
-
28
9
  function normalizeShellOutletTargetId(value = "") {
29
10
  const normalizedValue = normalizeText(value);
30
11
  if (!normalizedValue) {
@@ -250,126 +231,9 @@ function normalizePlacementTopologyDefinition(
250
231
  });
251
232
  }
252
233
 
253
- function resolveShellOutletTargetParts(
254
- {
255
- target = ""
256
- } = {}
257
- ) {
258
- const normalizedTargetId = normalizeShellOutletTargetId(target);
259
- if (!normalizedTargetId) {
260
- return null;
261
- }
262
-
263
- return Object.freeze({
264
- id: normalizedTargetId
265
- });
266
- }
267
-
268
- function findShellOutletTargetById(targets = [], targetId = "") {
269
- const entries = Array.isArray(targets) ? targets : [];
270
- const normalizedTargetId = normalizeShellOutletTargetId(targetId);
271
- if (!normalizedTargetId) {
272
- return null;
273
- }
274
-
275
- return entries.find((entry) => normalizeShellOutletTargetId(entry?.id) === normalizedTargetId) || null;
276
- }
277
-
278
- function describeShellOutletTargets(targets = []) {
279
- return (Array.isArray(targets) ? targets : [])
280
- .map((entry) => normalizeShellOutletTargetId(entry?.id))
281
- .filter(Boolean)
282
- .join(", ");
283
- }
284
-
285
- function isDefaultAttributeEnabled(value) {
286
- if (value === true) {
287
- return true;
288
- }
289
-
290
- const normalized = normalizeText(value).toLowerCase();
291
- if (!normalized) {
292
- return true;
293
- }
294
-
295
- return normalized !== "false" && normalized !== "0" && normalized !== "no" && normalized !== "off";
296
- }
297
-
298
- function normalizeShellOutletTargetRecord(
299
- value = {},
300
- {
301
- context = "shell layout"
302
- } = {}
303
- ) {
304
- const record = normalizeObject(value);
305
- const resolvedContext = normalizeText(context) || "shell layout";
306
- const targetParts = resolveShellOutletTargetParts(
307
- {
308
- target: record.target
309
- },
310
- { context: resolvedContext }
311
- );
312
- if (!targetParts) {
313
- return null;
314
- }
315
-
316
- return Object.freeze({
317
- ...targetParts,
318
- default:
319
- Object.hasOwn(record, "default") &&
320
- isDefaultAttributeEnabled(record.default)
321
- });
322
- }
323
-
324
- function discoverShellOutletTargetsFromVueSource(
325
- source = "",
326
- {
327
- context = "shell layout",
328
- enforceSingleDefault = true
329
- } = {}
330
- ) {
331
- const sourceText = String(source || "");
332
- const resolvedContext = normalizeText(context) || "shell layout";
333
- const targetById = new Map();
334
- let defaultTargetId = "";
335
-
336
- for (const tagMatch of sourceText.matchAll(SHELL_OUTLET_TAG_PATTERN)) {
337
- const attributes = parseTagAttributes(tagMatch[1]);
338
- const normalizedTarget = normalizeShellOutletTargetRecord(attributes, {
339
- context: resolvedContext
340
- });
341
- if (!normalizedTarget) {
342
- continue;
343
- }
344
- if (targetById.has(normalizedTarget.id)) {
345
- throw new Error(`${resolvedContext} contains duplicate ShellOutlet target "${normalizedTarget.id}".`);
346
- }
347
-
348
- if (normalizedTarget.default) {
349
- if (enforceSingleDefault === true && defaultTargetId) {
350
- throw new Error(
351
- `${resolvedContext} defines multiple default ShellOutlet targets: "${defaultTargetId}" and "${normalizedTarget.id}".`
352
- );
353
- }
354
- if (!defaultTargetId) {
355
- defaultTargetId = normalizedTarget.id;
356
- }
357
- }
358
-
359
- targetById.set(normalizedTarget.id, normalizedTarget);
360
- }
361
-
362
- return Object.freeze({
363
- targets: Object.freeze([...targetById.values()]),
364
- defaultTargetId
365
- });
366
- }
367
-
368
234
  export {
369
235
  PLACEMENT_LAYOUT_CLASSES,
370
- describeShellOutletTargets,
371
- discoverShellOutletTargetsFromVueSource,
372
- findShellOutletTargetById,
236
+ WEB_PLACEMENT_SURFACE_ANY,
373
237
  normalizePlacementKind,
374
238
  normalizePlacementLayoutClass,
375
239
  normalizePlacementOwnerId,
@@ -380,7 +244,5 @@ export {
380
244
  normalizePlacementTopologyVariant,
381
245
  normalizeSemanticPlacementId,
382
246
  normalizeShellOutletTargetId,
383
- normalizeShellOutletTargetRecord,
384
- resolvePlacementTargetReference,
385
- resolveShellOutletTargetParts
247
+ resolvePlacementTargetReference
386
248
  };