@stripe/extensibility-custom-objects-tools 0.40.0 → 0.41.1

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.
@@ -383,7 +383,7 @@ function injectRuntimeImports(sourceFile, hasDescriptors = false) {
383
383
  }
384
384
  if (hasDescriptors) {
385
385
  const existingRuntimeStdlibImport = sourceFile.getImportDeclarations().find(
386
- (decl) => decl.getModuleSpecifierValue() === "@stripe/extensibility-sdk/stdlib" && !decl.isTypeOnly()
386
+ (decl) => decl.getModuleSpecifierValue() === "@stripe/extensibility-sdk" && !decl.isTypeOnly()
387
387
  );
388
388
  if (existingRuntimeStdlibImport) {
389
389
  const namedImports = existingRuntimeStdlibImport.getNamedImports().map((ni) => ni.getName());
@@ -394,7 +394,7 @@ function injectRuntimeImports(sourceFile, hasDescriptors = false) {
394
394
  }
395
395
  } else {
396
396
  sourceFile.insertImportDeclaration(0, {
397
- moduleSpecifier: "@stripe/extensibility-sdk/stdlib",
397
+ moduleSpecifier: "@stripe/extensibility-sdk",
398
398
  namedImports: [...STDLIB_IMPORTS]
399
399
  });
400
400
  }
package/dist/build/cli.js CHANGED
@@ -382,7 +382,7 @@ function injectRuntimeImports(sourceFile, hasDescriptors = false) {
382
382
  }
383
383
  if (hasDescriptors) {
384
384
  const existingRuntimeStdlibImport = sourceFile.getImportDeclarations().find(
385
- (decl) => decl.getModuleSpecifierValue() === "@stripe/extensibility-sdk/stdlib" && !decl.isTypeOnly()
385
+ (decl) => decl.getModuleSpecifierValue() === "@stripe/extensibility-sdk" && !decl.isTypeOnly()
386
386
  );
387
387
  if (existingRuntimeStdlibImport) {
388
388
  const namedImports = existingRuntimeStdlibImport.getNamedImports().map((ni) => ni.getName());
@@ -393,7 +393,7 @@ function injectRuntimeImports(sourceFile, hasDescriptors = false) {
393
393
  }
394
394
  } else {
395
395
  sourceFile.insertImportDeclaration(0, {
396
- moduleSpecifier: "@stripe/extensibility-sdk/stdlib",
396
+ moduleSpecifier: "@stripe/extensibility-sdk",
397
397
  namedImports: [...STDLIB_IMPORTS]
398
398
  });
399
399
  }
@@ -7676,7 +7676,7 @@ function injectRuntimeImports(sourceFile, hasDescriptors = false) {
7676
7676
  }
7677
7677
  if (hasDescriptors) {
7678
7678
  const existingRuntimeStdlibImport = sourceFile.getImportDeclarations().find(
7679
- (decl) => decl.getModuleSpecifierValue() === "@stripe/extensibility-sdk/stdlib" && !decl.isTypeOnly()
7679
+ (decl) => decl.getModuleSpecifierValue() === "@stripe/extensibility-sdk" && !decl.isTypeOnly()
7680
7680
  );
7681
7681
  if (existingRuntimeStdlibImport) {
7682
7682
  const namedImports = existingRuntimeStdlibImport.getNamedImports().map((ni) => ni.getName());
@@ -7687,7 +7687,7 @@ function injectRuntimeImports(sourceFile, hasDescriptors = false) {
7687
7687
  }
7688
7688
  } else {
7689
7689
  sourceFile.insertImportDeclaration(0, {
7690
- moduleSpecifier: "@stripe/extensibility-sdk/stdlib",
7690
+ moduleSpecifier: "@stripe/extensibility-sdk",
7691
7691
  namedImports: [...STDLIB_IMPORTS]
7692
7692
  });
7693
7693
  }
@@ -8137,6 +8137,7 @@ function buildCustomObjectPackage(options, dependencies = {}) {
8137
8137
  appendTransformPlan(transformPlans, target.modulePath, analysis.transformPlan);
8138
8138
  }
8139
8139
  if (analysis.buildResult) {
8140
+ diagnostics.push(...validateCustomObjectBuildResult(analysis.buildResult));
8140
8141
  objects[toObjectKey(target)] = analysis.buildResult;
8141
8142
  }
8142
8143
  }
@@ -8177,6 +8178,377 @@ function buildCustomObjectPackage(options, dependencies = {}) {
8177
8178
  function toObjectKey(target) {
8178
8179
  return `${target.modulePath}#${target.exportName}`;
8179
8180
  }
8181
+ function validateCustomObjectBuildResult(buildResult) {
8182
+ const diagnostics = [];
8183
+ const fieldsSchema = resolveTopLevelSchema(buildResult.fields.jsonSchema);
8184
+ validateTopLevelObjectShape(fieldsSchema, buildResult, "fields_payload", diagnostics);
8185
+ validatePropertyMap(fieldsSchema, buildResult, diagnostics, "field");
8186
+ for (const [actionApiName, action] of Object.entries(buildResult.actions)) {
8187
+ if (action.input?.jsonSchema) {
8188
+ const inputSchema = resolveTopLevelSchema(action.input.jsonSchema);
8189
+ validateTopLevelObjectShape(
8190
+ inputSchema,
8191
+ buildResult,
8192
+ "action_input_payload",
8193
+ diagnostics,
8194
+ actionApiName
8195
+ );
8196
+ validatePropertyMap(
8197
+ inputSchema,
8198
+ buildResult,
8199
+ diagnostics,
8200
+ "action_input_field",
8201
+ actionApiName
8202
+ );
8203
+ }
8204
+ const outputSchema = resolveTopLevelSchema(action.output.jsonSchema);
8205
+ validateTopLevelObjectShape(
8206
+ outputSchema,
8207
+ buildResult,
8208
+ "action_output_payload",
8209
+ diagnostics,
8210
+ actionApiName
8211
+ );
8212
+ validatePropertyMap(
8213
+ outputSchema,
8214
+ buildResult,
8215
+ diagnostics,
8216
+ "action_output_field",
8217
+ actionApiName
8218
+ );
8219
+ }
8220
+ return diagnostics;
8221
+ }
8222
+ function validatePropertyMap(schema, buildResult, diagnostics, kind, actionApiName) {
8223
+ if (!schema?.properties) {
8224
+ return;
8225
+ }
8226
+ const defs = schema.$defs ?? {};
8227
+ for (const [propertyName, propSchema] of Object.entries(schema.properties)) {
8228
+ const resolved = resolveSchemaRef(propSchema, defs);
8229
+ validatePropertySchema(
8230
+ resolved,
8231
+ buildResult,
8232
+ diagnostics,
8233
+ buildPropertyScope(kind, propertyName, actionApiName)
8234
+ );
8235
+ }
8236
+ }
8237
+ function buildPropertyScope(kind, propertyName, actionApiName) {
8238
+ switch (kind) {
8239
+ case "field":
8240
+ return { kind, propertyName };
8241
+ case "action_input_field":
8242
+ case "action_output_field":
8243
+ return {
8244
+ kind,
8245
+ actionApiName: actionApiName ?? "(unknown action)",
8246
+ propertyName
8247
+ };
8248
+ }
8249
+ }
8250
+ function validateTopLevelObjectShape(schema, buildResult, kind, diagnostics, actionApiName) {
8251
+ const hasProperties = schema.properties !== void 0 && Object.keys(schema.properties).length > 0;
8252
+ const looksObjectLike = schema.type === "object" || hasProperties;
8253
+ if (schema.additionalProperties !== void 0 && schema.additionalProperties !== false) {
8254
+ diagnostics.push(
8255
+ createUnsupportedSchemaDiagnostic(
8256
+ kind,
8257
+ buildResult,
8258
+ buildPayloadScope(kind, actionApiName),
8259
+ schema,
8260
+ getTopLevelMapShapeDetail(kind)
8261
+ )
8262
+ );
8263
+ return;
8264
+ }
8265
+ if (looksObjectLike) {
8266
+ return;
8267
+ }
8268
+ diagnostics.push(
8269
+ createUnsupportedSchemaDiagnostic(
8270
+ kind,
8271
+ buildResult,
8272
+ buildPayloadScope(kind, actionApiName),
8273
+ schema,
8274
+ getTopLevelObjectShapeDetail(kind)
8275
+ )
8276
+ );
8277
+ }
8278
+ function buildPayloadScope(kind, actionApiName) {
8279
+ switch (kind) {
8280
+ case "fields_payload":
8281
+ return { kind };
8282
+ case "action_input_payload":
8283
+ case "action_output_payload":
8284
+ return { kind, actionApiName: actionApiName ?? "(unknown action)" };
8285
+ }
8286
+ }
8287
+ function getTopLevelObjectShapeDetail(kind) {
8288
+ switch (kind) {
8289
+ case "fields_payload":
8290
+ return "Custom object fields must be object-shaped.";
8291
+ case "action_input_payload":
8292
+ case "action_output_payload":
8293
+ return "Action inputs and outputs must be object-shaped for custom objects.";
8294
+ }
8295
+ }
8296
+ function getTopLevelMapShapeDetail(kind) {
8297
+ switch (kind) {
8298
+ case "fields_payload":
8299
+ return "Custom object fields must declare named fields. Record<string, T> and other map-shaped field payloads are not supported for custom objects.";
8300
+ case "action_input_payload":
8301
+ case "action_output_payload":
8302
+ return "Action inputs and outputs must declare named fields. Record<string, T> and other map-shaped payloads are not supported for custom objects.";
8303
+ }
8304
+ }
8305
+ function validatePropertySchema(schema, buildResult, diagnostics, scope) {
8306
+ if (hasNonStringEnumValues(schema)) {
8307
+ diagnostics.push(
8308
+ createUnsupportedSchemaDiagnostic(
8309
+ scope.kind,
8310
+ buildResult,
8311
+ scope,
8312
+ schema,
8313
+ "Only string enums are supported for custom objects."
8314
+ )
8315
+ );
8316
+ return;
8317
+ }
8318
+ if (schema.format === "decimal") {
8319
+ diagnostics.push(
8320
+ createUnsupportedSchemaDiagnostic(
8321
+ scope.kind,
8322
+ buildResult,
8323
+ scope,
8324
+ schema,
8325
+ "Decimal fields are not supported for custom objects."
8326
+ )
8327
+ );
8328
+ return;
8329
+ }
8330
+ if (schema.type === "number") {
8331
+ diagnostics.push(
8332
+ createUnsupportedSchemaDiagnostic(
8333
+ scope.kind,
8334
+ buildResult,
8335
+ scope,
8336
+ schema,
8337
+ "Number fields are not supported for custom objects. Use Integer for whole numbers."
8338
+ )
8339
+ );
8340
+ return;
8341
+ }
8342
+ if (schema.type === "array") {
8343
+ diagnostics.push(
8344
+ createUnsupportedSchemaDiagnostic(
8345
+ scope.kind,
8346
+ buildResult,
8347
+ scope,
8348
+ schema,
8349
+ "Array fields are not supported for custom objects."
8350
+ )
8351
+ );
8352
+ return;
8353
+ }
8354
+ const refTarget = schema.type === "object" ? getRefTargetType(schema) : null;
8355
+ if (schema.type === "object" && refTarget === null) {
8356
+ diagnostics.push(
8357
+ createUnsupportedSchemaDiagnostic(
8358
+ scope.kind,
8359
+ buildResult,
8360
+ scope,
8361
+ schema,
8362
+ "Nested object fields are not supported for custom objects. Use Ref<T> for object references."
8363
+ )
8364
+ );
8365
+ return;
8366
+ }
8367
+ validateNumericBoundary(schema, buildResult, diagnostics, scope);
8368
+ validateDefaultValue(schema, buildResult, diagnostics, scope);
8369
+ if (!isSupportedCustomObjectSchema(schema, refTarget)) {
8370
+ diagnostics.push(
8371
+ createUnsupportedSchemaDiagnostic(
8372
+ scope.kind,
8373
+ buildResult,
8374
+ scope,
8375
+ schema,
8376
+ "This schema shape is not supported for custom objects."
8377
+ )
8378
+ );
8379
+ }
8380
+ }
8381
+ function validateNumericBoundary(schema, buildResult, diagnostics, scope) {
8382
+ const boundaries = [
8383
+ ["minimum", schema.minimum],
8384
+ ["exclusiveMinimum", schema.exclusiveMinimum],
8385
+ ["maximum", schema.maximum],
8386
+ ["exclusiveMaximum", schema.exclusiveMaximum]
8387
+ ];
8388
+ for (const [name, value] of boundaries) {
8389
+ if (value === void 0 || Number.isInteger(value)) {
8390
+ continue;
8391
+ }
8392
+ diagnostics.push(
8393
+ createUnsupportedSchemaDiagnostic(
8394
+ scope.kind,
8395
+ buildResult,
8396
+ scope,
8397
+ schema,
8398
+ `${name}=${String(value)} is not representable for custom objects; only integer numeric boundaries are supported.`
8399
+ )
8400
+ );
8401
+ }
8402
+ }
8403
+ function validateDefaultValue(schema, buildResult, diagnostics, scope) {
8404
+ if (schema.type === "integer" && schema.default !== void 0 && (typeof schema.default !== "number" || !Number.isInteger(schema.default))) {
8405
+ diagnostics.push(
8406
+ createUnsupportedSchemaDiagnostic(
8407
+ scope.kind,
8408
+ buildResult,
8409
+ scope,
8410
+ schema,
8411
+ `default=${JSON.stringify(schema.default)} is not representable for custom objects; integer fields require integer default values.`
8412
+ )
8413
+ );
8414
+ }
8415
+ }
8416
+ function isSupportedCustomObjectSchema(schema, refTarget) {
8417
+ if (extractEnumValues(schema) !== null) {
8418
+ return true;
8419
+ }
8420
+ if (refTarget !== null) {
8421
+ return true;
8422
+ }
8423
+ switch (schema.type) {
8424
+ case "string":
8425
+ case "integer":
8426
+ case "boolean":
8427
+ return true;
8428
+ default:
8429
+ return false;
8430
+ }
8431
+ }
8432
+ function createUnsupportedSchemaDiagnostic(kind, buildResult, scope, schema, detail) {
8433
+ const { modulePath, className } = buildResult.platformMetadata;
8434
+ return {
8435
+ severity: "error",
8436
+ code: getUnsupportedSchemaDiagnosticCode(kind),
8437
+ message: `${describeValidationScope(scope)} on "${className}" in "${modulePath}" is not supported for custom objects (${describeSchema(schema)}). ${detail}`,
8438
+ modulePath,
8439
+ exportName: className
8440
+ };
8441
+ }
8442
+ function getUnsupportedSchemaDiagnosticCode(kind) {
8443
+ switch (kind) {
8444
+ case "fields_payload":
8445
+ case "field":
8446
+ return "UNSUPPORTED_FIELD_SCHEMA";
8447
+ case "action_input_field":
8448
+ case "action_input_payload":
8449
+ return "UNSUPPORTED_ACTION_INPUT_SCHEMA";
8450
+ case "action_output_field":
8451
+ case "action_output_payload":
8452
+ return "UNSUPPORTED_ACTION_OUTPUT_SCHEMA";
8453
+ }
8454
+ }
8455
+ function describeValidationScope(scope) {
8456
+ switch (scope.kind) {
8457
+ case "fields_payload":
8458
+ return "Custom object fields schema";
8459
+ case "field":
8460
+ return `Field "${scope.propertyName}"`;
8461
+ case "action_input_field":
8462
+ return `Action "${scope.actionApiName}" input field "${scope.propertyName}"`;
8463
+ case "action_output_field":
8464
+ return `Action "${scope.actionApiName}" output field "${scope.propertyName}"`;
8465
+ case "action_input_payload":
8466
+ return `Action "${scope.actionApiName}" input payload`;
8467
+ case "action_output_payload":
8468
+ return `Action "${scope.actionApiName}" output payload`;
8469
+ }
8470
+ }
8471
+ function describeSchema(schema) {
8472
+ if (Array.isArray(schema.type)) {
8473
+ return `type: [${schema.type.join(", ")}]`;
8474
+ }
8475
+ if (schema.type === "string" && typeof schema.format === "string") {
8476
+ return `type: string, format: ${schema.format}`;
8477
+ }
8478
+ if (schema.type === "object") {
8479
+ return "type: object";
8480
+ }
8481
+ if (schema.type !== void 0) {
8482
+ return `type: ${schema.type}`;
8483
+ }
8484
+ if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
8485
+ return "anyOf union";
8486
+ }
8487
+ if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {
8488
+ return "oneOf union";
8489
+ }
8490
+ return "untyped schema";
8491
+ }
8492
+ function resolveSchemaRef(schema, defs) {
8493
+ const ref = schema.$ref;
8494
+ if (!ref) return schema;
8495
+ if (!ref.startsWith("#/$defs/")) return schema;
8496
+ const defName = ref.slice("#/$defs/".length);
8497
+ const defSchema = defs[defName];
8498
+ if (!defSchema) return schema;
8499
+ const { $ref: _, ...rest } = schema;
8500
+ return { ...defSchema, ...rest };
8501
+ }
8502
+ function resolveTopLevelSchema(schema) {
8503
+ return resolveSchemaRef(schema, schema.$defs ?? {});
8504
+ }
8505
+ function getRefTargetType(schema) {
8506
+ if (!schema.properties) return null;
8507
+ const properties = schema.properties;
8508
+ const realProps = Object.keys(properties).filter((key) => !key.startsWith("__"));
8509
+ const allowedProps = /* @__PURE__ */ new Set(["id", "type", "url"]);
8510
+ if (realProps.length === 0 || realProps.some((key) => !allowedProps.has(key))) {
8511
+ return null;
8512
+ }
8513
+ const idProp = properties.id;
8514
+ const typeProp = properties.type;
8515
+ if (!idProp || !typeProp) return null;
8516
+ if (idProp.type !== "string") return null;
8517
+ const requiredSet = new Set(schema.required ?? []);
8518
+ if (!requiredSet.has("id") || !requiredSet.has("type")) return null;
8519
+ return extractSingleLiteral(typeProp);
8520
+ }
8521
+ function extractSingleLiteral(schema) {
8522
+ if (Array.isArray(schema.enum) && schema.enum.length === 1 && typeof schema.enum[0] === "string") {
8523
+ return schema.enum[0];
8524
+ }
8525
+ if (Array.isArray(schema.oneOf) && schema.oneOf.length === 1 && schema.oneOf.every(
8526
+ (item) => "const" in item && typeof item.const === "string"
8527
+ )) {
8528
+ const values = schema.oneOf.map(
8529
+ (item) => String(item.const)
8530
+ );
8531
+ return values[0] ?? null;
8532
+ }
8533
+ return null;
8534
+ }
8535
+ function extractEnumValues(schema) {
8536
+ if (Array.isArray(schema.enum) && schema.enum.every((value) => typeof value === "string")) {
8537
+ return schema.enum.map(String);
8538
+ }
8539
+ if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0 && schema.oneOf.every(
8540
+ (item) => "const" in item && typeof item.const === "string"
8541
+ )) {
8542
+ return schema.oneOf.map((item) => String(item.const));
8543
+ }
8544
+ return null;
8545
+ }
8546
+ function hasNonStringEnumValues(schema) {
8547
+ if (Array.isArray(schema.enum) && schema.enum.length > 0) {
8548
+ return schema.enum.some((value) => typeof value !== "string");
8549
+ }
8550
+ return Array.isArray(schema.oneOf) && schema.oneOf.length > 0 && schema.oneOf.every((item) => "const" in item) && schema.oneOf.some((item) => typeof item.const !== "string");
8551
+ }
8180
8552
  var PRIMARY_FIELD_TAG_RE = /@primaryField\s*(?:$|\*)/m;
8181
8553
  var SECONDARY_FIELD_TAG_RE = /@secondaryField\s*(?:$|\*)/m;
8182
8554
  var API_NAME_TAG_RE = /@apiName\s+(\S+)/;
@@ -8729,6 +9101,14 @@ async function main() {
8729
9101
  );
8730
9102
  }
8731
9103
  const result = buildCustomObjectPackage({ targets: parsed });
9104
+ const errorDiagnostics = result.diagnostics.filter(
9105
+ (diagnostic) => diagnostic.severity === "error"
9106
+ );
9107
+ if (errorDiagnostics.length > 0) {
9108
+ throw new Error(
9109
+ errorDiagnostics.map((diagnostic) => diagnostic.message).join("\n")
9110
+ );
9111
+ }
8732
9112
  const output = {
8733
9113
  schemas: result.objects,
8734
9114
  diagnostics: result.diagnostics