nucleus-core-ts 0.9.866 → 0.9.867

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 (157) hide show
  1. package/dist/fe/components/IntegrationsPage/components/ConnectionTest.js +11 -1
  2. package/dist/fe/components/IntegrationsPage/components/EndpointSample.js +24 -0
  3. package/dist/fe/components/IntegrationsPage/components/EndpointsTab.js +15 -2
  4. package/dist/fe/components/IntegrationsPage/store/index.js +19 -0
  5. package/package.json +1 -1
  6. package/scripts/build.ts +71 -3
  7. package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.test.d.ts +0 -1
  8. package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.test.js +0 -44
  9. package/dist/fe/components/IntegrationsPage/components/RunPlanSuggestions.test.d.ts +0 -1
  10. package/dist/fe/components/IntegrationsPage/components/RunPlanSuggestions.test.js +0 -103
  11. package/dist/fe/components/IntegrationsPage/components/connectionDraft.test.d.ts +0 -1
  12. package/dist/fe/components/IntegrationsPage/components/connectionDraft.test.js +0 -198
  13. package/dist/fe/components/IntegrationsPage/components/credentialSlots.contract.test.d.ts +0 -1
  14. package/dist/fe/components/IntegrationsPage/components/credentialSlots.contract.test.js +0 -140
  15. package/dist/fe/components/IntegrationsPage/components/deleteIsConfirmed.test.d.ts +0 -1
  16. package/dist/fe/components/IntegrationsPage/components/deleteIsConfirmed.test.js +0 -29
  17. package/dist/fe/components/IntegrationsPage/components/formSupport.test.d.ts +0 -1
  18. package/dist/fe/components/IntegrationsPage/components/formSupport.test.js +0 -156
  19. package/dist/fe/components/IntegrationsPage/components/jsonObjectField.test.d.ts +0 -1
  20. package/dist/fe/components/IntegrationsPage/components/jsonObjectField.test.js +0 -86
  21. package/dist/fe/components/IntegrationsPage/components/mappingDraft.test.d.ts +0 -1
  22. package/dist/fe/components/IntegrationsPage/components/mappingDraft.test.js +0 -59
  23. package/dist/fe/components/IntegrationsPage/components/passwordIsNeeded.test.d.ts +0 -1
  24. package/dist/fe/components/IntegrationsPage/components/passwordIsNeeded.test.js +0 -45
  25. package/dist/fe/components/IntegrationsPage/components/runFormat.test.d.ts +0 -1
  26. package/dist/fe/components/IntegrationsPage/components/runFormat.test.js +0 -47
  27. package/dist/fe/components/IntegrationsPage/components/settleOwnership.test.d.ts +0 -1
  28. package/dist/fe/components/IntegrationsPage/components/settleOwnership.test.js +0 -56
  29. package/dist/fe/components/IntegrationsPage/store/upsertInto.test.d.ts +0 -1
  30. package/dist/fe/components/IntegrationsPage/store/upsertInto.test.js +0 -67
  31. package/dist/fe/components/IntegrationsPage/text/text.test.d.ts +0 -1
  32. package/dist/fe/components/IntegrationsPage/text/text.test.js +0 -67
  33. package/dist/src/Client/Proxy/authz.test.d.ts +0 -1
  34. package/dist/src/Client/Proxy/authz.test.js +0 -453
  35. package/dist/src/Client/Proxy/httpProxy.test.d.ts +0 -1
  36. package/dist/src/Client/Proxy/httpProxy.test.js +0 -73
  37. package/dist/src/ElysiaPlugin/auditAttribution.test.d.ts +0 -1
  38. package/dist/src/ElysiaPlugin/authRouteClassifier.test.d.ts +0 -1
  39. package/dist/src/ElysiaPlugin/dbPoolConfig.test.d.ts +0 -1
  40. package/dist/src/ElysiaPlugin/publicRoutes.test.d.ts +0 -1
  41. package/dist/src/ElysiaPlugin/redactSensitiveOutput.test.d.ts +0 -1
  42. package/dist/src/ElysiaPlugin/resolveConfigCredential.test.d.ts +0 -1
  43. package/dist/src/ElysiaPlugin/routes/auth/accountState.test.d.ts +0 -1
  44. package/dist/src/ElysiaPlugin/routes/auth/admin/createUserAuthz.test.d.ts +0 -1
  45. package/dist/src/ElysiaPlugin/routes/auth/admin/resolveMutationSchema.test.d.ts +0 -1
  46. package/dist/src/ElysiaPlugin/routes/auth/captchaWiring.test.d.ts +0 -1
  47. package/dist/src/ElysiaPlugin/routes/auth/emailLinks/emailLinks.test.d.ts +0 -1
  48. package/dist/src/ElysiaPlugin/routes/auth/emailWiring.test.d.ts +0 -1
  49. package/dist/src/ElysiaPlugin/routes/auth/login/deviceInfo.test.d.ts +0 -1
  50. package/dist/src/ElysiaPlugin/routes/auth/magicLink/utils.test.d.ts +0 -1
  51. package/dist/src/ElysiaPlugin/routes/auth/oauth/absolutizeRedirect.test.d.ts +0 -1
  52. package/dist/src/ElysiaPlugin/routes/auth/oauth/oauthStateCookie.test.d.ts +0 -1
  53. package/dist/src/ElysiaPlugin/routes/auth/oauth/resolveSafeOAuthRedirect.test.d.ts +0 -1
  54. package/dist/src/ElysiaPlugin/routes/auth/sessions/adminRoutes.test.d.ts +0 -1
  55. package/dist/src/ElysiaPlugin/routes/authorization/discovery.test.d.ts +0 -1
  56. package/dist/src/ElysiaPlugin/routes/chat/moderation.test.d.ts +0 -1
  57. package/dist/src/ElysiaPlugin/routes/config/helpers.test.d.ts +0 -1
  58. package/dist/src/ElysiaPlugin/routes/domain/hostnames.test.d.ts +0 -1
  59. package/dist/src/ElysiaPlugin/routes/entity/anonymousCreate.test.d.ts +0 -1
  60. package/dist/src/ElysiaPlugin/routes/entity/createScope.test.d.ts +0 -1
  61. package/dist/src/ElysiaPlugin/routes/entity/filterFields.test.d.ts +0 -1
  62. package/dist/src/ElysiaPlugin/routes/entity/relationAuthz.test.d.ts +0 -1
  63. package/dist/src/ElysiaPlugin/routes/monitoring/index.test.d.ts +0 -1
  64. package/dist/src/ElysiaPlugin/routes/monitoring/serverLogs.test.d.ts +0 -1
  65. package/dist/src/ElysiaPlugin/routes/payment/marketplace/authz.test.d.ts +0 -1
  66. package/dist/src/ElysiaPlugin/routes/payment/paymentAuthz.test.d.ts +0 -1
  67. package/dist/src/ElysiaPlugin/routes/payment/validation.test.d.ts +0 -1
  68. package/dist/src/ElysiaPlugin/routes/pubsub/daprAuth.test.d.ts +0 -1
  69. package/dist/src/ElysiaPlugin/routes/pubsub/frame.test.d.ts +0 -1
  70. package/dist/src/ElysiaPlugin/routes/pubsub/topicCap.test.d.ts +0 -1
  71. package/dist/src/ElysiaPlugin/routes/shared/adminGuard.test.d.ts +0 -1
  72. package/dist/src/ElysiaPlugin/routes/shared/basePathCollision.test.d.ts +0 -1
  73. package/dist/src/ElysiaPlugin/routes/shared/clientIp.test.d.ts +0 -1
  74. package/dist/src/ElysiaPlugin/routes/shared/csrf.test.d.ts +0 -1
  75. package/dist/src/ElysiaPlugin/routes/shared/redact.test.d.ts +0 -1
  76. package/dist/src/ElysiaPlugin/routes/shared/requestLimits.test.d.ts +0 -1
  77. package/dist/src/ElysiaPlugin/routes/shared/sensitiveColumns.test.d.ts +0 -1
  78. package/dist/src/ElysiaPlugin/routes/shared/sensitiveTables.test.d.ts +0 -1
  79. package/dist/src/ElysiaPlugin/routes/shared/systemSensitiveColumns.test.d.ts +0 -1
  80. package/dist/src/ElysiaPlugin/routes/shared/tenantBinding.test.d.ts +0 -1
  81. package/dist/src/ElysiaPlugin/routes/shared/writeLock.test.d.ts +0 -1
  82. package/dist/src/ElysiaPlugin/routes/storage/cdn.test.d.ts +0 -1
  83. package/dist/src/ElysiaPlugin/routes/storage/helpers.test.d.ts +0 -1
  84. package/dist/src/ElysiaPlugin/routes/storage/imageTransform.test.d.ts +0 -1
  85. package/dist/src/ElysiaPlugin/routes/storage/videoTransform.test.d.ts +0 -1
  86. package/dist/src/ElysiaPlugin/runtime.test.d.ts +0 -1
  87. package/dist/src/ElysiaPlugin/sanitizePayload.test.d.ts +0 -1
  88. package/dist/src/ElysiaPlugin/signNewAccessToken.test.d.ts +0 -1
  89. package/dist/src/ElysiaPlugin/validationError.test.d.ts +0 -1
  90. package/dist/src/Managers/Redis/keyPrefix.test.d.ts +0 -1
  91. package/dist/src/Managers/Storage/liveStorage.test.d.ts +0 -1
  92. package/dist/src/Managers/Storage/migrate.test.d.ts +0 -1
  93. package/dist/src/Managers/Storage/parseSmbUrl.test.d.ts +0 -1
  94. package/dist/src/Managers/Storage/storage.test.d.ts +0 -1
  95. package/dist/src/Services/Auth/DeviceTrust/deviceToken.test.d.ts +0 -1
  96. package/dist/src/Services/Auth/JWT/jwt.test.d.ts +0 -1
  97. package/dist/src/Services/Auth/Password/password.test.d.ts +0 -1
  98. package/dist/src/Services/Authorization/ClaimGuard/claimGuard.test.d.ts +0 -1
  99. package/dist/src/Services/Authorization/EndpointDiscovery/discovery.test.d.ts +0 -1
  100. package/dist/src/Services/Authorization/EndpointDiscovery/seed.test.d.ts +0 -1
  101. package/dist/src/Services/Authorization/Middleware/broaderGrantWins.test.d.ts +0 -1
  102. package/dist/src/Services/Authorization/Middleware/claimMatches.test.d.ts +0 -1
  103. package/dist/src/Services/Authorization/Middleware/fieldRelationGrant.test.d.ts +0 -1
  104. package/dist/src/Services/Authorization/Middleware/fieldWriteClaims.test.d.ts +0 -1
  105. package/dist/src/Services/Authorization/Middleware/jwtScopeEmbed.test.d.ts +0 -1
  106. package/dist/src/Services/Authorization/Middleware/scopeFailClosed.test.d.ts +0 -1
  107. package/dist/src/Services/Authorization/Middleware/scopeSelfFailClosed.test.d.ts +0 -1
  108. package/dist/src/Services/Authorization/SeedRunner/expandClaimPatterns.test.d.ts +0 -1
  109. package/dist/src/Services/Authorization/SeedRunner/scopeReconcile.test.d.ts +0 -1
  110. package/dist/src/Services/Backup/encryption.test.d.ts +0 -1
  111. package/dist/src/Services/Captcha/captchaAtomic.test.d.ts +0 -1
  112. package/dist/src/Services/Chat/sendMessageAtomicity.integration.test.d.ts +0 -1
  113. package/dist/src/Services/Integrations/bindings.test.d.ts +0 -1
  114. package/dist/src/Services/Integrations/collect.test.d.ts +0 -1
  115. package/dist/src/Services/Integrations/fetcher.test.d.ts +0 -1
  116. package/dist/src/Services/Integrations/lock.test.d.ts +0 -1
  117. package/dist/src/Services/Integrations/mapping.test.d.ts +0 -1
  118. package/dist/src/Services/Integrations/oauth.test.d.ts +0 -1
  119. package/dist/src/Services/Integrations/pagination.test.d.ts +0 -1
  120. package/dist/src/Services/Integrations/plan.test.d.ts +0 -1
  121. package/dist/src/Services/Integrations/referenceIds.test.d.ts +0 -1
  122. package/dist/src/Services/Integrations/revert.test.d.ts +0 -1
  123. package/dist/src/Services/Integrations/runner.test.d.ts +0 -1
  124. package/dist/src/Services/Integrations/scheduler.test.d.ts +0 -1
  125. package/dist/src/Services/Integrations/transfer.test.d.ts +0 -1
  126. package/dist/src/Services/Integrations/tree.test.d.ts +0 -1
  127. package/dist/src/Services/Integrations/writer.test.d.ts +0 -1
  128. package/dist/src/Services/Logger/ServerLogBuffer.test.d.ts +0 -1
  129. package/dist/src/Services/Logger/auditDedup.test.d.ts +0 -1
  130. package/dist/src/Services/Logger/auditGate.test.d.ts +0 -1
  131. package/dist/src/Services/Logger/auditRedact.test.d.ts +0 -1
  132. package/dist/src/Services/Logger/auditTaxonomy.test.d.ts +0 -1
  133. package/dist/src/Services/Logger/formatError.test.d.ts +0 -1
  134. package/dist/src/Services/Logger/redact.test.d.ts +0 -1
  135. package/dist/src/Services/Monitoring/collectors/memory.test.d.ts +0 -1
  136. package/dist/src/Services/OAuth/profileSync.test.d.ts +0 -1
  137. package/dist/src/Services/OAuth/providers/microsoft.test.d.ts +0 -1
  138. package/dist/src/Services/OAuth/stateStore.test.d.ts +0 -1
  139. package/dist/src/Services/Payment/Marketplace/marketplaceLedger.test.d.ts +0 -1
  140. package/dist/src/Services/Payment/Marketplace/marketplacePayout.integration.test.d.ts +0 -1
  141. package/dist/src/Services/RateLimiter/rateLimiter.integration.test.d.ts +0 -1
  142. package/dist/src/Services/RateLimiter/rateLimiter.test.d.ts +0 -1
  143. package/dist/src/Services/SchemaTables/resolveEntities.test.d.ts +0 -1
  144. package/dist/src/Services/Secrets/SecretsService.test.d.ts +0 -1
  145. package/dist/src/Services/Secrets/connectionTest.test.d.ts +0 -1
  146. package/dist/src/Services/Secrets/crypto.test.d.ts +0 -1
  147. package/dist/src/Services/Secrets/registry.test.d.ts +0 -1
  148. package/dist/src/Services/Secrets/store.test.d.ts +0 -1
  149. package/dist/src/Services/Tenant/helpers.test.d.ts +0 -1
  150. package/dist/src/Services/Tenant/isTrustedSource.test.d.ts +0 -1
  151. package/dist/src/Services/Tenant/schema.test.d.ts +0 -1
  152. package/dist/src/Services/WebAuthn/webauthnChallenge.integration.test.d.ts +0 -1
  153. package/dist/src/Shared/cron.test.d.ts +0 -1
  154. package/dist/src/Shared/cron.test.js +0 -223
  155. package/dist/src/Utils/cron.test.d.ts +0 -1
  156. package/dist/src/Utils/headerList.test.d.ts +0 -1
  157. package/dist/src/Utils/withDeadline.test.d.ts +0 -1
@@ -32,6 +32,13 @@ export function ConnectionTest({ actions, source, loading = false }) {
32
32
  * the record object is replaced but its id is not, so this never re-ran and a
33
33
  * green badge sat under an address that had never been called. Everything the
34
34
  * test actually depends on is in the key.
35
+ *
36
+ * A stored CREDENTIAL is such a setting, and it was the one left out. Clearing
37
+ * a token left the green verdict standing for a connection that would now be
38
+ * refused; the mirror image costs more of an afternoon — a red "Not reachable"
39
+ * from a 401 stayed up after the right password was saved, reading as "the
40
+ * password did not help". The masked hints are what move when a credential
41
+ * does, so they are what this watches.
35
42
  */ useEffect(()=>{
36
43
  clear();
37
44
  }, [
@@ -39,7 +46,10 @@ export function ConnectionTest({ actions, source, loading = false }) {
39
46
  source?.baseUrl,
40
47
  source?.authType,
41
48
  source?.tokenUrl,
42
- source?.tlsVerify
49
+ source?.tlsVerify,
50
+ source?.authTokenHint,
51
+ source?.oauthClientSecretHint,
52
+ source?.oauthPasswordHint
43
53
  ]);
44
54
  const handleTest = ()=>{
45
55
  if (!source) return;
@@ -69,6 +69,30 @@ export function EndpointSample({ endpoint, actions }) {
69
69
  actions.sampleEndpoint(requestedFor, params.length > 0 ? values : undefined).then((result)=>{
70
70
  if (settleElsewhere(mine, requestedFor)) return;
71
71
  store.setSample(result, requestedFor);
72
+ /**
73
+ * The ROW learns it has been tried, not just this card.
74
+ *
75
+ * The step strip counts "tried" off `endpoints[].sampleResponse`,
76
+ * because whether a call has ever been run is a fact about the call and
77
+ * not about whichever screen is open. The server stores it there. This
78
+ * panel never did, so a connection whose calls had all been sampled
79
+ * went on announcing "7 call(s), 0 tried" — seen exactly that way on a
80
+ * customer's live connection, where it read as "nobody has ever tried
81
+ * these", which was not true.
82
+ *
83
+ * Only a sample that actually returned records counts, which is the
84
+ * same rule the strip applies.
85
+ */ if (result.ok && result.recordCount > 0) {
86
+ const row = store.endpoints.find((candidate)=>candidate.id === requestedFor);
87
+ if (row) {
88
+ store.upsertEndpoint({
89
+ ...row,
90
+ sampleResponse: {
91
+ records: result.sample
92
+ }
93
+ });
94
+ }
95
+ }
72
96
  if (!result.ok && result.error) store.setError(result.error);
73
97
  }).catch((error)=>{
74
98
  if (settleElsewhere(mine, requestedFor)) return;
@@ -56,9 +56,22 @@ export function EndpointsTab({ actions }) {
56
56
  // selecting an id the list has not fetched yet made the whole editor
57
57
  // collapse to "pick one" — with the form still half-filled a moment ago.
58
58
  store.upsertEndpoint(saved);
59
- // Re-selecting drops the old sample: it described the call as it was
60
- // configured a moment ago, and the mapping would be built from it.
61
59
  store.selectEndpoint(saved.id);
60
+ /**
61
+ * The sample described the call as it was BEFORE this save.
62
+ *
63
+ * Re-selecting was supposed to drop it, and does not: `selectEndpoint`
64
+ * returns early when the id has not changed, which on a save it never has —
65
+ * it is the guard that stops re-choosing the open call from throwing away a
66
+ * sample somebody just paid a real request for. Correct there, and the
67
+ * reason nothing was cleared here.
68
+ *
69
+ * So the sample sat under an edited call: change `responseRootPath` from
70
+ * empty to `data`, save, and the card still reports "Status 200, 0 records"
71
+ * with the old body keys — and the mapping's field list is built from those
72
+ * keys. The answer on screen belonged to a request that is no longer the one
73
+ * configured.
74
+ */ store.setSample(null, null);
62
75
  setReloadToken((token)=>token + 1);
63
76
  };
64
77
  const handleDeleted = (deletedId)=>{
@@ -93,6 +93,25 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
93
93
  store.endpointsTotal = Math.max(0, store.endpointsTotal - 1);
94
94
  store.endpoints = kept;
95
95
  }
96
+ /**
97
+ * A mapping cannot outlive the call it reads from.
98
+ *
99
+ * The server deletes them — the foreign key cascades — and this list did
100
+ * not, so the step strip went on counting mappings that no longer existed
101
+ * and the IMPORT step stayed unlocked by them. Pressing it landed on an
102
+ * import whose mapping the server had already removed.
103
+ *
104
+ * Filtered here rather than at the call site because the store is what
105
+ * every one of those readings comes from; a caller that forgot would put
106
+ * the same ghost back.
107
+ */ const survivors = store.mappings.filter((mapping)=>mapping.endpointId !== id);
108
+ if (survivors.length !== store.mappings.length) {
109
+ store.mappingsTotal = Math.max(0, store.mappingsTotal - (store.mappings.length - survivors.length));
110
+ store.mappings = survivors;
111
+ if (store.selectedMappingId && !survivors.some((m)=>m.id === store.selectedMappingId)) {
112
+ store.selectedMappingId = null;
113
+ }
114
+ }
96
115
  }),
97
116
  setEndpointsSearch: (store)=>(search)=>batch(()=>{
98
117
  store.endpointsSearch = search;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nucleus-core-ts",
3
- "version": "0.9.866",
3
+ "version": "0.9.867",
4
4
  "description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
5
5
  "author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
6
6
  "license": "SEE LICENSE IN LICENSE",
package/scripts/build.ts CHANGED
@@ -113,8 +113,18 @@ async function transpileClientModules() {
113
113
 
114
114
  log.info(` Transpiling directory: ${srcDir}`)
115
115
 
116
- // Use swc to transpile - output to dist, swc will preserve directory structure
117
- await $`bunx @swc/cli ${srcDir} -d ${config.build.outDir} -C jsc.parser.syntax=typescript -C jsc.parser.tsx=true -C jsc.target=es2020 -C module.type=es6 -C jsc.transform.react.runtime=automatic`
116
+ /*
117
+ * Tests do not ship.
118
+ *
119
+ * swc transpiled the whole directory, tests included, so sixteen `.test.js`
120
+ * files went out in the published package — dead weight for every consumer,
121
+ * and a live hazard here: `bun test <path>` matches by substring, so
122
+ * `bun test fe/...` also collected `dist/fe/...` and ran the COMPILED
123
+ * copies. A test that reads a source file by relative path then failed
124
+ * from dist, where that path does not exist — a red run caused entirely by
125
+ * a stale build artefact of a test that passes.
126
+ */
127
+ await $`bunx @swc/cli ${srcDir} -d ${config.build.outDir} --ignore ${'**/*.test.ts'} --ignore ${'**/*.test.tsx'} -C jsc.parser.syntax=typescript -C jsc.parser.tsx=true -C jsc.target=es2020 -C module.type=es6 -C jsc.transform.react.runtime=automatic`
118
128
  .quiet()
119
129
  .catch((e) => {
120
130
  log.warn(`swc warning: ${e}`)
@@ -146,7 +156,7 @@ async function transpileClientModules() {
146
156
  const transpileDirs = config.transpileDirs || []
147
157
  for (const srcDir of transpileDirs) {
148
158
  log.info(` Transpiling dependency: ${srcDir}`)
149
- await $`bunx @swc/cli ${srcDir} -d ${config.build.outDir} -C jsc.parser.syntax=typescript -C jsc.parser.tsx=true -C jsc.target=es2020 -C module.type=es6 -C jsc.transform.react.runtime=automatic`
159
+ await $`bunx @swc/cli ${srcDir} -d ${config.build.outDir} --ignore ${'**/*.test.ts'} --ignore ${'**/*.test.tsx'} -C jsc.parser.syntax=typescript -C jsc.parser.tsx=true -C jsc.target=es2020 -C module.type=es6 -C jsc.transform.react.runtime=automatic`
150
160
  .quiet()
151
161
  .catch((e) => {
152
162
  log.warn(`swc warning: ${e}`)
@@ -185,6 +195,63 @@ async function generateTypes() {
185
195
  log.success('Type definitions generated (core src/ verified clean)')
186
196
  }
187
197
 
198
+ /**
199
+ * What the package must and must not contain, checked before it can be published.
200
+ *
201
+ * Both halves of this were live mistakes on the same afternoon. Tests shipped
202
+ * for months — sixteen `.test.js` files in every consumer's node_modules, and
203
+ * a trap here too, because `bun test <path>` matches by substring and so ran
204
+ * the COMPILED copies out of `dist`. Then excluding them with an unquoted glob
205
+ * silently took `src/Shared/cron.js` with it: the build still said OK, the
206
+ * declarations were still there, and the missing file would have broken the
207
+ * CONSUMER's build rather than ours — the config file next to it warns about
208
+ * exactly that failure.
209
+ *
210
+ * So the shape of the package is asserted, not assumed. A build that cannot
211
+ * satisfy this refuses to finish, which is the only moment the cost is still
212
+ * ours.
213
+ */
214
+ async function verifyPackageShape() {
215
+ log.info('Verifying what the package contains...')
216
+
217
+ /*
218
+ * Walked here rather than through `getJsFiles`, which filters out everything
219
+ * the obfuscator is told to skip — `fe/` among them. That is the right list
220
+ * for obfuscation and the wrong one for this: the sixteen test files this
221
+ * check exists to catch were all under `dist/fe`, and a check that cannot see
222
+ * them would have passed while they shipped.
223
+ */
224
+ const shipped: string[] = []
225
+ const walk = async (dir: string) => {
226
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
227
+ const full = join(dir, entry.name)
228
+ if (entry.isDirectory()) await walk(full)
229
+ else if (entry.name.endsWith('.js')) shipped.push(full)
230
+ }
231
+ }
232
+ await walk(config.build.outDir)
233
+
234
+ const tests = shipped.filter((file) => /\.test\.js$/.test(file))
235
+ if (tests.length > 0) {
236
+ log.error(`${tests.length} test file(s) would be published, e.g. ${tests[0]}`)
237
+ throw new Error('tests must not ship in the package')
238
+ }
239
+
240
+ // Every directory shipped for the frontend to import at runtime has to have
241
+ // actually produced something. An empty one is a file the consumer asks for
242
+ // and never receives.
243
+ for (const srcDir of config.transpileDirs || []) {
244
+ const outDir = join(config.build.outDir, srcDir.replace('./', ''))
245
+ const produced = shipped.filter((file) => file.includes(outDir))
246
+ if (produced.length === 0) {
247
+ log.error(`${srcDir} produced no JavaScript in ${outDir}`)
248
+ throw new Error(`${srcDir} is declared shippable but nothing was emitted for it`)
249
+ }
250
+ }
251
+
252
+ log.success(`Package shape verified (${shipped.length} files, no tests)`)
253
+ }
254
+
188
255
  async function getJsFiles(dir: string): Promise<string[]> {
189
256
  const files: string[] = []
190
257
 
@@ -346,6 +413,7 @@ async function main() {
346
413
 
347
414
  log.step(7, 7, 'Verifying...')
348
415
  await verifyBuild()
416
+ await verifyPackageShape()
349
417
 
350
418
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(2)
351
419
  console.log(`\n${GREEN}🎉 Build completed in ${elapsed}s${NC}\n`)
@@ -1,44 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { describeRow } from './RunPlanIssues';
3
- /**
4
- * A preview that names the key which collided but not the values behind it
5
- * cannot answer the only question worth asking: is this the same record listed
6
- * twice, or two records the key cannot tell apart? The first is a skip; the
7
- * second means the key is too narrow and rows are being lost.
8
- */ describe('describeRow', ()=>{
9
- test('lists the mapped columns and their values, in order', ()=>{
10
- expect(describeRow({
11
- name: 'ÜRETİM PLANLAMA MÜDÜRLÜĞÜ',
12
- slug: 'TAT-110'
13
- })).toBe('name: ÜRETİM PLANLAMA MÜDÜRLÜĞÜ, slug: TAT-110');
14
- });
15
- test('an empty column reads as a dash, not as nothing at all', ()=>{
16
- expect(describeRow({
17
- name: null,
18
- slug: 'TAT-110'
19
- })).toBe('name: —, slug: TAT-110');
20
- expect(describeRow({
21
- name: undefined,
22
- slug: 'X'
23
- })).toBe('name: —, slug: X');
24
- });
25
- test('a long value is cut rather than allowed to bury the rest', ()=>{
26
- const long = 'A'.repeat(200);
27
- const described = describeRow({
28
- description: long,
29
- slug: 'X'
30
- });
31
- expect(described.length).toBeLessThan(120);
32
- expect(described).toContain('…');
33
- expect(described).toContain('slug: X');
34
- });
35
- test('non-string values are shown rather than dropped', ()=>{
36
- expect(describeRow({
37
- displayOrder: 0,
38
- enabled: false
39
- })).toBe('displayOrder: 0, enabled: false');
40
- });
41
- test('an empty row yields an empty description rather than punctuation', ()=>{
42
- expect(describeRow({})).toBe('');
43
- });
44
- });
@@ -1,103 +0,0 @@
1
- import { describe, expect, it } from 'bun:test';
2
- import { missingRequiredCounts, suggestEmailTemplate } from './RunPlanSuggestions';
3
- /**
4
- * A preview that says "561 records are missing e-mail" has told the truth and
5
- * stopped one step short of being useful. The answer is nearly always to build
6
- * the value from the fields that ARE there, and the panel knows which those are.
7
- */ const plan = (issues)=>({
8
- issues,
9
- referenceInserts: [],
10
- warnings: []
11
- });
12
- const issue = (message, i)=>({
13
- sourceIndex: i,
14
- kind: 'missing_required',
15
- message
16
- });
17
- describe('counting what each missing field costs', ()=>{
18
- it('counts the records one field kept out', ()=>{
19
- const counts = missingRequiredCounts(plan([
20
- issue('Required field(s) empty: email', 0),
21
- issue('Required field(s) empty: email', 1)
22
- ]));
23
- expect(counts).toEqual([
24
- {
25
- field: 'email',
26
- records: 2
27
- }
28
- ]);
29
- });
30
- it('counts each field of a record that is missing two', ()=>{
31
- const counts = missingRequiredCounts(plan([
32
- issue('Required field(s) empty: email, code', 0)
33
- ]));
34
- expect(counts).toEqual([
35
- {
36
- field: 'email',
37
- records: 1
38
- },
39
- {
40
- field: 'code',
41
- records: 1
42
- }
43
- ]);
44
- });
45
- it('puts the biggest problem first', ()=>{
46
- const counts = missingRequiredCounts(plan([
47
- issue('Required field(s) empty: code', 0),
48
- issue('Required field(s) empty: email', 1),
49
- issue('Required field(s) empty: email', 2)
50
- ]));
51
- expect(counts[0]).toEqual({
52
- field: 'email',
53
- records: 2
54
- });
55
- });
56
- it('ignores issues of every other kind', ()=>{
57
- const counts = missingRequiredCounts(plan([
58
- {
59
- sourceIndex: 0,
60
- kind: 'duplicate_in_batch',
61
- message: 'twice'
62
- }
63
- ]));
64
- expect(counts).toEqual([]);
65
- });
66
- });
67
- describe('the address it suggests building', ()=>{
68
- const mapping = (sources)=>({
69
- fieldMappings: sources.map((source)=>({
70
- source,
71
- target: source
72
- }))
73
- });
74
- it('is built from the fields this source really sends', ()=>{
75
- expect(suggestEmailTemplate(mapping([
76
- 'name',
77
- 'surname',
78
- 'employeeNo'
79
- ]))).toBe('{name}.{surname}@example.com');
80
- });
81
- it('recognises the other names those fields go by', ()=>{
82
- expect(suggestEmailTemplate(mapping([
83
- 'firstName',
84
- 'lastName'
85
- ]))).toBe('{firstName}.{lastName}@example.com');
86
- });
87
- it('offers what it can when only one of the two is there', ()=>{
88
- expect(suggestEmailTemplate(mapping([
89
- 'surname'
90
- ]))).toBe('{surname}@example.com');
91
- });
92
- it('suggests nothing rather than nonsense when there is no name at all', ()=>{
93
- expect(suggestEmailTemplate(mapping([
94
- 'employeeNo',
95
- 'departmentCode'
96
- ]))).toBeNull();
97
- });
98
- it('ignores a source that is already a template', ()=>{
99
- expect(suggestEmailTemplate(mapping([
100
- '{a}-{b}'
101
- ]))).toBeNull();
102
- });
103
- });
@@ -1,198 +0,0 @@
1
- import { describe, expect, it, test } from 'bun:test';
2
- import { draftFromSource, draftPatchFromSource, draftToSource, emptyConnectionDraft, validateConnectionDraft } from './connectionDraft';
3
- /**
4
- * Found in production, creating the first connection: the form said "Empty
5
- * means the server default" under the timeout, and saving with it empty
6
- * answered "Missing required field: timeout_ms".
7
- *
8
- * `timeout_ms` is NOT NULL with a default of 30000. An explicit null does not
9
- * fall back to a column default — it overrides it and violates the constraint.
10
- * The distinction between "send null" and "do not send the key" is the whole
11
- * bug, so it is what these assert.
12
- */ const filled = ()=>({
13
- ...emptyConnectionDraft,
14
- name: 'TatMetal HR',
15
- slug: 'tatmetal-hr',
16
- baseUrl: 'https://online.tatmetal.com/hr-service'
17
- });
18
- describe('draftToSource — columns that are NOT NULL with a default', ()=>{
19
- test('an empty timeout is omitted, not sent as null', ()=>{
20
- const payload = draftToSource(filled());
21
- expect('timeoutMs' in payload).toBe(false);
22
- expect(payload.timeoutMs).toBeUndefined();
23
- });
24
- test('a timeout that was typed is sent as a number', ()=>{
25
- const payload = draftToSource({
26
- ...filled(),
27
- timeoutMs: '45000'
28
- });
29
- expect(payload.timeoutMs).toBe(45000);
30
- });
31
- test('whitespace and nonsense are treated as empty rather than as null', ()=>{
32
- for (const value of [
33
- ' ',
34
- 'abc',
35
- '0',
36
- '-1'
37
- ]){
38
- const payload = draftToSource({
39
- ...filled(),
40
- timeoutMs: value
41
- });
42
- expect(`${value} → ${'timeoutMs' in payload}`).toBe(`${value} → false`);
43
- }
44
- });
45
- test('the other NOT NULL columns always carry a concrete value', ()=>{
46
- const payload = draftToSource(filled());
47
- expect(payload.authType).toBe('none');
48
- expect(payload.tlsVerify).toBe(true);
49
- expect(payload.enabled).toBe(true);
50
- });
51
- });
52
- describe('draftToSource — fields the chosen auth type does not use', ()=>{
53
- test('a bearer connection clears the OAuth settings rather than leaving them', ()=>{
54
- const payload = draftToSource({
55
- ...filled(),
56
- authType: 'bearer',
57
- tokenUrl: 'https://example.com/token',
58
- oauthClientId: 'left-behind'
59
- });
60
- expect(payload.tokenUrl).toBeNull();
61
- expect(payload.oauthClientId).toBeNull();
62
- expect(payload.oauthGrantType).toBeNull();
63
- });
64
- test('a username is kept only for the password grant', ()=>{
65
- const base = {
66
- ...filled(),
67
- authType: 'oauth2',
68
- oauthUsername: 'svc-account'
69
- };
70
- expect(draftToSource({
71
- ...base,
72
- oauthGrantType: 'password'
73
- }).oauthUsername).toBe('svc-account');
74
- expect(draftToSource({
75
- ...base,
76
- oauthGrantType: 'client_credentials'
77
- }).oauthUsername).toBeNull();
78
- });
79
- });
80
- describe('validateConnectionDraft', ()=>{
81
- test('the three fields a connection cannot do without are required', ()=>{
82
- const errors = validateConnectionDraft(emptyConnectionDraft);
83
- expect(errors.name).toBeDefined();
84
- expect(errors.slug).toBeDefined();
85
- expect(errors.baseUrl).toBeDefined();
86
- });
87
- test('a filled draft passes', ()=>{
88
- expect(validateConnectionDraft(filled())).toEqual({});
89
- });
90
- test('plain http is accepted — an in-cluster service is a normal target', ()=>{
91
- const errors = validateConnectionDraft({
92
- ...filled(),
93
- baseUrl: 'http://hr-service:8080'
94
- });
95
- expect(errors.baseUrl).toBeUndefined();
96
- });
97
- test('a non-numeric timeout is refused before it can reach the server', ()=>{
98
- expect(validateConnectionDraft({
99
- ...filled(),
100
- timeoutMs: 'soon'
101
- }).timeoutMs).toBeDefined();
102
- expect(validateConnectionDraft({
103
- ...filled(),
104
- timeoutMs: ''
105
- }).timeoutMs).toBeUndefined();
106
- });
107
- });
108
- describe('draftFromSource', ()=>{
109
- test('a saved timeout comes back into the field', ()=>{
110
- const draft = draftFromSource({
111
- id: 'a',
112
- name: 'n',
113
- slug: 's',
114
- baseUrl: 'https://x.test',
115
- authType: 'none',
116
- timeoutMs: 45000
117
- });
118
- expect(draft.timeoutMs).toBe('45000');
119
- });
120
- test('a round trip through the form does not invent a change', ()=>{
121
- const source = {
122
- id: 'a',
123
- name: 'n',
124
- slug: 's',
125
- baseUrl: 'https://x.test',
126
- authType: 'none',
127
- tlsVerify: true,
128
- enabled: true,
129
- timeoutMs: 30000
130
- };
131
- expect(draftToSource(draftFromSource(source), 'a').timeoutMs).toBe(30000);
132
- });
133
- });
134
- /**
135
- * What a save is allowed to correct in the boxes.
136
- *
137
- * After an edit the form showed what was TYPED, not what was stored: emptying
138
- * the timeout left the box empty while the connection kept timing out at the
139
- * stored value, and a trailing space in the name was trimmed on the way out so
140
- * the switcher above and the box below disagreed until F5. The reply has to be
141
- * read back — but only the parts of it that are actually there, because a
142
- * field-scoped patch claim strips columns out, and `draftFromSource` fills a
143
- * default for every column it does not find. Blanking a required box is worse
144
- * than leaving it stale.
145
- */ describe('the boxes a saved record corrects', ()=>{
146
- const stored = {
147
- id: 's1',
148
- name: 'Acme',
149
- slug: 'acme',
150
- baseUrl: 'https://acme.test',
151
- timeoutMs: 30000
152
- };
153
- it('takes the server’s value for a field it answered with', ()=>{
154
- // Typed "Acme " with a trailing space; stored trimmed.
155
- expect(draftPatchFromSource(stored).name).toBe('Acme');
156
- });
157
- /** The case this exists for: the box says empty, the server says 30000. */ it('corrects a timeout the server kept when the box was emptied', ()=>{
158
- expect(draftPatchFromSource(stored).timeoutMs).toBe('30000');
159
- });
160
- it('says nothing at all about a field the reply left out', ()=>{
161
- const patch = draftPatchFromSource({
162
- id: 's1',
163
- name: 'Acme'
164
- });
165
- expect(patch).toEqual({
166
- name: 'Acme'
167
- });
168
- expect('slug' in patch).toBe(false);
169
- expect('timeoutMs' in patch).toBe(false);
170
- });
171
- /**
172
- * The failure this shape prevents: applied over a real draft, a partial reply
173
- * must correct what it mentions and leave everything else alone.
174
- */ it('leaves the rest of the draft untouched when applied over it', ()=>{
175
- const draft = {
176
- ...emptyConnectionDraft,
177
- name: 'Acme ',
178
- slug: 'acme',
179
- baseUrl: 'https://a.test'
180
- };
181
- const next = {
182
- ...draft,
183
- ...draftPatchFromSource({
184
- id: 's1',
185
- name: 'Acme'
186
- })
187
- };
188
- expect(next.name).toBe('Acme');
189
- expect(next.slug).toBe('acme');
190
- expect(next.baseUrl).toBe('https://a.test');
191
- });
192
- /** null is an answer — "no timeout" — not an absence. */ it('treats a null the server returned as a value, not as silence', ()=>{
193
- expect(draftPatchFromSource({
194
- id: 's1',
195
- timeoutMs: null
196
- }).timeoutMs).toBe('');
197
- });
198
- });