@stripe/extensibility-dev-tools 0.23.5
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/LICENSE.md +19 -0
- package/dist/bin/build-custom-object-definitions.cjs +2111 -0
- package/dist/bin/build-custom-object-definitions.d.ts +11 -0
- package/dist/bin/build-custom-object-definitions.d.ts.map +1 -0
- package/dist/bin/build-custom-object-definitions.js +2088 -0
- package/dist/bin/create-upload-image.cjs +2136 -0
- package/dist/bin/create-upload-image.d.ts +17 -0
- package/dist/bin/create-upload-image.d.ts.map +1 -0
- package/dist/bin/create-upload-image.js +2113 -0
- package/dist/bin/dev-tools-rpc.cjs +3874 -0
- package/dist/bin/dev-tools-rpc.d.ts +3 -0
- package/dist/bin/dev-tools-rpc.d.ts.map +1 -0
- package/dist/bin/dev-tools-rpc.js +3864 -0
- package/dist/bin/gen-schemas.cjs +20739 -0
- package/dist/bin/gen-schemas.d.ts +8 -0
- package/dist/bin/gen-schemas.d.ts.map +1 -0
- package/dist/bin/gen-schemas.js +20715 -0
- package/dist/bin/gen-workspace.cjs +3847 -0
- package/dist/bin/gen-workspace.d.ts +8 -0
- package/dist/bin/gen-workspace.d.ts.map +1 -0
- package/dist/bin/gen-workspace.js +3841 -0
- package/dist/bin/manifest.cjs +686 -0
- package/dist/bin/manifest.d.ts +10 -0
- package/dist/bin/manifest.d.ts.map +1 -0
- package/dist/bin/manifest.js +663 -0
- package/dist/bin/rpc/dispatch.d.ts +10 -0
- package/dist/bin/rpc/dispatch.d.ts.map +1 -0
- package/dist/bin/rpc/handlers.d.ts +4 -0
- package/dist/bin/rpc/handlers.d.ts.map +1 -0
- package/dist/bin/rpc/types.d.ts +29 -0
- package/dist/bin/rpc/types.d.ts.map +1 -0
- package/dist/bin/template-info.cjs +1511 -0
- package/dist/bin/template-info.d.ts +9 -0
- package/dist/bin/template-info.d.ts.map +1 -0
- package/dist/bin/template-info.js +1488 -0
- package/dist/custom-objects/build-definitions.d.ts +98 -0
- package/dist/custom-objects/build-definitions.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/custom_objects/pub/api/app_api/object_definitions_app_service.pb.d.ts +191 -0
- package/dist/custom-objects/generated/proto/custom_objects/pub/api/app_api/object_definitions_app_service.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/custom_objects/pub/api/common/schema.pb.d.ts +131 -0
- package/dist/custom-objects/generated/proto/custom_objects/pub/api/common/schema.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/google/protobuf/descriptor.pb.d.ts +1482 -0
- package/dist/custom-objects/generated/proto/google/protobuf/descriptor.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/google/protobuf/timestamp.pb.d.ts +167 -0
- package/dist/custom-objects/generated/proto/google/protobuf/timestamp.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/proto/annotations.pb.d.ts +64 -0
- package/dist/custom-objects/generated/proto/proto/annotations.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/proto/extensions.pb.d.ts +657 -0
- package/dist/custom-objects/generated/proto/proto/extensions.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/metadata/pub/api/api_metadata.pb.d.ts +105 -0
- package/dist/custom-objects/generated/proto/vendor/metadata/pub/api/api_metadata.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/net/idempotency/idempotency_model.pb.d.ts +79 -0
- package/dist/custom-objects/generated/proto/vendor/net/idempotency/idempotency_model.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/api_group_enum.pb.d.ts +129 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/api_group_enum.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/api_visibility.pb.d.ts +76 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/api_visibility.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/customize_dispatch_middleware_enum.pb.d.ts +78 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/customize_dispatch_middleware_enum.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/docs_namespace_group_enum.pb.d.ts +146 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/docs_namespace_group_enum.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/documented_enum.pb.d.ts +76 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/documented_enum.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/event_scope_enum.pb.d.ts +92 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/event_scope_enum.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/extension_interface.pb.d.ts +124 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/extension_interface.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/feature_enum.pb.d.ts +1070 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/feature_enum.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/field_validation_rules.pb.d.ts +279 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/field_validation_rules.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/flavor_enum.pb.d.ts +78 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/flavor_enum.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/http_error_status.pb.d.ts +102 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/http_error_status.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/method_kind_enum.pb.d.ts +86 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/method_kind_enum.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/method_priority.pb.d.ts +80 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/method_priority.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/permission_check_enum.pb.d.ts +74 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/permission_check_enum.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/redaction_enum.pb.d.ts +76 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/redaction_enum.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/region_routers.pb.d.ts +103 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/region_routers.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/rollout_configs.pb.d.ts +153 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/rollout_configs.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/v2ext.pb.d.ts +1111 -0
- package/dist/custom-objects/generated/proto/vendor/publicapi/v2ext.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/vext/annotations.pb.d.ts +602 -0
- package/dist/custom-objects/generated/proto/vendor/vext/annotations.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/vext/extensions.pb.d.ts +144 -0
- package/dist/custom-objects/generated/proto/vendor/vext/extensions.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/vext/privacy_unified_annotations.pb.d.ts +851 -0
- package/dist/custom-objects/generated/proto/vendor/vext/privacy_unified_annotations.pb.d.ts.map +1 -0
- package/dist/custom-objects/generated/proto/vendor/vext/xml_annotations.pb.d.ts +125 -0
- package/dist/custom-objects/generated/proto/vendor/vext/xml_annotations.pb.d.ts.map +1 -0
- package/dist/custom-objects/to-proto-json.d.ts +17 -0
- package/dist/custom-objects/to-proto-json.d.ts.map +1 -0
- package/dist/dependencies/index.cjs +601 -0
- package/dist/dependencies/index.d.ts +320 -0
- package/dist/dependencies/index.d.ts.map +1 -0
- package/dist/dependencies/index.js +560 -0
- package/dist/extensibility-dev-tools-alpha.d.ts +199 -0
- package/dist/extensibility-dev-tools-beta.d.ts +199 -0
- package/dist/extensibility-dev-tools-dependencies-alpha.d.ts +51 -0
- package/dist/extensibility-dev-tools-dependencies-beta.d.ts +51 -0
- package/dist/extensibility-dev-tools-dependencies-internal.d.ts +372 -0
- package/dist/extensibility-dev-tools-dependencies-public.d.ts +51 -0
- package/dist/extensibility-dev-tools-internal.d.ts +1722 -0
- package/dist/extensibility-dev-tools-jsonschema-tools-alpha.d.ts +57 -0
- package/dist/extensibility-dev-tools-jsonschema-tools-beta.d.ts +57 -0
- package/dist/extensibility-dev-tools-jsonschema-tools-internal.d.ts +123 -0
- package/dist/extensibility-dev-tools-jsonschema-tools-public.d.ts +57 -0
- package/dist/extensibility-dev-tools-manifest-alpha.d.ts +31 -0
- package/dist/extensibility-dev-tools-manifest-beta.d.ts +31 -0
- package/dist/extensibility-dev-tools-manifest-internal.d.ts +461 -0
- package/dist/extensibility-dev-tools-manifest-public.d.ts +31 -0
- package/dist/extensibility-dev-tools-public.d.ts +199 -0
- package/dist/extensibility-dev-tools-schemas-alpha.d.ts +9 -0
- package/dist/extensibility-dev-tools-schemas-beta.d.ts +9 -0
- package/dist/extensibility-dev-tools-schemas-internal.d.ts +41 -0
- package/dist/extensibility-dev-tools-schemas-public.d.ts +9 -0
- package/dist/extensibility-dev-tools-templates-alpha.d.ts +67 -0
- package/dist/extensibility-dev-tools-templates-beta.d.ts +67 -0
- package/dist/extensibility-dev-tools-templates-internal.d.ts +554 -0
- package/dist/extensibility-dev-tools-templates-public.d.ts +67 -0
- package/dist/extensibility-dev-tools-workspace-alpha.d.ts +51 -0
- package/dist/extensibility-dev-tools-workspace-beta.d.ts +51 -0
- package/dist/extensibility-dev-tools-workspace-internal.d.ts +410 -0
- package/dist/extensibility-dev-tools-workspace-public.d.ts +51 -0
- package/dist/index.cjs +3810 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3758 -0
- package/dist/jsonschema-tools.cjs +20451 -0
- package/dist/jsonschema-tools.d.ts +98 -0
- package/dist/jsonschema-tools.d.ts.map +1 -0
- package/dist/jsonschema-tools.js +20404 -0
- package/dist/manifest/index.cjs +610 -0
- package/dist/manifest/index.d.ts +8 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +571 -0
- package/dist/manifest/manifest-v1.d.ts +102 -0
- package/dist/manifest/manifest-v1.d.ts.map +1 -0
- package/dist/manifest/manifest-v2.d.ts +253 -0
- package/dist/manifest/manifest-v2.d.ts.map +1 -0
- package/dist/manifest/stripe-app-manifest.d.ts +114 -0
- package/dist/manifest/stripe-app-manifest.d.ts.map +1 -0
- package/dist/schemas/index.cjs +20692 -0
- package/dist/schemas/index.d.ts +37 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +20656 -0
- package/dist/templates/diff-viewer/diff-generator.d.ts +22 -0
- package/dist/templates/diff-viewer/diff-generator.d.ts.map +1 -0
- package/dist/templates/diff-viewer/diff-prompt.d.ts +13 -0
- package/dist/templates/diff-viewer/diff-prompt.d.ts.map +1 -0
- package/dist/templates/diff-viewer/index.d.ts +7 -0
- package/dist/templates/diff-viewer/index.d.ts.map +1 -0
- package/dist/templates/diff-viewer/terminal-renderer.d.ts +29 -0
- package/dist/templates/diff-viewer/terminal-renderer.d.ts.map +1 -0
- package/dist/templates/diff-viewer/types.d.ts +58 -0
- package/dist/templates/diff-viewer/types.d.ts.map +1 -0
- package/dist/templates/extensions/base.d.ts +23 -0
- package/dist/templates/extensions/base.d.ts.map +1 -0
- package/dist/templates/extensions/billing.bill.discount_calculation.d.ts +6 -0
- package/dist/templates/extensions/billing.bill.discount_calculation.d.ts.map +1 -0
- package/dist/templates/extensions/billing.customer_balance_application.d.ts +6 -0
- package/dist/templates/extensions/billing.customer_balance_application.d.ts.map +1 -0
- package/dist/templates/extensions/billing.invoice_collection_setting.d.ts +6 -0
- package/dist/templates/extensions/billing.invoice_collection_setting.d.ts.map +1 -0
- package/dist/templates/extensions/billing.prorations.d.ts +6 -0
- package/dist/templates/extensions/billing.prorations.d.ts.map +1 -0
- package/dist/templates/extensions/billing.recurring_billing_item_handling.d.ts +6 -0
- package/dist/templates/extensions/billing.recurring_billing_item_handling.d.ts.map +1 -0
- package/dist/templates/extensions/core.workflows.custom_action.d.ts +6 -0
- package/dist/templates/extensions/core.workflows.custom_action.d.ts.map +1 -0
- package/dist/templates/extensions/extend.objects.custom_objects.d.ts +6 -0
- package/dist/templates/extensions/extend.objects.custom_objects.d.ts.map +1 -0
- package/dist/templates/extensions/extend.workflows.custom_action.d.ts +6 -0
- package/dist/templates/extensions/extend.workflows.custom_action.d.ts.map +1 -0
- package/dist/templates/extensions/index.d.ts +13 -0
- package/dist/templates/extensions/index.d.ts.map +1 -0
- package/dist/templates/extensions/registry.d.ts +10 -0
- package/dist/templates/extensions/registry.d.ts.map +1 -0
- package/dist/templates/extensions/types.d.ts +104 -0
- package/dist/templates/extensions/types.d.ts.map +1 -0
- package/dist/templates/file-writer.d.ts +140 -0
- package/dist/templates/file-writer.d.ts.map +1 -0
- package/dist/templates/fs/_impl.d.ts +29 -0
- package/dist/templates/fs/_impl.d.ts.map +1 -0
- package/dist/templates/fs/filesystem.d.ts +8 -0
- package/dist/templates/fs/filesystem.d.ts.map +1 -0
- package/dist/templates/fs/in-memory.d.ts +9 -0
- package/dist/templates/fs/in-memory.d.ts.map +1 -0
- package/dist/templates/fs/index.d.ts +25 -0
- package/dist/templates/fs/index.d.ts.map +1 -0
- package/dist/templates/fs-utils.d.ts +17 -0
- package/dist/templates/fs-utils.d.ts.map +1 -0
- package/dist/templates/index.cjs +2248 -0
- package/dist/templates/index.d.ts +32 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +2203 -0
- package/dist/templates/root/index.d.ts +60 -0
- package/dist/templates/root/index.d.ts.map +1 -0
- package/dist/templates/simple-templates.d.ts +8 -0
- package/dist/templates/simple-templates.d.ts.map +1 -0
- package/dist/templates/template-manager.d.ts +8 -0
- package/dist/templates/template-manager.d.ts.map +1 -0
- package/dist/templates/types.d.ts +9 -0
- package/dist/templates/types.d.ts.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/workspace/index.cjs +3756 -0
- package/dist/workspace/index.d.ts +336 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +3731 -0
- package/package.json +137 -0
- package/templates/extensions/billing.bill.discount_calculation/index.test.ts +15 -0
- package/templates/extensions/billing.bill.discount_calculation/index.ts +20 -0
- package/templates/extensions/billing.customer_balance_application/index.test.ts +15 -0
- package/templates/extensions/billing.customer_balance_application/index.ts +18 -0
- package/templates/extensions/billing.invoice_collection_setting/index.test.ts +15 -0
- package/templates/extensions/billing.invoice_collection_setting/index.ts +16 -0
- package/templates/extensions/billing.prorations/index.test.ts +15 -0
- package/templates/extensions/billing.prorations/index.ts +18 -0
- package/templates/extensions/billing.recurring_billing_item_handling/index.test.ts +15 -0
- package/templates/extensions/billing.recurring_billing_item_handling/index.ts +42 -0
- package/templates/extensions/common/.prettierignore +3 -0
- package/templates/extensions/common/eslint.config.mts.mustache +95 -0
- package/templates/extensions/common/package.json.mustache +26 -0
- package/templates/extensions/common/tsconfig.build.json.mustache +15 -0
- package/templates/extensions/common/tsconfig.json.mustache +16 -0
- package/templates/extensions/core.workflows.custom_action/custom_input.schema.json +6 -0
- package/templates/extensions/core.workflows.custom_action/index.test.ts +15 -0
- package/templates/extensions/core.workflows.custom_action/index.ts +31 -0
- package/templates/extensions/extend.workflows.custom_action/custom_input.schema.json +6 -0
- package/templates/extensions/extend.workflows.custom_action/index.test.ts +15 -0
- package/templates/extensions/extend.workflows.custom_action/index.ts +31 -0
- package/templates/root/.husky/pre-commit +1 -0
- package/templates/root/.prettierignore +5 -0
- package/templates/root/.prettierrc +7 -0
- package/templates/root/_gitignore +28 -0
- package/templates/root/custom-objects/package.json +20 -0
- package/templates/root/custom-objects/tsconfig.json +9 -0
- package/templates/root/eslint.config.mts +95 -0
- package/templates/root/package.json.mustache +32 -0
- package/templates/root/pnpm-workspace.yaml +7 -0
- package/templates/root/stripe-app.yaml.mustache +6 -0
- package/templates/root/tools/test.mts +38 -0
- package/templates/root/tsconfig.base.json +23 -0
- package/templates/root/tsconfig.json +15 -0
- package/templates/root/ui/package.json +17 -0
- package/templates/root/vitest.config.mts +47 -0
|
@@ -0,0 +1,2113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/bin/create-upload-image.ts
|
|
4
|
+
import { execSync as execSync2 } from "child_process";
|
|
5
|
+
import * as fs3 from "fs";
|
|
6
|
+
import * as os2 from "os";
|
|
7
|
+
import * as path3 from "path";
|
|
8
|
+
|
|
9
|
+
// src/custom-objects/build-definitions.ts
|
|
10
|
+
import * as fs2 from "fs";
|
|
11
|
+
import * as path2 from "path";
|
|
12
|
+
import * as customObjectTools from "@stripe/extensibility-custom-objects-tools/internal";
|
|
13
|
+
|
|
14
|
+
// src/manifest/stripe-app-manifest.ts
|
|
15
|
+
import { _toSnakeCase } from "@stripe/extensibility-tool-utils";
|
|
16
|
+
|
|
17
|
+
// src/manifest/manifest-v2.ts
|
|
18
|
+
import { readFile, writeFile } from "fs/promises";
|
|
19
|
+
import * as yaml from "yaml";
|
|
20
|
+
import { _toPascalCase } from "@stripe/extensibility-tool-utils";
|
|
21
|
+
function isNonEmptyString(value) {
|
|
22
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
23
|
+
}
|
|
24
|
+
function isYamlObject(value) {
|
|
25
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
function validateManifestV2Data(data) {
|
|
28
|
+
if (!isNonEmptyString(data.id)) {
|
|
29
|
+
throw new Error('Manifest must contain non-empty "id" field');
|
|
30
|
+
}
|
|
31
|
+
if (!isNonEmptyString(data.name)) {
|
|
32
|
+
throw new Error('Manifest must contain non-empty "name" field');
|
|
33
|
+
}
|
|
34
|
+
if (!isNonEmptyString(data.version)) {
|
|
35
|
+
throw new Error('Manifest must contain non-empty "version" field');
|
|
36
|
+
}
|
|
37
|
+
if ("extensions" in data && !Array.isArray(data.extensions)) {
|
|
38
|
+
throw new Error('Manifest field "extensions" must be an array when provided');
|
|
39
|
+
}
|
|
40
|
+
if ("custom_object_definitions" in data && !isYamlObject(data.custom_object_definitions)) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
'Manifest field "custom_object_definitions" must be an object with a "definitions" array'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const coBlock = data.custom_object_definitions;
|
|
46
|
+
const definitions = isYamlObject(coBlock) ? coBlock.definitions : void 0;
|
|
47
|
+
if (!definitions) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!Array.isArray(definitions)) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
'Manifest field "custom_object_definitions.definitions" must be an array'
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
for (let i = 0; i < definitions.length; i++) {
|
|
56
|
+
const def = definitions[i];
|
|
57
|
+
const prefix = `custom_object_definitions.definitions[${String(i)}]`;
|
|
58
|
+
if (!isYamlObject(def) || !isNonEmptyString(def.id)) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`${prefix}: missing or invalid "id" field. Got: ${JSON.stringify(def)}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
if (!isYamlObject(def.specification)) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`${prefix}: missing or invalid "specification" field. Expected: { type: "typescript", content: "path/to/file.ts" }. Got: ${JSON.stringify(def)}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (!isNonEmptyString(def.specification.type)) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`${prefix}: missing or invalid "specification.type" field. Got: ${JSON.stringify(def)}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (!isNonEmptyString(def.specification.content)) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`${prefix}: missing or invalid "specification.content" field. Got: ${JSON.stringify(def)}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
var _ManifestV2 = class __ManifestV2 {
|
|
81
|
+
constructor(filePath, data) {
|
|
82
|
+
this.filePath = filePath;
|
|
83
|
+
this.data = data;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Load stripe-app.yaml from disk
|
|
87
|
+
* @param filePath - Path to stripe-app.yaml
|
|
88
|
+
* @throws Error if file is invalid or cannot be read
|
|
89
|
+
*/
|
|
90
|
+
static async load(filePath) {
|
|
91
|
+
try {
|
|
92
|
+
const content = await readFile(filePath, "utf-8");
|
|
93
|
+
const parsed = yaml.parse(content);
|
|
94
|
+
if (!isYamlObject(parsed)) {
|
|
95
|
+
throw new Error("Invalid stripe-app.yaml format: root must be an object");
|
|
96
|
+
}
|
|
97
|
+
validateManifestV2Data(parsed);
|
|
98
|
+
const data = parsed;
|
|
99
|
+
data.extensions ??= [];
|
|
100
|
+
return new __ManifestV2(filePath, data);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
if (err instanceof Error) {
|
|
103
|
+
throw new Error(`Error reading stripe-app.yaml: ${err.message}`);
|
|
104
|
+
}
|
|
105
|
+
throw new Error(`Error reading stripe-app.yaml: ${String(err)}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get the app ID from the manifest
|
|
110
|
+
*/
|
|
111
|
+
getAppId() {
|
|
112
|
+
return this.data.id;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get the app name from the manifest
|
|
116
|
+
*/
|
|
117
|
+
getAppName() {
|
|
118
|
+
return this.data.name;
|
|
119
|
+
}
|
|
120
|
+
// ============================================================
|
|
121
|
+
// Custom Objects
|
|
122
|
+
// ============================================================
|
|
123
|
+
/**
|
|
124
|
+
* Get all custom object definitions, parsed into internal format.
|
|
125
|
+
* Entries are guaranteed structurally valid by load-time validation.
|
|
126
|
+
*/
|
|
127
|
+
getCustomObjects() {
|
|
128
|
+
const definitions = this.data.custom_object_definitions?.definitions;
|
|
129
|
+
if (!definitions || definitions.length === 0) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
return definitions.map((def) => ({
|
|
133
|
+
id: def.id,
|
|
134
|
+
type: def.specification.type,
|
|
135
|
+
path: def.specification.content,
|
|
136
|
+
name: _toPascalCase(def.id)
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Check if a custom object exists by id
|
|
141
|
+
*/
|
|
142
|
+
hasCustomObject(id) {
|
|
143
|
+
const definitions = this.data.custom_object_definitions?.definitions;
|
|
144
|
+
if (!definitions) return false;
|
|
145
|
+
return definitions.some((def) => def.id === id);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Add a custom object definition.
|
|
149
|
+
*
|
|
150
|
+
* Identity is keyed on `id` alone. The export name is derived via
|
|
151
|
+
* `_toPascalCase(id)` at read time, so `id` should be valid snake_case
|
|
152
|
+
* (e.g., "device", "loyalty_card", "http_request").
|
|
153
|
+
*
|
|
154
|
+
* @param id - Object identifier (e.g., "device", "loyalty_card")
|
|
155
|
+
* @param content - Path to the definition file (e.g., "src/device.object.ts")
|
|
156
|
+
* @param type - Specification type, defaults to "typescript"
|
|
157
|
+
* @returns true if added, false if already exists
|
|
158
|
+
*/
|
|
159
|
+
addCustomObject(id, content, type = "typescript") {
|
|
160
|
+
if (this.hasCustomObject(id)) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
this.data.custom_object_definitions ??= {};
|
|
164
|
+
this.data.custom_object_definitions.definitions ??= [];
|
|
165
|
+
this.data.custom_object_definitions.definitions.push({
|
|
166
|
+
id,
|
|
167
|
+
specification: { type, content }
|
|
168
|
+
});
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Remove a custom object definition by id
|
|
173
|
+
* @param id - Object identifier to remove
|
|
174
|
+
* @returns true if removed, false if not found
|
|
175
|
+
*/
|
|
176
|
+
removeCustomObject(id) {
|
|
177
|
+
const block = this.data.custom_object_definitions;
|
|
178
|
+
if (!block?.definitions) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
const originalLength = block.definitions.length;
|
|
182
|
+
block.definitions = block.definitions.filter((def) => def.id !== id);
|
|
183
|
+
const newLength = block.definitions.length;
|
|
184
|
+
if (newLength === 0) {
|
|
185
|
+
delete this.data.custom_object_definitions;
|
|
186
|
+
}
|
|
187
|
+
return newLength < originalLength;
|
|
188
|
+
}
|
|
189
|
+
// ============================================================
|
|
190
|
+
// Extensions
|
|
191
|
+
// ============================================================
|
|
192
|
+
/**
|
|
193
|
+
* Add a new extension or update an existing one by ID
|
|
194
|
+
* If an extension with the provided ID exists, it will be replaced entirely
|
|
195
|
+
* @param extension - Extension configuration with all required fields including id
|
|
196
|
+
* @returns The extension ID
|
|
197
|
+
*/
|
|
198
|
+
addOrUpdateExtension(extension) {
|
|
199
|
+
const newExtension = {
|
|
200
|
+
id: extension.id,
|
|
201
|
+
name: extension.name,
|
|
202
|
+
interface_id: extension.interface_id,
|
|
203
|
+
version: extension.version,
|
|
204
|
+
...extension.description !== void 0 && {
|
|
205
|
+
description: extension.description
|
|
206
|
+
},
|
|
207
|
+
...extension.stripe_version !== void 0 && {
|
|
208
|
+
stripe_version: extension.stripe_version
|
|
209
|
+
},
|
|
210
|
+
...extension.script_entry_point !== void 0 && {
|
|
211
|
+
script_entry_point: extension.script_entry_point
|
|
212
|
+
},
|
|
213
|
+
...extension.script !== void 0 && { script: extension.script },
|
|
214
|
+
permissions: extension.permissions,
|
|
215
|
+
methods: extension.methods,
|
|
216
|
+
...extension.configuration !== void 0 && {
|
|
217
|
+
configuration: extension.configuration
|
|
218
|
+
},
|
|
219
|
+
...extension.endpoints !== void 0 && {
|
|
220
|
+
endpoints: extension.endpoints
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
this.data.extensions ??= [];
|
|
224
|
+
const existingIndex = this.data.extensions.findIndex(
|
|
225
|
+
(ext) => ext.id === extension.id
|
|
226
|
+
);
|
|
227
|
+
if (existingIndex !== -1) {
|
|
228
|
+
this.data.extensions[existingIndex] = newExtension;
|
|
229
|
+
} else {
|
|
230
|
+
this.data.extensions.push(newExtension);
|
|
231
|
+
}
|
|
232
|
+
return extension.id;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get a read-only view of all extensions
|
|
236
|
+
*/
|
|
237
|
+
getExtensions() {
|
|
238
|
+
return this.data.extensions ?? [];
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get the raw manifest data (for reading other fields)
|
|
242
|
+
*/
|
|
243
|
+
getRawData() {
|
|
244
|
+
return this.data;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Save changes back to disk
|
|
248
|
+
* @throws Error if file cannot be written
|
|
249
|
+
*/
|
|
250
|
+
async save() {
|
|
251
|
+
try {
|
|
252
|
+
const updatedContent = yaml.stringify(this.data);
|
|
253
|
+
await writeFile(this.filePath, updatedContent, "utf-8");
|
|
254
|
+
} catch (err) {
|
|
255
|
+
if (err instanceof Error) {
|
|
256
|
+
throw new Error(`Error writing stripe-app.yaml: ${err.message}`);
|
|
257
|
+
}
|
|
258
|
+
throw new Error(`Error writing stripe-app.yaml: ${String(err)}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// src/templates/template-manager.ts
|
|
264
|
+
import { _TemplateManager } from "@stripe/extensibility-tool-utils";
|
|
265
|
+
|
|
266
|
+
// src/templates/extensions/types.ts
|
|
267
|
+
var _ExtensionTemplateManager = class extends _TemplateManager {
|
|
268
|
+
/**
|
|
269
|
+
* Get summary info for all registered extension templates
|
|
270
|
+
*/
|
|
271
|
+
getTemplateInfo() {
|
|
272
|
+
return this.getTemplateEntries().map(([key, t]) => ({
|
|
273
|
+
key,
|
|
274
|
+
description: t.description,
|
|
275
|
+
deprecated: t.deprecated ?? false,
|
|
276
|
+
hidden: t.hidden ?? false,
|
|
277
|
+
methods: t.methods
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/templates/fs/in-memory.ts
|
|
283
|
+
import { _createInMemoryTemplateFS } from "@stripe/extensibility-tool-utils";
|
|
284
|
+
|
|
285
|
+
// templates-virtual:./_impl.js
|
|
286
|
+
var TEMPLATE_FS_IMAGE = [
|
|
287
|
+
{
|
|
288
|
+
path: "extensions/billing.bill.discount_calculation/index.test.ts",
|
|
289
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
290
|
+
|
|
291
|
+
import MyDiscountCalculation from './index.js';
|
|
292
|
+
|
|
293
|
+
describe('MyDiscountCalculation', () => {
|
|
294
|
+
let instance: MyDiscountCalculation;
|
|
295
|
+
|
|
296
|
+
beforeEach(() => {
|
|
297
|
+
instance = new MyDiscountCalculation();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should be constructable', () => {
|
|
301
|
+
expect(instance).toBeInstanceOf(MyDiscountCalculation);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
`
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
path: "extensions/billing.bill.discount_calculation/index.ts",
|
|
308
|
+
content: `import type { Billing } from '@stripe/extensibility-sdk/extensions';
|
|
309
|
+
import type { Context } from '@stripe/extensibility-sdk/extensions';
|
|
310
|
+
|
|
311
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
312
|
+
interface MyDiscountCalculationConfig extends Record<string, unknown> {}
|
|
313
|
+
|
|
314
|
+
export default class MyDiscountCalculation implements Billing.Bill
|
|
315
|
+
.DiscountCalculation<MyDiscountCalculationConfig> {
|
|
316
|
+
computeDiscounts(
|
|
317
|
+
request: Billing.Bill.DiscountCalculation.DiscountableItem,
|
|
318
|
+
_config: MyDiscountCalculationConfig,
|
|
319
|
+
_context: Context
|
|
320
|
+
) {
|
|
321
|
+
// TODO: implement your discount logic here
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
discount: { amount: request.grossAmount },
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
`
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
path: "extensions/billing.customer_balance_application/index.test.ts",
|
|
332
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
333
|
+
|
|
334
|
+
import MyCustomerBalanceApplication from './index.js';
|
|
335
|
+
|
|
336
|
+
describe('MyCustomerBalanceApplication', () => {
|
|
337
|
+
let instance: MyCustomerBalanceApplication;
|
|
338
|
+
|
|
339
|
+
beforeEach(() => {
|
|
340
|
+
instance = new MyCustomerBalanceApplication();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('should be constructable', () => {
|
|
344
|
+
expect(instance).toBeInstanceOf(MyCustomerBalanceApplication);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
`
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
path: "extensions/billing.customer_balance_application/index.ts",
|
|
351
|
+
content: `import type { Billing, Context } from '@stripe/extensibility-sdk/extensions';
|
|
352
|
+
|
|
353
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
354
|
+
interface MyCustomerBalanceApplicationConfig extends Record<string, unknown> {}
|
|
355
|
+
|
|
356
|
+
export default class MyCustomerBalanceApplication implements Billing.CustomerBalanceApplication<MyCustomerBalanceApplicationConfig> {
|
|
357
|
+
computeAppliedCustomerBalance(
|
|
358
|
+
request: Billing.CustomerBalanceApplication.CustomerBalanceApplicationInput,
|
|
359
|
+
_config: MyCustomerBalanceApplicationConfig,
|
|
360
|
+
_context: Context
|
|
361
|
+
) {
|
|
362
|
+
// TODO: implement your customer balance logic here
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
appliedCustomerBalance: request.customerBalance,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
`
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
path: "extensions/billing.invoice_collection_setting/index.test.ts",
|
|
373
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
374
|
+
|
|
375
|
+
import MyInvoiceCollectionSetting from './index.js';
|
|
376
|
+
|
|
377
|
+
describe('MyInvoiceCollectionSetting', () => {
|
|
378
|
+
let instance: MyInvoiceCollectionSetting;
|
|
379
|
+
|
|
380
|
+
beforeEach(() => {
|
|
381
|
+
instance = new MyInvoiceCollectionSetting();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('should be constructable', () => {
|
|
385
|
+
expect(instance).toBeInstanceOf(MyInvoiceCollectionSetting);
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
`
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
path: "extensions/billing.invoice_collection_setting/index.ts",
|
|
392
|
+
content: `import type { Billing, Context } from '@stripe/extensibility-sdk/extensions';
|
|
393
|
+
|
|
394
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
395
|
+
interface MyInvoiceCollectionSettingConfig extends Record<string, unknown> {}
|
|
396
|
+
|
|
397
|
+
export default class MyInvoiceCollectionSetting implements Billing.InvoiceCollectionSetting<MyInvoiceCollectionSettingConfig> {
|
|
398
|
+
collectionOverride(
|
|
399
|
+
_request: Billing.InvoiceCollectionSetting.InvoiceCollectionRequest,
|
|
400
|
+
_config: MyInvoiceCollectionSettingConfig,
|
|
401
|
+
_context: Context
|
|
402
|
+
) {
|
|
403
|
+
// TODO: implement your collection setting logic here
|
|
404
|
+
|
|
405
|
+
return {};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
`
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
path: "extensions/billing.prorations/index.test.ts",
|
|
412
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
413
|
+
|
|
414
|
+
import MyProrations from './index.js';
|
|
415
|
+
|
|
416
|
+
describe('MyProrations', () => {
|
|
417
|
+
let instance: MyProrations;
|
|
418
|
+
|
|
419
|
+
beforeEach(() => {
|
|
420
|
+
instance = new MyProrations();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should be constructable', () => {
|
|
424
|
+
expect(instance).toBeInstanceOf(MyProrations);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
`
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
path: "extensions/billing.prorations/index.ts",
|
|
431
|
+
content: `import type { Billing, Context } from '@stripe/extensibility-sdk/extensions';
|
|
432
|
+
|
|
433
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
434
|
+
interface MyProrationsConfig extends Record<string, unknown> {}
|
|
435
|
+
|
|
436
|
+
export default class MyProrations implements Billing.Prorations<MyProrationsConfig> {
|
|
437
|
+
prorateItems(
|
|
438
|
+
_request: Billing.Prorations.ProrateItemsInput,
|
|
439
|
+
_config: MyProrationsConfig,
|
|
440
|
+
_context: Context
|
|
441
|
+
) {
|
|
442
|
+
// TODO: implement your proration logic here
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
items: [],
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
`
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
path: "extensions/billing.recurring_billing_item_handling/index.test.ts",
|
|
453
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
454
|
+
|
|
455
|
+
import MyRecurringBillingItemHandling from './index.js';
|
|
456
|
+
|
|
457
|
+
describe('MyRecurringBillingItemHandling', () => {
|
|
458
|
+
let instance: MyRecurringBillingItemHandling;
|
|
459
|
+
|
|
460
|
+
beforeEach(() => {
|
|
461
|
+
instance = new MyRecurringBillingItemHandling();
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('should be constructable', () => {
|
|
465
|
+
expect(instance).toBeInstanceOf(MyRecurringBillingItemHandling);
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
`
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
path: "extensions/billing.recurring_billing_item_handling/index.ts",
|
|
472
|
+
content: `import type { Billing, Context } from '@stripe/extensibility-sdk/extensions';
|
|
473
|
+
|
|
474
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
475
|
+
interface MyRecurringBillingItemHandlingConfig extends Record<string, unknown> {}
|
|
476
|
+
|
|
477
|
+
export default class MyRecurringBillingItemHandling implements Billing.RecurringBillingItemHandling<MyRecurringBillingItemHandlingConfig> {
|
|
478
|
+
beforeItemCreation(
|
|
479
|
+
_request: Billing.RecurringBillingItemHandling.BeforeItemCreationInput,
|
|
480
|
+
_config: MyRecurringBillingItemHandlingConfig,
|
|
481
|
+
_context: Context
|
|
482
|
+
) {
|
|
483
|
+
// TODO: implement your before-item-creation logic here
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
items: [],
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
filterItems(
|
|
491
|
+
_request: Billing.RecurringBillingItemHandling.FilterItemsInput,
|
|
492
|
+
_config: MyRecurringBillingItemHandlingConfig,
|
|
493
|
+
_context: Context
|
|
494
|
+
) {
|
|
495
|
+
// TODO: implement your filter-items logic here
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
items: [],
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
groupItems(
|
|
503
|
+
_request: Billing.RecurringBillingItemHandling.GroupItemsInput,
|
|
504
|
+
_config: MyRecurringBillingItemHandlingConfig,
|
|
505
|
+
_context: Context
|
|
506
|
+
) {
|
|
507
|
+
// TODO: implement your group-items logic here
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
groups: [],
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
`
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
path: "extensions/common/.prettierignore",
|
|
518
|
+
content: `dist
|
|
519
|
+
generated
|
|
520
|
+
node_modules
|
|
521
|
+
`
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
path: "extensions/common/eslint.config.mts.mustache",
|
|
525
|
+
content: `import eslint from '@eslint/js';
|
|
526
|
+
import { defineConfig } from 'eslint/config';
|
|
527
|
+
import tseslint from 'typescript-eslint';
|
|
528
|
+
import eslintConfigPrettier from 'eslint-config-prettier/flat';
|
|
529
|
+
|
|
530
|
+
import globals from 'globals';
|
|
531
|
+
|
|
532
|
+
import stripeAppsConfig from '@stripe/extensibility-eslint-plugin';
|
|
533
|
+
import extensionTypeConfig from '@stripe/extensibility-eslint-plugin/{{extensionInterfaceId}}';
|
|
534
|
+
|
|
535
|
+
export default defineConfig([
|
|
536
|
+
eslint.configs.recommended,
|
|
537
|
+
...tseslint.configs.recommended,
|
|
538
|
+
...stripeAppsConfig,
|
|
539
|
+
...extensionTypeConfig,
|
|
540
|
+
|
|
541
|
+
// Global ignores
|
|
542
|
+
{
|
|
543
|
+
ignores: ['dist', 'generated', 'node_modules'],
|
|
544
|
+
},
|
|
545
|
+
|
|
546
|
+
// TypeScript source files (with type-checking)
|
|
547
|
+
{
|
|
548
|
+
name: 'sources',
|
|
549
|
+
files: ['src/**/*.ts'],
|
|
550
|
+
ignores: ['**/*.test.ts', '**/__tests__/**'],
|
|
551
|
+
languageOptions: {
|
|
552
|
+
globals: {
|
|
553
|
+
...globals.node,
|
|
554
|
+
},
|
|
555
|
+
parserOptions: {
|
|
556
|
+
projectService: true,
|
|
557
|
+
tsconfigRootDir: import.meta.dirname,
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
|
|
562
|
+
// Test files (type-checking, relaxed rules)
|
|
563
|
+
{
|
|
564
|
+
name: 'tests',
|
|
565
|
+
files: ['src/**/*.test.ts', 'src/**/__tests__/**/*.ts'],
|
|
566
|
+
languageOptions: {
|
|
567
|
+
globals: {
|
|
568
|
+
...globals.node,
|
|
569
|
+
},
|
|
570
|
+
parserOptions: {
|
|
571
|
+
projectService: true,
|
|
572
|
+
tsconfigRootDir: import.meta.dirname,
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
rules: {
|
|
576
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
577
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
578
|
+
'no-console': 'off',
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
|
|
582
|
+
// Config files
|
|
583
|
+
{
|
|
584
|
+
name: 'ts-configs',
|
|
585
|
+
files: ['*.config.m?ts', 'eslint.config.mts'],
|
|
586
|
+
languageOptions: {
|
|
587
|
+
globals: {
|
|
588
|
+
...globals.node,
|
|
589
|
+
},
|
|
590
|
+
parserOptions: {
|
|
591
|
+
projectService: false,
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
rules: {
|
|
595
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
|
|
599
|
+
// JavaScript/MJS files (scripts, configs)
|
|
600
|
+
{
|
|
601
|
+
name: 'js-configs',
|
|
602
|
+
files: ['**/*.js', '**/*.mjs'],
|
|
603
|
+
languageOptions: {
|
|
604
|
+
globals: {
|
|
605
|
+
...globals.node,
|
|
606
|
+
},
|
|
607
|
+
parserOptions: {
|
|
608
|
+
projectService: false,
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
rules: {
|
|
612
|
+
'@typescript-eslint/no-require-imports': 'off',
|
|
613
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
614
|
+
'no-unused-vars': 'off',
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
|
|
618
|
+
eslintConfigPrettier,
|
|
619
|
+
]);
|
|
620
|
+
`
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
path: "extensions/common/package.json.mustache",
|
|
624
|
+
content: `{
|
|
625
|
+
"name": "{{id}}",
|
|
626
|
+
"version": "{{version}}",
|
|
627
|
+
"description": "{{name}}",
|
|
628
|
+
"private": true,
|
|
629
|
+
"license": "~~proprietary~~",
|
|
630
|
+
"type": "module",
|
|
631
|
+
"scripts": {
|
|
632
|
+
"build": "tsc -p tsconfig.build.json && pnpm build:schemas",
|
|
633
|
+
"build:schemas": "gen-schemas --root ../.. --extension-id \\"$npm_package_name\\" --out-dir generated --schema-name config",
|
|
634
|
+
"lint": "pnpm lint:types && pnpm lint:eslint && pnpm lint:format",
|
|
635
|
+
"lint:types": "tsc -p tsconfig.build.json --noEmit && tsc -p tsconfig.json --noEmit",
|
|
636
|
+
"lint:eslint": "eslint .",
|
|
637
|
+
"lint:format": "prettier --check .",
|
|
638
|
+
"fix:lint": "eslint --fix .",
|
|
639
|
+
"fix:format": "prettier --write .",
|
|
640
|
+
"test": "vitest --root ../.. run --project \\"$npm_package_name\\"",
|
|
641
|
+
"test:watch": "pnpm test --watch",
|
|
642
|
+
"dev": "concurrently -n build,lint,test -c blue,yellow,green 'tsc -p tsconfig.json -w --noEmit' 'chokidar \\"src/**/*.{ts,json}\\" -c \\"eslint .\\"' 'pnpm test --watch --no-clear-screen'",
|
|
643
|
+
"stripe:regen": "gen-workspace --root ../.. --package-dir . --template-id {{extensionInterfaceId}}"
|
|
644
|
+
},
|
|
645
|
+
"lint-staged": {
|
|
646
|
+
"*.{ts,mts,mjs,js}": ["eslint --fix", "prettier --write"],
|
|
647
|
+
"*.{json,md,yaml}": "prettier --write"
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
`
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
path: "extensions/common/tsconfig.build.json.mustache",
|
|
654
|
+
content: `{
|
|
655
|
+
"extends": "../../tsconfig.base.json",
|
|
656
|
+
"compilerOptions": {
|
|
657
|
+
"lib": null,
|
|
658
|
+
"noLib": true,
|
|
659
|
+
"outDir": "./dist",
|
|
660
|
+
"rootDir": "./src"
|
|
661
|
+
},
|
|
662
|
+
"include": [
|
|
663
|
+
"src/**/*.ts",
|
|
664
|
+
"node_modules/@stripe/extensibility-sdk/tslibs/5.9.3/lib.es2022.{{executionProfile}}.d.ts",
|
|
665
|
+
"node_modules/@stripe/extensibility-sdk/tslibs/lib.{{executionProfile}}.globals.d.ts"
|
|
666
|
+
],
|
|
667
|
+
"exclude": ["src/**/*.test.ts", "src/**/__tests__"]
|
|
668
|
+
}
|
|
669
|
+
`
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
path: "extensions/common/tsconfig.json.mustache",
|
|
673
|
+
content: `{
|
|
674
|
+
"extends": "../../tsconfig.json",
|
|
675
|
+
"compilerOptions": {
|
|
676
|
+
"rootDir": ".",
|
|
677
|
+
"plugins": [
|
|
678
|
+
{
|
|
679
|
+
"name": "@stripe/extensibility-language-server/plugin"
|
|
680
|
+
}
|
|
681
|
+
]
|
|
682
|
+
},
|
|
683
|
+
"include": [
|
|
684
|
+
"src/**/*.ts",
|
|
685
|
+
"node_modules/@stripe/extensibility-sdk/tslibs/lib.{{executionProfile}}.globals.d.ts"
|
|
686
|
+
],
|
|
687
|
+
"exclude": ["dist"]
|
|
688
|
+
}
|
|
689
|
+
`
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
path: "extensions/core.workflows.custom_action/custom_input.schema.json",
|
|
693
|
+
content: `{
|
|
694
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
695
|
+
"type": "object",
|
|
696
|
+
"properties": {},
|
|
697
|
+
"additionalProperties": false
|
|
698
|
+
}
|
|
699
|
+
`
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
path: "extensions/core.workflows.custom_action/index.test.ts",
|
|
703
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
704
|
+
|
|
705
|
+
import MyCustomAction from './index.js';
|
|
706
|
+
|
|
707
|
+
describe('MyCustomAction', () => {
|
|
708
|
+
let instance: MyCustomAction;
|
|
709
|
+
|
|
710
|
+
beforeEach(() => {
|
|
711
|
+
instance = new MyCustomAction();
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it('should be constructable', () => {
|
|
715
|
+
expect(instance).toBeInstanceOf(MyCustomAction);
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
`
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
path: "extensions/core.workflows.custom_action/index.ts",
|
|
722
|
+
content: `import type { Core } from '@stripe/extensibility-sdk/extensions';
|
|
723
|
+
import type { Context } from '@stripe/extensibility-sdk/extensions';
|
|
724
|
+
|
|
725
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
726
|
+
interface MyCustomActionConfig extends Record<string, unknown> {}
|
|
727
|
+
|
|
728
|
+
export default class MyCustomAction implements Core.Workflows
|
|
729
|
+
.CustomAction<MyCustomActionConfig> {
|
|
730
|
+
execute(
|
|
731
|
+
_request: Core.Workflows.CustomAction.ExecuteCustomActionRequest,
|
|
732
|
+
_config: MyCustomActionConfig,
|
|
733
|
+
_context: Context
|
|
734
|
+
) {
|
|
735
|
+
// TODO: implement your action logic here
|
|
736
|
+
|
|
737
|
+
return {};
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
getFormState(
|
|
741
|
+
_request: Core.Workflows.CustomAction.GetFormStateRequest,
|
|
742
|
+
_config: MyCustomActionConfig,
|
|
743
|
+
_context: Context
|
|
744
|
+
) {
|
|
745
|
+
// TODO: implement your logic here
|
|
746
|
+
|
|
747
|
+
return {
|
|
748
|
+
values: {},
|
|
749
|
+
config: {},
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
`
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
path: "extensions/extend.workflows.custom_action/custom_input.schema.json",
|
|
757
|
+
content: `{
|
|
758
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
759
|
+
"type": "object",
|
|
760
|
+
"properties": {},
|
|
761
|
+
"additionalProperties": false
|
|
762
|
+
}
|
|
763
|
+
`
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
path: "extensions/extend.workflows.custom_action/index.test.ts",
|
|
767
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
768
|
+
|
|
769
|
+
import MyCustomAction from './index.js';
|
|
770
|
+
|
|
771
|
+
describe('MyCustomAction', () => {
|
|
772
|
+
let instance: MyCustomAction;
|
|
773
|
+
|
|
774
|
+
beforeEach(() => {
|
|
775
|
+
instance = new MyCustomAction();
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it('should be constructable', () => {
|
|
779
|
+
expect(instance).toBeInstanceOf(MyCustomAction);
|
|
780
|
+
});
|
|
781
|
+
});
|
|
782
|
+
`
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
path: "extensions/extend.workflows.custom_action/index.ts",
|
|
786
|
+
content: `import type { Extend } from '@stripe/extensibility-sdk/extensions';
|
|
787
|
+
import type { Context } from '@stripe/extensibility-sdk/extensions';
|
|
788
|
+
|
|
789
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
790
|
+
interface MyCustomActionConfig extends Record<string, unknown> {}
|
|
791
|
+
|
|
792
|
+
export default class MyCustomAction implements Extend.Workflows
|
|
793
|
+
.CustomAction<MyCustomActionConfig> {
|
|
794
|
+
execute(
|
|
795
|
+
_request: Extend.Workflows.CustomAction.ExecuteCustomActionRequest,
|
|
796
|
+
_config: MyCustomActionConfig,
|
|
797
|
+
_context: Context
|
|
798
|
+
) {
|
|
799
|
+
// TODO: implement your action logic here
|
|
800
|
+
|
|
801
|
+
return {};
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
getFormState(
|
|
805
|
+
_request: Extend.Workflows.CustomAction.GetFormStateRequest,
|
|
806
|
+
_config: MyCustomActionConfig,
|
|
807
|
+
_context: Context
|
|
808
|
+
) {
|
|
809
|
+
// TODO: implement your logic here
|
|
810
|
+
|
|
811
|
+
return {
|
|
812
|
+
values: {},
|
|
813
|
+
config: {},
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
`
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
path: "root/.husky/pre-commit",
|
|
821
|
+
content: `pnpm lint-staged
|
|
822
|
+
`
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
path: "root/.prettierignore",
|
|
826
|
+
content: `dist
|
|
827
|
+
generated
|
|
828
|
+
node_modules
|
|
829
|
+
pnpm-lock.yaml
|
|
830
|
+
.build
|
|
831
|
+
`
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
path: "root/.prettierrc",
|
|
835
|
+
content: `{
|
|
836
|
+
"semi": true,
|
|
837
|
+
"singleQuote": true,
|
|
838
|
+
"tabWidth": 2,
|
|
839
|
+
"trailingComma": "es5",
|
|
840
|
+
"printWidth": 90
|
|
841
|
+
}
|
|
842
|
+
`
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
path: "root/_gitignore",
|
|
846
|
+
content: `# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
847
|
+
|
|
848
|
+
*~
|
|
849
|
+
|
|
850
|
+
# dependencies
|
|
851
|
+
node_modules
|
|
852
|
+
dist
|
|
853
|
+
|
|
854
|
+
# testing
|
|
855
|
+
/coverage
|
|
856
|
+
|
|
857
|
+
# production
|
|
858
|
+
/.build
|
|
859
|
+
|
|
860
|
+
# misc
|
|
861
|
+
.DS_Store
|
|
862
|
+
.env.local
|
|
863
|
+
.env.development.local
|
|
864
|
+
.env.test.local
|
|
865
|
+
.env.production.local
|
|
866
|
+
|
|
867
|
+
npm-debug.log*
|
|
868
|
+
yarn-debug.log*
|
|
869
|
+
yarn-error.log*
|
|
870
|
+
install-deps.log
|
|
871
|
+
|
|
872
|
+
# generated schemas
|
|
873
|
+
generated
|
|
874
|
+
`
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
path: "root/custom-objects/package.json",
|
|
878
|
+
content: `{
|
|
879
|
+
"name": "custom-objects",
|
|
880
|
+
"type": "module",
|
|
881
|
+
"version": "0.0.1",
|
|
882
|
+
"license": "UNLICENSED",
|
|
883
|
+
"private": true,
|
|
884
|
+
"scripts": {
|
|
885
|
+
"build": "test -d src && custom-objects-build --input src --output dist || true",
|
|
886
|
+
"lint:types": "test ! -d src || tsc --noEmit",
|
|
887
|
+
"test": "vitest run"
|
|
888
|
+
},
|
|
889
|
+
"dependencies": {
|
|
890
|
+
"@stripe/extensibility-custom-objects": "0.7.4",
|
|
891
|
+
"@stripe/extensibility-sdk": "0.22.4"
|
|
892
|
+
},
|
|
893
|
+
"devDependencies": {
|
|
894
|
+
"@stripe/extensibility-custom-objects-tools": "0.40.0",
|
|
895
|
+
"@stripe/extensibility-test-helpers": "0.2.7"
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
`
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
path: "root/custom-objects/tsconfig.json",
|
|
902
|
+
content: `{
|
|
903
|
+
"extends": "../tsconfig.base.json",
|
|
904
|
+
"compilerOptions": {
|
|
905
|
+
"module": "ESNext",
|
|
906
|
+
"moduleResolution": "bundler",
|
|
907
|
+
"types": ["vitest/globals"]
|
|
908
|
+
},
|
|
909
|
+
"exclude": ["dist"]
|
|
910
|
+
}
|
|
911
|
+
`
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
path: "root/eslint.config.mts",
|
|
915
|
+
content: `import { readFileSync } from 'node:fs';
|
|
916
|
+
import { dirname, resolve } from 'node:path';
|
|
917
|
+
import { fileURLToPath } from 'node:url';
|
|
918
|
+
import eslint from '@eslint/js';
|
|
919
|
+
import { defineConfig } from 'eslint/config';
|
|
920
|
+
import tseslint from 'typescript-eslint';
|
|
921
|
+
import workspaces from 'eslint-plugin-workspaces';
|
|
922
|
+
import eslintConfigPrettier from 'eslint-config-prettier/flat';
|
|
923
|
+
|
|
924
|
+
import globals from 'globals';
|
|
925
|
+
|
|
926
|
+
import stripeAppsConfig from '@stripe/extensibility-eslint-plugin';
|
|
927
|
+
|
|
928
|
+
// Read additional ignore globs from package.json (written by the generate plugin
|
|
929
|
+
// to exclude generated SDK directories from linting).
|
|
930
|
+
let stripeGlobsToIgnore: string[] = [];
|
|
931
|
+
try {
|
|
932
|
+
const configDir = dirname(fileURLToPath(import.meta.url));
|
|
933
|
+
const packageJsonPath = resolve(configDir, './package.json');
|
|
934
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
935
|
+
const globs = pkg?.stripe?.eslintIgnoreGlobs;
|
|
936
|
+
if (Array.isArray(globs)) {
|
|
937
|
+
stripeGlobsToIgnore = globs.filter(
|
|
938
|
+
(g: unknown): g is string => typeof g === 'string'
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
} catch {
|
|
942
|
+
// package.json not found or unparseable \u2014 ignore
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
export default defineConfig([
|
|
946
|
+
eslint.configs.recommended,
|
|
947
|
+
...tseslint.configs.recommended,
|
|
948
|
+
...stripeAppsConfig,
|
|
949
|
+
|
|
950
|
+
// Global ignores
|
|
951
|
+
{
|
|
952
|
+
ignores: [
|
|
953
|
+
'.build',
|
|
954
|
+
'**/dist',
|
|
955
|
+
'**/generated',
|
|
956
|
+
'**/node_modules',
|
|
957
|
+
'extensions/**',
|
|
958
|
+
'custom-objects',
|
|
959
|
+
'ui',
|
|
960
|
+
...stripeGlobsToIgnore,
|
|
961
|
+
],
|
|
962
|
+
},
|
|
963
|
+
|
|
964
|
+
// Common rules for all files
|
|
965
|
+
{
|
|
966
|
+
plugins: { workspaces },
|
|
967
|
+
rules: {
|
|
968
|
+
...workspaces.configs.recommended.rules,
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
|
|
972
|
+
// Config files (vitest.config.ts, eslint.config.ts, etc.)
|
|
973
|
+
{
|
|
974
|
+
name: 'ts-configs',
|
|
975
|
+
files: ['extensions/*/*.config.m?ts', '*.config.m?ts', 'eslint.config.mts'],
|
|
976
|
+
languageOptions: {
|
|
977
|
+
globals: {
|
|
978
|
+
...globals.node,
|
|
979
|
+
},
|
|
980
|
+
parserOptions: {
|
|
981
|
+
projectService: false,
|
|
982
|
+
},
|
|
983
|
+
},
|
|
984
|
+
rules: {
|
|
985
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
986
|
+
},
|
|
987
|
+
},
|
|
988
|
+
|
|
989
|
+
// JavaScript/MJS files (scripts, configs)
|
|
990
|
+
{
|
|
991
|
+
name: 'js-configs',
|
|
992
|
+
files: ['**/*.js', '**/*.mjs'],
|
|
993
|
+
languageOptions: {
|
|
994
|
+
globals: {
|
|
995
|
+
...globals.node,
|
|
996
|
+
},
|
|
997
|
+
parserOptions: {
|
|
998
|
+
projectService: false,
|
|
999
|
+
},
|
|
1000
|
+
},
|
|
1001
|
+
rules: {
|
|
1002
|
+
'@typescript-eslint/no-require-imports': 'off',
|
|
1003
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
1004
|
+
'no-unused-vars': 'off',
|
|
1005
|
+
},
|
|
1006
|
+
},
|
|
1007
|
+
|
|
1008
|
+
eslintConfigPrettier,
|
|
1009
|
+
]);
|
|
1010
|
+
`
|
|
1011
|
+
},
|
|
1012
|
+
{
|
|
1013
|
+
path: "root/package.json.mustache",
|
|
1014
|
+
content: `{
|
|
1015
|
+
"name": "{{ appId }}",
|
|
1016
|
+
"version": "{{ version }}",
|
|
1017
|
+
"description": "{{ appName }}",
|
|
1018
|
+
"private": true,
|
|
1019
|
+
"license": "~~proprietary~~",
|
|
1020
|
+
"engines": {
|
|
1021
|
+
"node": ">=20.0.0"
|
|
1022
|
+
},
|
|
1023
|
+
"packageManager": "pnpm@10.30.3",
|
|
1024
|
+
"scripts": {
|
|
1025
|
+
"build": "pnpm -r --if-present build",
|
|
1026
|
+
"lint": "pnpm lint:types && pnpm lint:eslint && pnpm lint:format",
|
|
1027
|
+
"lint:types": "pnpm -r --if-present lint:types",
|
|
1028
|
+
"lint:eslint": "eslint . --ignore-pattern 'extensions/**' && pnpm -r --filter './extensions/*' --if-present lint:eslint",
|
|
1029
|
+
"lint:format": "prettier --check .",
|
|
1030
|
+
"fix:lint": "eslint --fix . --ignore-pattern 'extensions/**' && pnpm -r --filter './extensions/*' --if-present fix:lint",
|
|
1031
|
+
"fix:format": "prettier --write .",
|
|
1032
|
+
"test": "tsx tools/test.mts",
|
|
1033
|
+
"test:watch": "vitest watch",
|
|
1034
|
+
"check": "pnpm build && pnpm lint && pnpm test",
|
|
1035
|
+
"prepare": "husky",
|
|
1036
|
+
"preimage": "pnpm install --lockfile-only && pnpm build && pnpm lint:types && pnpm lint:eslint && pnpm test",
|
|
1037
|
+
"image": "rm -rf .build && create-upload-image .build",
|
|
1038
|
+
"postimage": "cp pnpm-lock.yaml .build/",
|
|
1039
|
+
"stripe:regen": "gen-workspace --package-dir ."
|
|
1040
|
+
},
|
|
1041
|
+
"lint-staged": {
|
|
1042
|
+
"*.{ts,mts,mjs,js}": ["eslint --fix", "prettier --write"],
|
|
1043
|
+
"*.{json,md,yaml}": "prettier --write"
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
`
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
path: "root/pnpm-workspace.yaml",
|
|
1050
|
+
content: `packages:
|
|
1051
|
+
- extensions/*
|
|
1052
|
+
- custom-objects
|
|
1053
|
+
- ui
|
|
1054
|
+
|
|
1055
|
+
overrides:
|
|
1056
|
+
vite: ^6.0.0
|
|
1057
|
+
`
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
path: "root/stripe-app.yaml.mustache",
|
|
1061
|
+
content: `$schema: https://stripe.com/stripe-app/v2.0.0/schema
|
|
1062
|
+
id: '{{ appId }}'
|
|
1063
|
+
name: '{{ appName }}'
|
|
1064
|
+
version: 0.0.1
|
|
1065
|
+
declarations:
|
|
1066
|
+
distribution_type: private
|
|
1067
|
+
`
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
path: "root/tools/test.mts",
|
|
1071
|
+
content: `#!/usr/bin/env tsx
|
|
1072
|
+
/**
|
|
1073
|
+
* Runs tests across the workspace:
|
|
1074
|
+
* - vitest for script extensions and custom objects (extensions/*)
|
|
1075
|
+
* - jest for UI extensions (ui/)
|
|
1076
|
+
*/
|
|
1077
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
1078
|
+
import { execSync } from 'node:child_process';
|
|
1079
|
+
|
|
1080
|
+
const hasExtensions =
|
|
1081
|
+
existsSync('extensions') &&
|
|
1082
|
+
readdirSync('extensions').some((name) => existsSync(\`extensions/\${name}/package.json\`));
|
|
1083
|
+
|
|
1084
|
+
const hasUI = existsSync('ui/package.json');
|
|
1085
|
+
|
|
1086
|
+
let exitCode = 0;
|
|
1087
|
+
|
|
1088
|
+
function run(cmd: string): void {
|
|
1089
|
+
try {
|
|
1090
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
1091
|
+
} catch (e: unknown) {
|
|
1092
|
+
exitCode = (e as NodeJS.ErrnoException & { status?: number }).status ?? 1;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (hasExtensions) {
|
|
1097
|
+
run('vitest run');
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
if (hasUI) {
|
|
1101
|
+
try {
|
|
1102
|
+
execSync('pnpm --filter "./ui" test', { stdio: 'inherit' });
|
|
1103
|
+
} catch {
|
|
1104
|
+
// UI test failures are non-fatal
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
process.exit(exitCode);
|
|
1109
|
+
`
|
|
1110
|
+
},
|
|
1111
|
+
{
|
|
1112
|
+
path: "root/tsconfig.base.json",
|
|
1113
|
+
content: `{
|
|
1114
|
+
"compilerOptions": {
|
|
1115
|
+
"target": "ES2022",
|
|
1116
|
+
"module": "NodeNext",
|
|
1117
|
+
"moduleResolution": "NodeNext",
|
|
1118
|
+
"lib": ["ES2022"],
|
|
1119
|
+
"declaration": true,
|
|
1120
|
+
"declarationMap": true,
|
|
1121
|
+
"esModuleInterop": true,
|
|
1122
|
+
"exactOptionalPropertyTypes": false,
|
|
1123
|
+
"forceConsistentCasingInFileNames": true,
|
|
1124
|
+
"isolatedModules": true,
|
|
1125
|
+
"noFallthroughCasesInSwitch": true,
|
|
1126
|
+
"noImplicitReturns": true,
|
|
1127
|
+
"removeComments": false,
|
|
1128
|
+
"resolveJsonModule": true,
|
|
1129
|
+
"skipLibCheck": true,
|
|
1130
|
+
"sourceMap": true,
|
|
1131
|
+
"strict": true,
|
|
1132
|
+
"types": [],
|
|
1133
|
+
"verbatimModuleSyntax": true
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
`
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
path: "root/tsconfig.json",
|
|
1140
|
+
content: `{
|
|
1141
|
+
"extends": "./tsconfig.base.json",
|
|
1142
|
+
"compilerOptions": {
|
|
1143
|
+
"noEmit": true,
|
|
1144
|
+
"plugins": [
|
|
1145
|
+
{
|
|
1146
|
+
"name": "@stripe/extensibility-language-server/plugin"
|
|
1147
|
+
}
|
|
1148
|
+
],
|
|
1149
|
+
"preserveWatchOutput": true,
|
|
1150
|
+
"types": ["vitest/globals", "node"]
|
|
1151
|
+
},
|
|
1152
|
+
"include": ["**/*.ts"],
|
|
1153
|
+
"exclude": ["**/node_modules", "**/dist"]
|
|
1154
|
+
}
|
|
1155
|
+
`
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
path: "root/ui/package.json",
|
|
1159
|
+
content: `{
|
|
1160
|
+
"name": "ui",
|
|
1161
|
+
"version": "0.0.1",
|
|
1162
|
+
"license": "UNLICENSED",
|
|
1163
|
+
"private": true,
|
|
1164
|
+
"scripts": {
|
|
1165
|
+
"test": "jest --passWithNoTests"
|
|
1166
|
+
},
|
|
1167
|
+
"dependencies": {
|
|
1168
|
+
"@stripe/ui-extension-sdk": "^9.1.0"
|
|
1169
|
+
},
|
|
1170
|
+
"devDependencies": {
|
|
1171
|
+
"@stripe/ui-extension-tools": "^0.0.1",
|
|
1172
|
+
"@types/jest": "^27.5.2",
|
|
1173
|
+
"@types/react": "^17.0.2"
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
`
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
path: "root/vitest.config.mts",
|
|
1180
|
+
content: `import { defineConfig } from 'vitest/config';
|
|
1181
|
+
import { existsSync, readdirSync } from 'fs';
|
|
1182
|
+
import { dirname, resolve } from 'path';
|
|
1183
|
+
import { fileURLToPath } from 'url';
|
|
1184
|
+
|
|
1185
|
+
const rootDir = dirname(fileURLToPath(import.meta.url));
|
|
1186
|
+
const extensionsDir = resolve(rootDir, 'extensions');
|
|
1187
|
+
|
|
1188
|
+
const extensionProjects = existsSync(extensionsDir)
|
|
1189
|
+
? readdirSync(extensionsDir)
|
|
1190
|
+
.filter((name) => existsSync(resolve(extensionsDir, name, 'package.json')))
|
|
1191
|
+
.map((name) => resolve(extensionsDir, name))
|
|
1192
|
+
: [];
|
|
1193
|
+
|
|
1194
|
+
const coProjects = existsSync(resolve(rootDir, 'custom-objects/package.json'))
|
|
1195
|
+
? [resolve(rootDir, 'custom-objects')]
|
|
1196
|
+
: [];
|
|
1197
|
+
|
|
1198
|
+
const projects = [...extensionProjects, ...coProjects];
|
|
1199
|
+
|
|
1200
|
+
if (projects.length === 0) {
|
|
1201
|
+
console.debug(\`No vitest projects detected. This means either:
|
|
1202
|
+
- You have no extension projects defined, in which case this warning is expected.
|
|
1203
|
+
- There is an internal error detecting vitest projects relative to the project root
|
|
1204
|
+
\${rootDir}.
|
|
1205
|
+
\`);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
export default projects.length > 0
|
|
1209
|
+
? defineConfig({
|
|
1210
|
+
test: {
|
|
1211
|
+
projects,
|
|
1212
|
+
passWithNoTests: true,
|
|
1213
|
+
|
|
1214
|
+
// Only run tests from src, not compiled dist
|
|
1215
|
+
exclude: ['**/node_modules', '**/dist'],
|
|
1216
|
+
// Place snapshots alongside test files instead of in __snapshots__
|
|
1217
|
+
snapshotFormat: {
|
|
1218
|
+
escapeString: false,
|
|
1219
|
+
printBasicPrototype: false,
|
|
1220
|
+
},
|
|
1221
|
+
resolveSnapshotPath: (testPath, snapExtension) => {
|
|
1222
|
+
return testPath.replace(/\\.test\\.ts$/, \`.test\${snapExtension}\`);
|
|
1223
|
+
},
|
|
1224
|
+
},
|
|
1225
|
+
})
|
|
1226
|
+
: defineConfig({ test: { passWithNoTests: true, include: [] } });
|
|
1227
|
+
`
|
|
1228
|
+
}
|
|
1229
|
+
];
|
|
1230
|
+
var _fs = _createInMemoryTemplateFS(TEMPLATE_FS_IMAGE);
|
|
1231
|
+
|
|
1232
|
+
// src/dependencies/index.ts
|
|
1233
|
+
import path from "path";
|
|
1234
|
+
import fs from "fs";
|
|
1235
|
+
import os from "os";
|
|
1236
|
+
import { execSync } from "child_process";
|
|
1237
|
+
import PackageJson from "@npmcli/package-json";
|
|
1238
|
+
import * as semver from "semver";
|
|
1239
|
+
import { load as parseToml } from "js-toml";
|
|
1240
|
+
import { _createCliContext } from "@stripe/extensibility-tool-utils";
|
|
1241
|
+
function _npmDep(name, version) {
|
|
1242
|
+
return { type: "npm", name, version };
|
|
1243
|
+
}
|
|
1244
|
+
function _devNpmDep(name, version) {
|
|
1245
|
+
return { type: "dev-npm", name, version };
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// src/templates/extensions/base.ts
|
|
1249
|
+
import { _workspaceVersion } from "@stripe/extensibility-tool-utils";
|
|
1250
|
+
var SDK_PACKAGE_NAME = "@stripe/extensibility-sdk";
|
|
1251
|
+
var LANGUAGE_SERVER_PACKAGE_NAME = "@stripe/extensibility-language-server";
|
|
1252
|
+
var LANGUAGE_SERVER_PACKAGE_VERSION = "^0.2.0";
|
|
1253
|
+
function _createExtensionEslintConfigFile(params, context) {
|
|
1254
|
+
const { id, extensionInterfaceId } = params;
|
|
1255
|
+
const { fs: fs4 } = context;
|
|
1256
|
+
return {
|
|
1257
|
+
path: `extensions/${id}/eslint.config.mts`,
|
|
1258
|
+
content: fs4.mustache(
|
|
1259
|
+
{ extensionInterfaceId },
|
|
1260
|
+
"common",
|
|
1261
|
+
"eslint.config.mts.mustache"
|
|
1262
|
+
),
|
|
1263
|
+
precious: true
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
function _createBaseOutput(params, context) {
|
|
1267
|
+
const { id } = params;
|
|
1268
|
+
const { fs: fs4 } = context;
|
|
1269
|
+
return {
|
|
1270
|
+
files: [
|
|
1271
|
+
{
|
|
1272
|
+
path: `extensions/${id}/package.json`,
|
|
1273
|
+
content: fs4.mustache(params, "common", "package.json.mustache")
|
|
1274
|
+
},
|
|
1275
|
+
{
|
|
1276
|
+
..._createExtensionEslintConfigFile(params, context)
|
|
1277
|
+
},
|
|
1278
|
+
{
|
|
1279
|
+
path: `extensions/${id}/tsconfig.build.json`,
|
|
1280
|
+
content: fs4.mustache(params, "common", "tsconfig.build.json.mustache")
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
path: `extensions/${id}/tsconfig.json`,
|
|
1284
|
+
content: fs4.mustache(params, "common", "tsconfig.json.mustache")
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
path: `extensions/${id}/.prettierignore`,
|
|
1288
|
+
content: fs4.textFile("common", ".prettierignore")
|
|
1289
|
+
}
|
|
1290
|
+
],
|
|
1291
|
+
methods: {},
|
|
1292
|
+
dependencies: {
|
|
1293
|
+
// Exact pin (no caret) — the SDK is tightly coupled to dev-tools and
|
|
1294
|
+
// must match the version that generated the extension scaffolding.
|
|
1295
|
+
runtime: [_npmDep(SDK_PACKAGE_NAME, _workspaceVersion(SDK_PACKAGE_NAME))],
|
|
1296
|
+
dev: [_devNpmDep(LANGUAGE_SERVER_PACKAGE_NAME, LANGUAGE_SERVER_PACKAGE_VERSION)]
|
|
1297
|
+
},
|
|
1298
|
+
postGenerationHooks: [
|
|
1299
|
+
{
|
|
1300
|
+
script: "build",
|
|
1301
|
+
description: "Build extension"
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
exec: "prettier --write .",
|
|
1305
|
+
description: "Format generated files"
|
|
1306
|
+
}
|
|
1307
|
+
]
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/templates/extensions/core.workflows.custom_action.ts
|
|
1312
|
+
var EXTENSION_INTERFACE_ID = "core.workflows.custom_action";
|
|
1313
|
+
var customActionTemplate = {
|
|
1314
|
+
methods: {
|
|
1315
|
+
execute: { implementation_types: ["script", "remote-function"] },
|
|
1316
|
+
get_form_state: { implementation_types: ["script", "remote-function"] }
|
|
1317
|
+
},
|
|
1318
|
+
description: "Custom actions let your app define actions that users can add to their automated workflows. When a workflow triggers a custom action, Stripe calls your app to execute it.",
|
|
1319
|
+
generate: (params, context) => {
|
|
1320
|
+
const { id } = params;
|
|
1321
|
+
const { fs: fs4 } = context;
|
|
1322
|
+
const base = _createBaseOutput(
|
|
1323
|
+
{
|
|
1324
|
+
...params,
|
|
1325
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID,
|
|
1326
|
+
executionProfile: "egress"
|
|
1327
|
+
},
|
|
1328
|
+
context
|
|
1329
|
+
);
|
|
1330
|
+
return {
|
|
1331
|
+
...base,
|
|
1332
|
+
files: [
|
|
1333
|
+
{
|
|
1334
|
+
path: `extensions/${id}/src/index.ts`,
|
|
1335
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID, "index.ts"),
|
|
1336
|
+
precious: true
|
|
1337
|
+
},
|
|
1338
|
+
{
|
|
1339
|
+
path: `extensions/${id}/src/custom_input.schema.json`,
|
|
1340
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID, "custom_input.schema.json"),
|
|
1341
|
+
precious: true
|
|
1342
|
+
},
|
|
1343
|
+
{
|
|
1344
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
1345
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID, "index.test.ts"),
|
|
1346
|
+
precious: true
|
|
1347
|
+
},
|
|
1348
|
+
...base.files
|
|
1349
|
+
],
|
|
1350
|
+
methods: {
|
|
1351
|
+
execute: {
|
|
1352
|
+
implementation_type: "script",
|
|
1353
|
+
custom_input: {
|
|
1354
|
+
input_schema: {
|
|
1355
|
+
type: "json_schema",
|
|
1356
|
+
content: `extensions/${id}/src/custom_input.schema.json`
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
},
|
|
1360
|
+
get_form_state: {
|
|
1361
|
+
implementation_type: "script"
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
var core_workflows_custom_action_default = {
|
|
1368
|
+
[EXTENSION_INTERFACE_ID]: customActionTemplate
|
|
1369
|
+
};
|
|
1370
|
+
|
|
1371
|
+
// src/templates/extensions/extend.objects.custom_objects.ts
|
|
1372
|
+
var EXTENSION_INTERFACE_ID2 = "extend.objects.custom_objects";
|
|
1373
|
+
var customObjectsTemplate = {
|
|
1374
|
+
methods: {
|
|
1375
|
+
execute_method: { implementation_types: ["script"] }
|
|
1376
|
+
},
|
|
1377
|
+
description: "Methods extension for custom object actions. Generated at build time \u2014 the transformed custom object class becomes the entry point.",
|
|
1378
|
+
hidden: true,
|
|
1379
|
+
generate: (params, context) => {
|
|
1380
|
+
const base = _createBaseOutput(
|
|
1381
|
+
{
|
|
1382
|
+
...params,
|
|
1383
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID2,
|
|
1384
|
+
executionProfile: "egress"
|
|
1385
|
+
},
|
|
1386
|
+
context
|
|
1387
|
+
);
|
|
1388
|
+
return {
|
|
1389
|
+
...base,
|
|
1390
|
+
methods: {
|
|
1391
|
+
execute_method: {
|
|
1392
|
+
implementation_type: "script"
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
var extend_objects_custom_objects_default = {
|
|
1399
|
+
[EXTENSION_INTERFACE_ID2]: customObjectsTemplate
|
|
1400
|
+
};
|
|
1401
|
+
|
|
1402
|
+
// src/templates/extensions/extend.workflows.custom_action.ts
|
|
1403
|
+
var EXTENSION_INTERFACE_ID3 = "extend.workflows.custom_action";
|
|
1404
|
+
var extendCustomActionTemplate = {
|
|
1405
|
+
methods: {
|
|
1406
|
+
execute: { implementation_types: ["script", "remote-function"] },
|
|
1407
|
+
get_form_state: { implementation_types: ["script", "remote-function"] }
|
|
1408
|
+
},
|
|
1409
|
+
description: "Custom actions let your app define actions that users can add to their automated workflows. When a workflow triggers a custom action, Stripe calls your app to execute it.",
|
|
1410
|
+
generate: (params, context) => {
|
|
1411
|
+
const { id } = params;
|
|
1412
|
+
const { fs: fs4 } = context;
|
|
1413
|
+
const base = _createBaseOutput(
|
|
1414
|
+
{
|
|
1415
|
+
...params,
|
|
1416
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID3,
|
|
1417
|
+
executionProfile: "egress"
|
|
1418
|
+
},
|
|
1419
|
+
context
|
|
1420
|
+
);
|
|
1421
|
+
return {
|
|
1422
|
+
...base,
|
|
1423
|
+
files: [
|
|
1424
|
+
{
|
|
1425
|
+
path: `extensions/${id}/src/index.ts`,
|
|
1426
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID3, "index.ts"),
|
|
1427
|
+
precious: true
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
path: `extensions/${id}/src/custom_input.schema.json`,
|
|
1431
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID3, "custom_input.schema.json"),
|
|
1432
|
+
precious: true
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
1436
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID3, "index.test.ts"),
|
|
1437
|
+
precious: true
|
|
1438
|
+
},
|
|
1439
|
+
...base.files
|
|
1440
|
+
],
|
|
1441
|
+
methods: {
|
|
1442
|
+
execute: {
|
|
1443
|
+
implementation_type: "script",
|
|
1444
|
+
custom_input: {
|
|
1445
|
+
input_schema: {
|
|
1446
|
+
type: "json_schema",
|
|
1447
|
+
content: `extensions/${id}/src/custom_input.schema.json`
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
},
|
|
1451
|
+
get_form_state: {
|
|
1452
|
+
implementation_type: "script"
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
var extend_workflows_custom_action_default = {
|
|
1459
|
+
[EXTENSION_INTERFACE_ID3]: extendCustomActionTemplate
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
// src/templates/extensions/billing.customer_balance_application.ts
|
|
1463
|
+
var EXTENSION_INTERFACE_ID4 = "billing.customer_balance_application";
|
|
1464
|
+
var customerBalanceApplicationTemplate = {
|
|
1465
|
+
methods: {
|
|
1466
|
+
compute_applied_customer_balance: { implementation_types: ["script"] }
|
|
1467
|
+
},
|
|
1468
|
+
description: "Implement custom logic to control when and how much customer balance is applied to subscription invoices using scripts.",
|
|
1469
|
+
generate: (params, context) => {
|
|
1470
|
+
const { id } = params;
|
|
1471
|
+
const { fs: fs4 } = context;
|
|
1472
|
+
const base = _createBaseOutput(
|
|
1473
|
+
{
|
|
1474
|
+
...params,
|
|
1475
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID4,
|
|
1476
|
+
executionProfile: "restricted"
|
|
1477
|
+
},
|
|
1478
|
+
context
|
|
1479
|
+
);
|
|
1480
|
+
return {
|
|
1481
|
+
...base,
|
|
1482
|
+
files: [
|
|
1483
|
+
{
|
|
1484
|
+
path: `extensions/${id}/src/index.ts`,
|
|
1485
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID4, "index.ts"),
|
|
1486
|
+
precious: true
|
|
1487
|
+
},
|
|
1488
|
+
{
|
|
1489
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
1490
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID4, "index.test.ts"),
|
|
1491
|
+
precious: true
|
|
1492
|
+
},
|
|
1493
|
+
...base.files
|
|
1494
|
+
],
|
|
1495
|
+
methods: {
|
|
1496
|
+
compute_applied_customer_balance: {
|
|
1497
|
+
implementation_type: "script"
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
var billing_customer_balance_application_default = {
|
|
1504
|
+
[EXTENSION_INTERFACE_ID4]: customerBalanceApplicationTemplate
|
|
1505
|
+
};
|
|
1506
|
+
|
|
1507
|
+
// src/templates/extensions/billing.bill.discount_calculation.ts
|
|
1508
|
+
var EXTENSION_INTERFACE_ID5 = "billing.bill.discount_calculation";
|
|
1509
|
+
var discountCalculationTemplate = {
|
|
1510
|
+
hidden: true,
|
|
1511
|
+
methods: {
|
|
1512
|
+
compute_discounts: { implementation_types: ["script"] }
|
|
1513
|
+
},
|
|
1514
|
+
description: "Create coupons with custom scripting logic for dynamic discounts based on subscription attributes, customer metadata, and complex business rules.",
|
|
1515
|
+
generate: (params, context) => {
|
|
1516
|
+
const { id } = params;
|
|
1517
|
+
const { fs: fs4 } = context;
|
|
1518
|
+
const base = _createBaseOutput(
|
|
1519
|
+
{
|
|
1520
|
+
...params,
|
|
1521
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID5,
|
|
1522
|
+
executionProfile: "restricted"
|
|
1523
|
+
},
|
|
1524
|
+
context
|
|
1525
|
+
);
|
|
1526
|
+
return {
|
|
1527
|
+
...base,
|
|
1528
|
+
files: [
|
|
1529
|
+
{
|
|
1530
|
+
path: `extensions/${id}/src/index.ts`,
|
|
1531
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID5, "index.ts"),
|
|
1532
|
+
precious: true
|
|
1533
|
+
},
|
|
1534
|
+
{
|
|
1535
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
1536
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID5, "index.test.ts"),
|
|
1537
|
+
precious: true
|
|
1538
|
+
},
|
|
1539
|
+
...base.files
|
|
1540
|
+
],
|
|
1541
|
+
methods: {
|
|
1542
|
+
compute_discounts: {
|
|
1543
|
+
implementation_type: "script"
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
var billing_bill_discount_calculation_default = {
|
|
1550
|
+
[EXTENSION_INTERFACE_ID5]: discountCalculationTemplate
|
|
1551
|
+
};
|
|
1552
|
+
|
|
1553
|
+
// src/templates/extensions/billing.invoice_collection_setting.ts
|
|
1554
|
+
var EXTENSION_INTERFACE_ID6 = "billing.invoice_collection_setting";
|
|
1555
|
+
var invoiceCollectionSettingTemplate = {
|
|
1556
|
+
hidden: true,
|
|
1557
|
+
methods: {
|
|
1558
|
+
collection_override: { implementation_types: ["script"] }
|
|
1559
|
+
},
|
|
1560
|
+
description: "Use Stripe Scripts to create custom invoice collection logic that controls how your integration handles invoices generated from subscriptions.",
|
|
1561
|
+
generate: (params, context) => {
|
|
1562
|
+
const { id } = params;
|
|
1563
|
+
const { fs: fs4 } = context;
|
|
1564
|
+
const base = _createBaseOutput(
|
|
1565
|
+
{
|
|
1566
|
+
...params,
|
|
1567
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID6,
|
|
1568
|
+
executionProfile: "restricted"
|
|
1569
|
+
},
|
|
1570
|
+
context
|
|
1571
|
+
);
|
|
1572
|
+
return {
|
|
1573
|
+
...base,
|
|
1574
|
+
files: [
|
|
1575
|
+
{
|
|
1576
|
+
path: `extensions/${id}/src/index.ts`,
|
|
1577
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID6, "index.ts"),
|
|
1578
|
+
precious: true
|
|
1579
|
+
},
|
|
1580
|
+
{
|
|
1581
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
1582
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID6, "index.test.ts"),
|
|
1583
|
+
precious: true
|
|
1584
|
+
},
|
|
1585
|
+
...base.files
|
|
1586
|
+
],
|
|
1587
|
+
methods: {
|
|
1588
|
+
collection_override: {
|
|
1589
|
+
implementation_type: "script"
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
var billing_invoice_collection_setting_default = {
|
|
1596
|
+
[EXTENSION_INTERFACE_ID6]: invoiceCollectionSettingTemplate
|
|
1597
|
+
};
|
|
1598
|
+
|
|
1599
|
+
// src/templates/extensions/billing.prorations.ts
|
|
1600
|
+
var EXTENSION_INTERFACE_ID7 = "billing.prorations";
|
|
1601
|
+
var prorationsTemplate = {
|
|
1602
|
+
methods: {
|
|
1603
|
+
prorate_items: { implementation_types: ["script"] }
|
|
1604
|
+
},
|
|
1605
|
+
description: "Create custom proration logic for subscriptions using scripts to handle upgrades, downgrades, and mid-cycle changes.",
|
|
1606
|
+
generate: (params, context) => {
|
|
1607
|
+
const { id } = params;
|
|
1608
|
+
const { fs: fs4 } = context;
|
|
1609
|
+
const base = _createBaseOutput(
|
|
1610
|
+
{
|
|
1611
|
+
...params,
|
|
1612
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID7,
|
|
1613
|
+
executionProfile: "restricted"
|
|
1614
|
+
},
|
|
1615
|
+
context
|
|
1616
|
+
);
|
|
1617
|
+
return {
|
|
1618
|
+
...base,
|
|
1619
|
+
files: [
|
|
1620
|
+
{
|
|
1621
|
+
path: `extensions/${id}/src/index.ts`,
|
|
1622
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID7, "index.ts"),
|
|
1623
|
+
precious: true
|
|
1624
|
+
},
|
|
1625
|
+
{
|
|
1626
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
1627
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID7, "index.test.ts"),
|
|
1628
|
+
precious: true
|
|
1629
|
+
},
|
|
1630
|
+
...base.files
|
|
1631
|
+
],
|
|
1632
|
+
methods: {
|
|
1633
|
+
prorate_items: {
|
|
1634
|
+
implementation_type: "script"
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1640
|
+
var billing_prorations_default = {
|
|
1641
|
+
[EXTENSION_INTERFACE_ID7]: prorationsTemplate
|
|
1642
|
+
};
|
|
1643
|
+
|
|
1644
|
+
// src/templates/extensions/billing.recurring_billing_item_handling.ts
|
|
1645
|
+
var EXTENSION_INTERFACE_ID8 = "billing.recurring_billing_item_handling";
|
|
1646
|
+
var template = {
|
|
1647
|
+
methods: {
|
|
1648
|
+
before_item_creation: { implementation_types: ["script"] },
|
|
1649
|
+
filter_items: { implementation_types: ["script"] },
|
|
1650
|
+
group_items: { implementation_types: ["script"] }
|
|
1651
|
+
},
|
|
1652
|
+
description: "Customize how recurring billing items are filtered, grouped, and created during subscription billing runs.",
|
|
1653
|
+
generate: (params, context) => {
|
|
1654
|
+
const { id } = params;
|
|
1655
|
+
const { fs: fs4 } = context;
|
|
1656
|
+
const base = _createBaseOutput(
|
|
1657
|
+
{
|
|
1658
|
+
...params,
|
|
1659
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID8,
|
|
1660
|
+
executionProfile: "restricted"
|
|
1661
|
+
},
|
|
1662
|
+
context
|
|
1663
|
+
);
|
|
1664
|
+
return {
|
|
1665
|
+
...base,
|
|
1666
|
+
files: [
|
|
1667
|
+
{
|
|
1668
|
+
path: `extensions/${id}/src/index.ts`,
|
|
1669
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID8, "index.ts"),
|
|
1670
|
+
precious: true
|
|
1671
|
+
},
|
|
1672
|
+
{
|
|
1673
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
1674
|
+
content: fs4.textFile(EXTENSION_INTERFACE_ID8, "index.test.ts"),
|
|
1675
|
+
precious: true
|
|
1676
|
+
},
|
|
1677
|
+
...base.files
|
|
1678
|
+
],
|
|
1679
|
+
methods: {
|
|
1680
|
+
before_item_creation: {
|
|
1681
|
+
implementation_type: "script"
|
|
1682
|
+
},
|
|
1683
|
+
filter_items: {
|
|
1684
|
+
implementation_type: "script"
|
|
1685
|
+
},
|
|
1686
|
+
group_items: {
|
|
1687
|
+
implementation_type: "script"
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
var billing_recurring_billing_item_handling_default = {
|
|
1694
|
+
[EXTENSION_INTERFACE_ID8]: template
|
|
1695
|
+
};
|
|
1696
|
+
|
|
1697
|
+
// src/templates/extensions/registry.ts
|
|
1698
|
+
var DEFAULT_TEMPLATES = {
|
|
1699
|
+
...core_workflows_custom_action_default,
|
|
1700
|
+
...extend_objects_custom_objects_default,
|
|
1701
|
+
...extend_workflows_custom_action_default,
|
|
1702
|
+
...billing_customer_balance_application_default,
|
|
1703
|
+
...billing_bill_discount_calculation_default,
|
|
1704
|
+
...billing_invoice_collection_setting_default,
|
|
1705
|
+
...billing_prorations_default,
|
|
1706
|
+
...billing_recurring_billing_item_handling_default
|
|
1707
|
+
};
|
|
1708
|
+
var registry_default = DEFAULT_TEMPLATES;
|
|
1709
|
+
|
|
1710
|
+
// src/templates/extensions/index.ts
|
|
1711
|
+
var _templateManager = new _ExtensionTemplateManager(
|
|
1712
|
+
registry_default,
|
|
1713
|
+
_fs.scope("extensions")
|
|
1714
|
+
);
|
|
1715
|
+
|
|
1716
|
+
// src/custom-objects/generated/proto/vendor/publicapi/v2ext.pb.ts
|
|
1717
|
+
var FieldPresence = {
|
|
1718
|
+
/** FIELD_PRESENCE_INVALID - buf:lint:ignore ENUM_ZERO_VALUE_SUFFIX */
|
|
1719
|
+
FIELD_PRESENCE_INVALID: "FIELD_PRESENCE_INVALID",
|
|
1720
|
+
PRESENT: "PRESENT",
|
|
1721
|
+
NOT_PRESENT: "NOT_PRESENT",
|
|
1722
|
+
PRESENT_BUT_NULL: "PRESENT_BUT_NULL",
|
|
1723
|
+
UNRECOGNIZED: "UNRECOGNIZED"
|
|
1724
|
+
};
|
|
1725
|
+
|
|
1726
|
+
// src/custom-objects/generated/proto/custom_objects/pub/api/common/schema.pb.ts
|
|
1727
|
+
var DataType = {
|
|
1728
|
+
UNSPECIFIED: "UNSPECIFIED",
|
|
1729
|
+
STRING_TYPE: "STRING_TYPE",
|
|
1730
|
+
INTEGER_TYPE: "INTEGER_TYPE",
|
|
1731
|
+
BOOLEAN_TYPE: "BOOLEAN_TYPE",
|
|
1732
|
+
ENUM_TYPE: "ENUM_TYPE",
|
|
1733
|
+
OBJECT_REFERENCE_TYPE: "OBJECT_REFERENCE_TYPE",
|
|
1734
|
+
DATETIME_TYPE: "DATETIME_TYPE",
|
|
1735
|
+
UNRECOGNIZED: "UNRECOGNIZED"
|
|
1736
|
+
};
|
|
1737
|
+
|
|
1738
|
+
// src/custom-objects/to-proto-json.ts
|
|
1739
|
+
function toUpsertRequestJson(buildArtifact) {
|
|
1740
|
+
const { platformMetadata, fields, actions } = buildArtifact;
|
|
1741
|
+
return {
|
|
1742
|
+
apiNameSingular: platformMetadata.apiName,
|
|
1743
|
+
apiNamePlural: platformMetadata.apiNamePlural,
|
|
1744
|
+
displayNameSingular: platformMetadata.displayName,
|
|
1745
|
+
displayNamePlural: platformMetadata.displayNamePlural,
|
|
1746
|
+
description: fields.jsonSchema.description,
|
|
1747
|
+
primaryDisplayProperty: buildArtifact.primaryDisplayProperty ?? "",
|
|
1748
|
+
secondaryDisplayProperty: buildArtifact.secondaryDisplayProperty,
|
|
1749
|
+
properties: mapProperties(fields.jsonSchema),
|
|
1750
|
+
apiActions: mapActions(actions),
|
|
1751
|
+
metadata: void 0,
|
|
1752
|
+
targetCompartment: void 0
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
function mapActions(actions) {
|
|
1756
|
+
const result = {};
|
|
1757
|
+
for (const [apiName, action] of Object.entries(actions)) {
|
|
1758
|
+
result[apiName] = {
|
|
1759
|
+
scriptActionDefinition: {
|
|
1760
|
+
scriptMethodName: action.methodName,
|
|
1761
|
+
inputProperties: mapProperties(action.input?.jsonSchema),
|
|
1762
|
+
outputProperties: mapProperties(action.output.jsonSchema)
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
return result;
|
|
1767
|
+
}
|
|
1768
|
+
function mapProperties(schema) {
|
|
1769
|
+
const result = {};
|
|
1770
|
+
const requiredSet = new Set(schema?.required ?? []);
|
|
1771
|
+
if (!schema?.properties) return result;
|
|
1772
|
+
const defs = schema.$defs ?? {};
|
|
1773
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
1774
|
+
result[key] = toFieldSchema(resolveRef(propSchema, defs), requiredSet.has(key));
|
|
1775
|
+
}
|
|
1776
|
+
return result;
|
|
1777
|
+
}
|
|
1778
|
+
function resolveRef(schema, defs) {
|
|
1779
|
+
const ref = schema.$ref;
|
|
1780
|
+
if (!ref) return schema;
|
|
1781
|
+
if (!ref.startsWith("#/$defs/")) return schema;
|
|
1782
|
+
const defName = ref.slice("#/$defs/".length);
|
|
1783
|
+
const defSchema = defs[defName];
|
|
1784
|
+
if (!defSchema) return schema;
|
|
1785
|
+
const { $ref: _, ...rest } = schema;
|
|
1786
|
+
return { ...defSchema, ...rest };
|
|
1787
|
+
}
|
|
1788
|
+
function toFieldSchema(schema, required) {
|
|
1789
|
+
const enumValues = extractEnumValues(schema);
|
|
1790
|
+
const refTarget = schema.type === "object" ? getRefTargetType(schema) : null;
|
|
1791
|
+
const dataType = resolveDataType(schema, enumValues, refTarget);
|
|
1792
|
+
const fieldSchema = {
|
|
1793
|
+
type: dataType,
|
|
1794
|
+
values: [],
|
|
1795
|
+
valuesPresence: FieldPresence.NOT_PRESENT
|
|
1796
|
+
};
|
|
1797
|
+
if (schema.title !== void 0) fieldSchema.title = schema.title;
|
|
1798
|
+
if (schema.description !== void 0) fieldSchema.description = schema.description;
|
|
1799
|
+
if (required) fieldSchema.required = true;
|
|
1800
|
+
if (schema.deprecated) fieldSchema.deprecated = true;
|
|
1801
|
+
if (refTarget !== null) fieldSchema.referencedType = refTarget;
|
|
1802
|
+
if (schema.minLength !== void 0) fieldSchema.minLength = schema.minLength;
|
|
1803
|
+
if (schema.maxLength !== void 0) fieldSchema.maxLength = schema.maxLength;
|
|
1804
|
+
if (schema.minimum !== void 0) fieldSchema.minimum = toValueBoundary(schema.minimum);
|
|
1805
|
+
if (schema.exclusiveMinimum !== void 0) {
|
|
1806
|
+
fieldSchema.exclusiveMinimum = toValueBoundary(schema.exclusiveMinimum);
|
|
1807
|
+
}
|
|
1808
|
+
if (schema.maximum !== void 0) fieldSchema.maximum = toValueBoundary(schema.maximum);
|
|
1809
|
+
if (schema.exclusiveMaximum !== void 0) {
|
|
1810
|
+
fieldSchema.exclusiveMaximum = toValueBoundary(schema.exclusiveMaximum);
|
|
1811
|
+
}
|
|
1812
|
+
if (enumValues) {
|
|
1813
|
+
fieldSchema.values = enumValues;
|
|
1814
|
+
fieldSchema.valuesPresence = FieldPresence.PRESENT;
|
|
1815
|
+
}
|
|
1816
|
+
if (schema.default !== void 0) {
|
|
1817
|
+
fieldSchema.default = toDefaultValue(schema.default, fieldSchema.type);
|
|
1818
|
+
}
|
|
1819
|
+
return fieldSchema;
|
|
1820
|
+
}
|
|
1821
|
+
function resolveDataType(schema, enumValues, refTarget) {
|
|
1822
|
+
if (enumValues) return DataType.ENUM_TYPE;
|
|
1823
|
+
if (refTarget !== null) return DataType.OBJECT_REFERENCE_TYPE;
|
|
1824
|
+
switch (schema.type) {
|
|
1825
|
+
case "string":
|
|
1826
|
+
if (schema.format === "date-time") return DataType.DATETIME_TYPE;
|
|
1827
|
+
return DataType.STRING_TYPE;
|
|
1828
|
+
case "integer":
|
|
1829
|
+
case "number":
|
|
1830
|
+
return DataType.INTEGER_TYPE;
|
|
1831
|
+
case "boolean":
|
|
1832
|
+
return DataType.BOOLEAN_TYPE;
|
|
1833
|
+
default:
|
|
1834
|
+
return DataType.UNSPECIFIED;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
function getRefTargetType(schema) {
|
|
1838
|
+
if (!schema.properties) return null;
|
|
1839
|
+
const properties = schema.properties;
|
|
1840
|
+
const realProps = Object.keys(properties).filter((k) => !k.startsWith("__"));
|
|
1841
|
+
const allowedProps = /* @__PURE__ */ new Set(["id", "type", "url"]);
|
|
1842
|
+
if (realProps.length === 0 || realProps.some((k) => !allowedProps.has(k))) return null;
|
|
1843
|
+
const idProp = properties.id;
|
|
1844
|
+
const typeProp = properties.type;
|
|
1845
|
+
if (!idProp || !typeProp) return null;
|
|
1846
|
+
if (idProp.type !== "string") return null;
|
|
1847
|
+
const requiredSet = new Set(schema.required ?? []);
|
|
1848
|
+
if (!requiredSet.has("id") || !requiredSet.has("type")) return null;
|
|
1849
|
+
return extractSingleLiteral(typeProp);
|
|
1850
|
+
}
|
|
1851
|
+
function extractSingleLiteral(schema) {
|
|
1852
|
+
if (Array.isArray(schema.enum) && schema.enum.length === 1 && typeof schema.enum[0] === "string") {
|
|
1853
|
+
return schema.enum[0];
|
|
1854
|
+
}
|
|
1855
|
+
if (Array.isArray(schema.oneOf) && schema.oneOf.length === 1 && schema.oneOf.every(
|
|
1856
|
+
(item) => "const" in item && typeof item.const === "string"
|
|
1857
|
+
)) {
|
|
1858
|
+
const values = schema.oneOf.map(
|
|
1859
|
+
(item) => String(item.const)
|
|
1860
|
+
);
|
|
1861
|
+
return values[0] ?? null;
|
|
1862
|
+
}
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
function extractEnumValues(schema) {
|
|
1866
|
+
if (schema.enum) {
|
|
1867
|
+
return schema.enum.map(String);
|
|
1868
|
+
}
|
|
1869
|
+
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0 && schema.oneOf.every((item) => "const" in item)) {
|
|
1870
|
+
return schema.oneOf.map((item) => String(item.const));
|
|
1871
|
+
}
|
|
1872
|
+
return null;
|
|
1873
|
+
}
|
|
1874
|
+
function toValueBoundary(value) {
|
|
1875
|
+
if (!Number.isInteger(value)) {
|
|
1876
|
+
throw new Error(
|
|
1877
|
+
`ValueBoundary only supports integers, got ${String(value)}. Custom object numeric fields are integer-only.`
|
|
1878
|
+
);
|
|
1879
|
+
}
|
|
1880
|
+
return { value: { $case: "integerBoundary", value } };
|
|
1881
|
+
}
|
|
1882
|
+
function toDefaultValue(value, dataType) {
|
|
1883
|
+
if (dataType === DataType.STRING_TYPE && typeof value === "string") {
|
|
1884
|
+
return { value: { $case: "stringDefault", value } };
|
|
1885
|
+
}
|
|
1886
|
+
if (dataType === DataType.INTEGER_TYPE && typeof value === "number") {
|
|
1887
|
+
return { value: { $case: "integerDefault", value } };
|
|
1888
|
+
}
|
|
1889
|
+
if (dataType === DataType.BOOLEAN_TYPE && typeof value === "boolean") {
|
|
1890
|
+
return { value: { $case: "booleanDefault", value } };
|
|
1891
|
+
}
|
|
1892
|
+
return { value: void 0 };
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
// src/custom-objects/build-definitions.ts
|
|
1896
|
+
import { _createCliContext as _createCliContext2 } from "@stripe/extensibility-tool-utils";
|
|
1897
|
+
async function analyzeAndInjectManifest(options) {
|
|
1898
|
+
const context = options.context ?? _createCliContext2();
|
|
1899
|
+
const manifestPath = options.manifestPath ?? "stripe-app.yaml";
|
|
1900
|
+
const projectRoot = options.projectRoot ?? path2.dirname(path2.resolve(manifestPath));
|
|
1901
|
+
const resolvedManifestPath = path2.resolve(manifestPath);
|
|
1902
|
+
const manifest = await _ManifestV2.load(resolvedManifestPath);
|
|
1903
|
+
const customObjects = manifest.getCustomObjects();
|
|
1904
|
+
if (customObjects.length === 0) {
|
|
1905
|
+
return null;
|
|
1906
|
+
}
|
|
1907
|
+
const targets = customObjects.map((obj) => ({
|
|
1908
|
+
modulePath: path2.resolve(projectRoot, obj.path),
|
|
1909
|
+
exportName: obj.name
|
|
1910
|
+
}));
|
|
1911
|
+
context.ux.log(
|
|
1912
|
+
`Building ${String(customObjects.length)} custom object definition(s)...`
|
|
1913
|
+
);
|
|
1914
|
+
for (const obj of customObjects) {
|
|
1915
|
+
context.ux.log(` ${obj.path}#${obj.name}`);
|
|
1916
|
+
}
|
|
1917
|
+
const packageBuild = customObjectTools.buildCustomObjectPackage({ targets });
|
|
1918
|
+
const errorDiagnostics = packageBuild.diagnostics.filter(
|
|
1919
|
+
(diagnostic) => diagnostic.severity === "error"
|
|
1920
|
+
);
|
|
1921
|
+
if (errorDiagnostics.length > 0) {
|
|
1922
|
+
const details = errorDiagnostics.map((diagnostic) => diagnostic.message).join("; ");
|
|
1923
|
+
throw new Error(details);
|
|
1924
|
+
}
|
|
1925
|
+
const coPackageJsonPath = path2.join(projectRoot, "custom-objects", "package.json");
|
|
1926
|
+
const coVersion = readPackageVersion(coPackageJsonPath);
|
|
1927
|
+
const coDependencies = readPackageDependencies(coPackageJsonPath);
|
|
1928
|
+
let manifestModified = false;
|
|
1929
|
+
for (const obj of customObjects) {
|
|
1930
|
+
const absoluteModulePath = path2.resolve(projectRoot, obj.path);
|
|
1931
|
+
const objectKey = customObjectTools.toObjectKey({
|
|
1932
|
+
modulePath: absoluteModulePath,
|
|
1933
|
+
exportName: obj.name
|
|
1934
|
+
});
|
|
1935
|
+
const artifact = packageBuild.objects[objectKey];
|
|
1936
|
+
if (!artifact) {
|
|
1937
|
+
throw new Error(
|
|
1938
|
+
`Package build did not return an artifact for "${obj.name}" (${obj.path}).`
|
|
1939
|
+
);
|
|
1940
|
+
}
|
|
1941
|
+
const hasActions = Object.keys(artifact.actions).length > 0;
|
|
1942
|
+
if (!hasActions) {
|
|
1943
|
+
continue;
|
|
1944
|
+
}
|
|
1945
|
+
const transformedCode = packageBuild.transformedModules[absoluteModulePath];
|
|
1946
|
+
if (!transformedCode) {
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
const extensionId = `methods.${obj.id}`;
|
|
1950
|
+
const scriptContentPath = `extensions/${extensionId}/src/index.ts`;
|
|
1951
|
+
const templateOutput = await _templateManager.generate(
|
|
1952
|
+
"extend.objects.custom_objects",
|
|
1953
|
+
{ id: extensionId, name: `${obj.id} methods`, version: coVersion }
|
|
1954
|
+
);
|
|
1955
|
+
manifest.addOrUpdateExtension({
|
|
1956
|
+
id: extensionId,
|
|
1957
|
+
name: `${obj.id} methods`,
|
|
1958
|
+
interface_id: "extend.objects.custom_objects",
|
|
1959
|
+
version: manifest.getRawData().version,
|
|
1960
|
+
script: {
|
|
1961
|
+
type: "typescript",
|
|
1962
|
+
content: scriptContentPath
|
|
1963
|
+
},
|
|
1964
|
+
permissions: [],
|
|
1965
|
+
methods: templateOutput.methods
|
|
1966
|
+
});
|
|
1967
|
+
manifestModified = true;
|
|
1968
|
+
}
|
|
1969
|
+
if (manifestModified) {
|
|
1970
|
+
await manifest.save();
|
|
1971
|
+
context.ux.log(" Updated stripe-app.yaml with methods extension entries");
|
|
1972
|
+
}
|
|
1973
|
+
return {
|
|
1974
|
+
packageBuild,
|
|
1975
|
+
customObjects,
|
|
1976
|
+
manifest,
|
|
1977
|
+
coVersion,
|
|
1978
|
+
coDependencies,
|
|
1979
|
+
projectRoot
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
async function writeCustomObjectArtifacts(options, state) {
|
|
1983
|
+
const context = options.context ?? _createCliContext2();
|
|
1984
|
+
const targetPath = path2.resolve(options.targetPath);
|
|
1985
|
+
const { packageBuild, customObjects, coVersion, coDependencies, projectRoot } = state;
|
|
1986
|
+
for (const obj of customObjects) {
|
|
1987
|
+
const absoluteModulePath = path2.resolve(projectRoot, obj.path);
|
|
1988
|
+
const objectKey = customObjectTools.toObjectKey({
|
|
1989
|
+
modulePath: absoluteModulePath,
|
|
1990
|
+
exportName: obj.name
|
|
1991
|
+
});
|
|
1992
|
+
const artifact = packageBuild.objects[objectKey];
|
|
1993
|
+
if (!artifact) {
|
|
1994
|
+
throw new Error(
|
|
1995
|
+
`Package build did not return an artifact for "${obj.name}" (${obj.path}).`
|
|
1996
|
+
);
|
|
1997
|
+
}
|
|
1998
|
+
const requestJson = toUpsertRequestJson(artifact);
|
|
1999
|
+
const definitionPath = path2.join(targetPath, obj.path.replace(/\.tsx?$/, ".json"));
|
|
2000
|
+
fs2.mkdirSync(path2.dirname(definitionPath), { recursive: true });
|
|
2001
|
+
fs2.writeFileSync(definitionPath, JSON.stringify(requestJson, null, 2) + "\n");
|
|
2002
|
+
context.ux.log(` \u2192 ${path2.relative(targetPath, definitionPath)}`);
|
|
2003
|
+
const hasActions = Object.keys(artifact.actions).length > 0;
|
|
2004
|
+
if (!hasActions) {
|
|
2005
|
+
continue;
|
|
2006
|
+
}
|
|
2007
|
+
const transformedCode = packageBuild.transformedModules[absoluteModulePath];
|
|
2008
|
+
if (!transformedCode) {
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
2011
|
+
const extensionId = `methods.${obj.id}`;
|
|
2012
|
+
const scriptContentPath = `extensions/${extensionId}/src/index.ts`;
|
|
2013
|
+
const templateOutput = await _templateManager.generate(
|
|
2014
|
+
"extend.objects.custom_objects",
|
|
2015
|
+
{ id: extensionId, name: `${obj.id} methods`, version: coVersion }
|
|
2016
|
+
);
|
|
2017
|
+
for (const file of templateOutput.files) {
|
|
2018
|
+
const filePath = path2.join(targetPath, file.path);
|
|
2019
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
2020
|
+
if (file.path.endsWith("package.json") && coDependencies) {
|
|
2021
|
+
const pkg = JSON.parse(file.content);
|
|
2022
|
+
pkg.dependencies = coDependencies;
|
|
2023
|
+
fs2.writeFileSync(filePath, JSON.stringify(pkg, null, 2) + "\n");
|
|
2024
|
+
} else {
|
|
2025
|
+
fs2.writeFileSync(filePath, file.content);
|
|
2026
|
+
}
|
|
2027
|
+
context.ux.log(` \u2192 ${path2.relative(targetPath, filePath)}`);
|
|
2028
|
+
}
|
|
2029
|
+
const transformedPath = path2.join(targetPath, scriptContentPath);
|
|
2030
|
+
fs2.mkdirSync(path2.dirname(transformedPath), { recursive: true });
|
|
2031
|
+
fs2.writeFileSync(transformedPath, transformedCode);
|
|
2032
|
+
context.ux.log(` \u2192 ${path2.relative(targetPath, transformedPath)}`);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
function readPackageJson(packageJsonPath) {
|
|
2036
|
+
try {
|
|
2037
|
+
const raw = JSON.parse(fs2.readFileSync(packageJsonPath, "utf8"));
|
|
2038
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
2039
|
+
return raw;
|
|
2040
|
+
}
|
|
2041
|
+
} catch {
|
|
2042
|
+
}
|
|
2043
|
+
return void 0;
|
|
2044
|
+
}
|
|
2045
|
+
function readPackageVersion(packageJsonPath) {
|
|
2046
|
+
const pkg = readPackageJson(packageJsonPath);
|
|
2047
|
+
return typeof pkg?.version === "string" ? pkg.version : "0.0.0";
|
|
2048
|
+
}
|
|
2049
|
+
function readPackageDependencies(packageJsonPath) {
|
|
2050
|
+
const pkg = readPackageJson(packageJsonPath);
|
|
2051
|
+
const deps = pkg?.dependencies;
|
|
2052
|
+
if (typeof deps === "object" && deps !== null && !Array.isArray(deps)) {
|
|
2053
|
+
return deps;
|
|
2054
|
+
}
|
|
2055
|
+
return void 0;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
// src/bin/create-upload-image.ts
|
|
2059
|
+
async function main() {
|
|
2060
|
+
const targetPath = process.argv[2];
|
|
2061
|
+
if (!targetPath) {
|
|
2062
|
+
console.error("Usage: create-upload-image <target-path>");
|
|
2063
|
+
process.exit(1);
|
|
2064
|
+
}
|
|
2065
|
+
let state = null;
|
|
2066
|
+
const manifestPath = "stripe-app.yaml";
|
|
2067
|
+
if (fs3.existsSync(manifestPath)) {
|
|
2068
|
+
state = await analyzeAndInjectManifest({ manifestPath });
|
|
2069
|
+
}
|
|
2070
|
+
fs3.mkdirSync(targetPath, { recursive: true });
|
|
2071
|
+
const tarball = path3.join(os2.tmpdir(), `upload-image-${String(Date.now())}.tgz`);
|
|
2072
|
+
try {
|
|
2073
|
+
execSync2(`pnpm pack --out ${JSON.stringify(tarball)}`, { stdio: "pipe" });
|
|
2074
|
+
execSync2(
|
|
2075
|
+
`tar -xzf ${JSON.stringify(tarball)} -C ${JSON.stringify(targetPath)} --strip-components=1`,
|
|
2076
|
+
{ stdio: "pipe" }
|
|
2077
|
+
);
|
|
2078
|
+
} finally {
|
|
2079
|
+
try {
|
|
2080
|
+
fs3.unlinkSync(tarball);
|
|
2081
|
+
} catch {
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
const extensionsDir = "extensions";
|
|
2085
|
+
if (fs3.existsSync(extensionsDir)) {
|
|
2086
|
+
const entries = fs3.readdirSync(extensionsDir, { withFileTypes: true });
|
|
2087
|
+
for (const entry of entries) {
|
|
2088
|
+
if (!entry.isDirectory()) {
|
|
2089
|
+
continue;
|
|
2090
|
+
}
|
|
2091
|
+
const generatedSrc = path3.join(extensionsDir, entry.name, "generated");
|
|
2092
|
+
if (!fs3.existsSync(generatedSrc)) {
|
|
2093
|
+
continue;
|
|
2094
|
+
}
|
|
2095
|
+
const generatedDst = path3.join(targetPath, extensionsDir, entry.name, "generated");
|
|
2096
|
+
fs3.mkdirSync(generatedDst, { recursive: true });
|
|
2097
|
+
fs3.cpSync(generatedSrc, generatedDst, { recursive: true });
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
if (state) {
|
|
2101
|
+
await writeCustomObjectArtifacts({ targetPath }, state);
|
|
2102
|
+
}
|
|
2103
|
+
const imageMetadata = JSON.stringify(
|
|
2104
|
+
{ image: { version: "1.0", built: (/* @__PURE__ */ new Date()).toISOString() } },
|
|
2105
|
+
null,
|
|
2106
|
+
2
|
|
2107
|
+
);
|
|
2108
|
+
fs3.writeFileSync(path3.join(targetPath, ".image.json"), imageMetadata + "\n");
|
|
2109
|
+
}
|
|
2110
|
+
main().catch((err) => {
|
|
2111
|
+
console.error(err);
|
|
2112
|
+
process.exit(1);
|
|
2113
|
+
});
|