@deadair/plugin-sdk 0.53.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1533,6 +1533,40 @@ the whole row stays fillable until they say what kind of thing it is. A `secret`
1533
1533
  cell that stops applying keeps whatever is stored; nothing here deletes a
1534
1534
  credential.
1535
1535
 
1536
+ ### A credential that goes to an address the operator types
1537
+
1538
+ A secret is write-only, and a save that leaves one out keeps what is stored. That
1539
+ is what lets an operator change one setting without retyping every key, and it is
1540
+ also what would let anybody allowed to save the form point the address at a server
1541
+ of their own and collect the key the station sends there. So a secret names the
1542
+ address it is sent to:
1543
+
1544
+ ```ts
1545
+ { key: 'baseUrl', label: 'Server address', type: 'url', required: true },
1546
+ { key: 'apiKey', label: 'API key', type: 'secret', sentTo: 'baseUrl' },
1547
+ ```
1548
+
1549
+ and a column does the same inside its list:
1550
+
1551
+ ```ts
1552
+ { key: 'baseUrl', label: 'Address', type: 'url' },
1553
+ { key: 'apiKey', label: 'API key', type: 'secret', sentTo: 'baseUrl' },
1554
+ ```
1555
+
1556
+ With it declared, the host refuses a save that keeps the stored secret while
1557
+ moving that address to another origin (scheme, host or port), and the operator is
1558
+ asked to type the secret again. A changed path on the same server is not a move, a
1559
+ secret typed or cleared in the same save is never refused, and on a row the
1560
+ address is compared with the same row as it was stored. An empty field counts as
1561
+ its `default`.
1562
+
1563
+ Declare it on every secret that goes to an address the operator supplies. Leave it
1564
+ off one that goes to a fixed vendor whatever the form says (a token for a public
1565
+ API whose host is in your manifest): there is no address to move. `sentTo` must
1566
+ name a `url` or `string` field, or a column in the same list; the manifest schema
1567
+ refuses one that names anything else, because a declaration pointing at nothing
1568
+ would be a protection that is silently off.
1569
+
1536
1570
  ### Rows to start from
1537
1571
 
1538
1572
  Where most rows describe one of a few well-known things, `presets` offers them:
package/dist/index.js CHANGED
@@ -1074,6 +1074,23 @@ function parseRows(raw) {
1074
1074
  }
1075
1075
  }
1076
1076
  __name(parseRows, "parseRows");
1077
+ function sentToProblems(fields) {
1078
+ const isAddress = /* @__PURE__ */ __name((type) => type === "url" || type === "string", "isAddress");
1079
+ const problems = [];
1080
+ for (const field of fields) {
1081
+ if (field.sentTo !== void 0 && !isAddress(fields.find((candidate) => candidate.key === field.sentTo)?.type)) {
1082
+ problems.push(`"${field.key}" is sent to "${field.sentTo}", which is not a url or string field in this form`);
1083
+ }
1084
+ const columns = field.columns ?? [];
1085
+ for (const column of columns) {
1086
+ if (column.sentTo !== void 0 && !isAddress(columns.find((candidate) => candidate.key === column.sentTo)?.type)) {
1087
+ problems.push(`"${field.key}.${column.key}" is sent to "${column.sentTo}", which is not a url or string column in this list`);
1088
+ }
1089
+ }
1090
+ }
1091
+ return problems;
1092
+ }
1093
+ __name(sentToProblems, "sentToProblems");
1077
1094
  var configFieldOptionSchema = z.object({
1078
1095
  value: z.string(),
1079
1096
  label: z.string()
@@ -1135,7 +1152,8 @@ var configFieldColumnSchema = z.object({
1135
1152
  // one, and a schema that refuses what the contract accepts is the drift the generated-output
1136
1153
  // rule exists to prevent; values without a target are documented as ignored instead.
1137
1154
  dependsOn: z.string().min(1).optional(),
1138
- dependsOnValues: z.array(z.string().min(1)).optional()
1155
+ dependsOnValues: z.array(z.string().min(1)).optional(),
1156
+ sentTo: z.string().min(1).optional()
1139
1157
  });
1140
1158
  var configFieldPresetSchema = z.object({
1141
1159
  label: z.string().min(1),
@@ -1163,7 +1181,10 @@ var configFieldSchema = z.object({
1163
1181
  columns: z.array(configFieldColumnSchema).optional(),
1164
1182
  presets: z.array(configFieldPresetSchema).optional(),
1165
1183
  dependsOn: z.string().optional(),
1166
- rangeWith: z.string().optional()
1184
+ rangeWith: z.string().optional(),
1185
+ // Named here for the reason `dependsOn` is named on a column: the loader keeps the zod OUTPUT,
1186
+ // and a declaration stripped at load is a protection that is silently not there.
1187
+ sentTo: z.string().min(1).optional()
1167
1188
  });
1168
1189
 
1169
1190
  // src/plugin.config.read.ts
@@ -1341,7 +1362,13 @@ var pluginManifestSchema = z3.object({
1341
1362
  homepage: z3.string().optional(),
1342
1363
  icon: z3.string().optional(),
1343
1364
  permissions: pluginPermissionsSchema,
1344
- configFields: z3.array(configFieldSchema),
1365
+ // A `sentTo` naming nothing is refused here rather than ignored: see `sentToProblems`.
1366
+ configFields: z3.array(configFieldSchema).superRefine((fields, ctx) => {
1367
+ for (const message of sentToProblems(fields)) ctx.addIssue({
1368
+ code: "custom",
1369
+ message
1370
+ });
1371
+ }),
1345
1372
  configSchema: z3.custom(isZodSchema, {
1346
1373
  message: "configSchema must be a zod schema"
1347
1374
  })
@@ -1431,6 +1458,7 @@ export {
1431
1458
  readRowSecret,
1432
1459
  retryAfterMs,
1433
1460
  rowSecretKey,
1461
+ sentToProblems,
1434
1462
  sentencesOf,
1435
1463
  sentencesWithin,
1436
1464
  toPluginError,