@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,3847 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/bin/gen-workspace.ts
|
|
27
|
+
var import_yargs = __toESM(require("yargs"), 1);
|
|
28
|
+
var import_helpers = require("yargs/helpers");
|
|
29
|
+
var import_extensibility_tool_utils12 = require("@stripe/extensibility-tool-utils");
|
|
30
|
+
|
|
31
|
+
// src/workspace/index.ts
|
|
32
|
+
var import_zod = require("zod");
|
|
33
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
34
|
+
var import_node_fs2 = require("fs");
|
|
35
|
+
var import_promises5 = require("fs/promises");
|
|
36
|
+
var import_node_child_process2 = require("child_process");
|
|
37
|
+
|
|
38
|
+
// src/dependencies/index.ts
|
|
39
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
40
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
41
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
42
|
+
var import_node_child_process = require("child_process");
|
|
43
|
+
var import_package_json = __toESM(require("@npmcli/package-json"), 1);
|
|
44
|
+
var semver = __toESM(require("semver"), 1);
|
|
45
|
+
var import_js_toml = require("js-toml");
|
|
46
|
+
var import_extensibility_tool_utils = require("@stripe/extensibility-tool-utils");
|
|
47
|
+
function _npmDep(name, version) {
|
|
48
|
+
return { type: "npm", name, version };
|
|
49
|
+
}
|
|
50
|
+
function _devNpmDep(name, version) {
|
|
51
|
+
return { type: "dev-npm", name, version };
|
|
52
|
+
}
|
|
53
|
+
function _detectPackageManager(cwd) {
|
|
54
|
+
if (import_node_fs.default.existsSync(import_node_path.default.join(cwd, "pnpm-lock.yaml"))) {
|
|
55
|
+
return "pnpm";
|
|
56
|
+
}
|
|
57
|
+
if (import_node_fs.default.existsSync(import_node_path.default.join(cwd, "yarn.lock"))) {
|
|
58
|
+
return "yarn";
|
|
59
|
+
}
|
|
60
|
+
if (import_node_fs.default.existsSync(import_node_path.default.join(cwd, "package-lock.json"))) {
|
|
61
|
+
return "npm";
|
|
62
|
+
}
|
|
63
|
+
let currentDir = cwd;
|
|
64
|
+
const root = import_node_path.default.parse(currentDir).root;
|
|
65
|
+
while (currentDir !== root) {
|
|
66
|
+
const parentDir = import_node_path.default.dirname(currentDir);
|
|
67
|
+
if (parentDir === currentDir) break;
|
|
68
|
+
if (import_node_fs.default.existsSync(import_node_path.default.join(parentDir, "pnpm-lock.yaml"))) {
|
|
69
|
+
if (import_node_fs.default.existsSync(import_node_path.default.join(parentDir, "pnpm-workspace.yaml"))) {
|
|
70
|
+
return "pnpm";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (import_node_fs.default.existsSync(import_node_path.default.join(parentDir, "yarn.lock"))) {
|
|
74
|
+
return "yarn";
|
|
75
|
+
}
|
|
76
|
+
if (import_node_fs.default.existsSync(import_node_path.default.join(parentDir, "package-lock.json"))) {
|
|
77
|
+
return "npm";
|
|
78
|
+
}
|
|
79
|
+
currentDir = parentDir;
|
|
80
|
+
}
|
|
81
|
+
return "npm";
|
|
82
|
+
}
|
|
83
|
+
var _NpmDependencyHandler = class {
|
|
84
|
+
cwd;
|
|
85
|
+
quiet;
|
|
86
|
+
devDependencies;
|
|
87
|
+
context;
|
|
88
|
+
constructor(options) {
|
|
89
|
+
this.cwd = options.cwd;
|
|
90
|
+
this.quiet = options.quiet;
|
|
91
|
+
this.devDependencies = options.devDependencies ?? false;
|
|
92
|
+
this.context = options.context ?? (0, import_extensibility_tool_utils._createCliContext)();
|
|
93
|
+
}
|
|
94
|
+
get dependencyKey() {
|
|
95
|
+
return this.devDependencies ? "devDependencies" : "dependencies";
|
|
96
|
+
}
|
|
97
|
+
get dependencyLabel() {
|
|
98
|
+
return this.devDependencies ? "devDependencies" : "dependencies";
|
|
99
|
+
}
|
|
100
|
+
/** Check whether the npm dependency is already present in package.json. */
|
|
101
|
+
async check(dep) {
|
|
102
|
+
const packageJsonPath = import_node_path.default.join(this.cwd, "package.json");
|
|
103
|
+
if (!import_node_fs.default.existsSync(packageJsonPath)) {
|
|
104
|
+
return {
|
|
105
|
+
satisfied: false,
|
|
106
|
+
message: `package.json not found at ${this.cwd}`
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const pkgJson = await import_package_json.default.load(this.cwd);
|
|
110
|
+
const rawDeps = pkgJson.content[this.dependencyKey];
|
|
111
|
+
const deps = rawDeps ?? {};
|
|
112
|
+
const currentVersion = deps[dep.name];
|
|
113
|
+
if (currentVersion === void 0) {
|
|
114
|
+
return {
|
|
115
|
+
satisfied: false,
|
|
116
|
+
message: `${dep.name} not found in ${this.dependencyLabel}`
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (currentVersion === dep.version) {
|
|
120
|
+
return {
|
|
121
|
+
satisfied: true,
|
|
122
|
+
currentVersion,
|
|
123
|
+
message: `${dep.name}@${dep.version} already in ${this.dependencyLabel}`
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (this.versionsCompatible(currentVersion, dep.version)) {
|
|
127
|
+
return {
|
|
128
|
+
satisfied: true,
|
|
129
|
+
currentVersion,
|
|
130
|
+
message: `${dep.name}@${currentVersion} satisfies ${dep.version}`
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
satisfied: false,
|
|
135
|
+
currentVersion,
|
|
136
|
+
message: `${dep.name}@${currentVersion} does not satisfy ${dep.version}`
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** Check whether the requested version conflicts with the currently installed version. */
|
|
140
|
+
async checkConflict(dep) {
|
|
141
|
+
const packageJsonPath = import_node_path.default.join(this.cwd, "package.json");
|
|
142
|
+
if (!import_node_fs.default.existsSync(packageJsonPath)) {
|
|
143
|
+
return { hasConflict: false };
|
|
144
|
+
}
|
|
145
|
+
const pkgJson = await import_package_json.default.load(this.cwd);
|
|
146
|
+
const rawDeps = pkgJson.content[this.dependencyKey];
|
|
147
|
+
const deps = rawDeps ?? {};
|
|
148
|
+
const existingVersion = deps[dep.name];
|
|
149
|
+
if (existingVersion === void 0) {
|
|
150
|
+
return { hasConflict: false };
|
|
151
|
+
}
|
|
152
|
+
if (existingVersion === dep.version) {
|
|
153
|
+
return { hasConflict: false };
|
|
154
|
+
}
|
|
155
|
+
const existingMajor = this.extractMajorVersion(existingVersion);
|
|
156
|
+
const requestedMajor = this.extractMajorVersion(dep.version);
|
|
157
|
+
if (existingMajor !== null && requestedMajor !== null && existingMajor !== requestedMajor) {
|
|
158
|
+
return {
|
|
159
|
+
hasConflict: true,
|
|
160
|
+
existingVersion,
|
|
161
|
+
requestedVersion: dep.version,
|
|
162
|
+
message: `Major version mismatch: existing ${existingVersion} vs requested ${dep.version}`
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (!this.versionsCompatible(existingVersion, dep.version)) {
|
|
166
|
+
return {
|
|
167
|
+
hasConflict: true,
|
|
168
|
+
existingVersion,
|
|
169
|
+
requestedVersion: dep.version,
|
|
170
|
+
message: `Incompatible version ranges: existing ${existingVersion} vs requested ${dep.version}`
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return { hasConflict: false };
|
|
174
|
+
}
|
|
175
|
+
/** Add or update the npm dependency in package.json. */
|
|
176
|
+
async add(dep) {
|
|
177
|
+
const packageJsonPath = import_node_path.default.join(this.cwd, "package.json");
|
|
178
|
+
if (!import_node_fs.default.existsSync(packageJsonPath)) {
|
|
179
|
+
throw new Error(`package.json not found at ${this.cwd}`);
|
|
180
|
+
}
|
|
181
|
+
const pkgJson = await import_package_json.default.load(this.cwd);
|
|
182
|
+
const rawDeps = pkgJson.content[this.dependencyKey];
|
|
183
|
+
const deps = rawDeps ?? {};
|
|
184
|
+
const existingVersion = deps[dep.name];
|
|
185
|
+
const isUpdate = existingVersion !== void 0;
|
|
186
|
+
const update = {
|
|
187
|
+
[this.dependencyKey]: {
|
|
188
|
+
...deps,
|
|
189
|
+
[dep.name]: dep.version
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
pkgJson.update(update);
|
|
193
|
+
await pkgJson.save();
|
|
194
|
+
const action = isUpdate ? "update" : "add";
|
|
195
|
+
const message = isUpdate ? `Updated ${dep.name} from ${existingVersion} to ${dep.version} in ${this.dependencyLabel}` : `Added ${dep.name}@${dep.version} to ${this.dependencyLabel}`;
|
|
196
|
+
if (!this.quiet) {
|
|
197
|
+
this.context.ux.log(`\u2713 ${message}`);
|
|
198
|
+
}
|
|
199
|
+
return { action, message };
|
|
200
|
+
}
|
|
201
|
+
/** Run the package manager install command in the working directory. */
|
|
202
|
+
install(_deps) {
|
|
203
|
+
const packageManager = _detectPackageManager(this.cwd);
|
|
204
|
+
if (!this.quiet) {
|
|
205
|
+
this.context.ux.log(`Running ${packageManager} install...`);
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
(0, import_node_child_process.execSync)(`${packageManager} install`, {
|
|
209
|
+
cwd: this.cwd,
|
|
210
|
+
stdio: this.quiet ? "pipe" : "inherit"
|
|
211
|
+
});
|
|
212
|
+
} catch (error) {
|
|
213
|
+
const baseMessage = `Failed to run ${packageManager} install`;
|
|
214
|
+
if (error instanceof Error) {
|
|
215
|
+
return Promise.reject(new Error(`${baseMessage}: ${error.message}`));
|
|
216
|
+
}
|
|
217
|
+
return Promise.reject(new Error(`${baseMessage}: ${String(error)}`));
|
|
218
|
+
}
|
|
219
|
+
return Promise.resolve();
|
|
220
|
+
}
|
|
221
|
+
versionsCompatible(existing, requested) {
|
|
222
|
+
if (existing.startsWith("file:") || requested.startsWith("file:") || existing.startsWith("workspace:") || requested.startsWith("workspace:")) {
|
|
223
|
+
return existing === requested;
|
|
224
|
+
}
|
|
225
|
+
if (!semver.validRange(existing) || !semver.validRange(requested)) {
|
|
226
|
+
return existing === requested;
|
|
227
|
+
}
|
|
228
|
+
return semver.intersects(existing, requested);
|
|
229
|
+
}
|
|
230
|
+
extractMajorVersion(version) {
|
|
231
|
+
if (version.startsWith("file:") || version.startsWith("workspace:")) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
const coerced = semver.coerce(version);
|
|
235
|
+
if (coerced) {
|
|
236
|
+
return coerced.major;
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
var _StripeCliPluginHandler = class {
|
|
242
|
+
stripeConfigDir;
|
|
243
|
+
quiet;
|
|
244
|
+
context;
|
|
245
|
+
constructor(options) {
|
|
246
|
+
this.stripeConfigDir = options.stripeConfigDir;
|
|
247
|
+
this.quiet = options.quiet;
|
|
248
|
+
this.context = options.context ?? (0, import_extensibility_tool_utils._createCliContext)();
|
|
249
|
+
}
|
|
250
|
+
/** Check whether the Stripe CLI plugin is installed and meets the minimum version. */
|
|
251
|
+
check(dep) {
|
|
252
|
+
const pluginsTomlPath = import_node_path.default.join(this.stripeConfigDir, "plugins.toml");
|
|
253
|
+
if (!import_node_fs.default.existsSync(pluginsTomlPath)) {
|
|
254
|
+
return Promise.resolve({
|
|
255
|
+
satisfied: false,
|
|
256
|
+
message: `Stripe CLI plugin '${dep.name}' is not installed (plugins.toml not found)`
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
const pluginsContent = import_node_fs.default.readFileSync(pluginsTomlPath, "utf-8");
|
|
260
|
+
const installedPlugin = this.findPluginInToml(pluginsContent, dep.name);
|
|
261
|
+
if (!installedPlugin) {
|
|
262
|
+
return Promise.resolve({
|
|
263
|
+
satisfied: false,
|
|
264
|
+
message: `Stripe CLI plugin '${dep.name}' is not installed`
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
if (dep.minVersion && installedPlugin.version) {
|
|
268
|
+
if (!this.isVersionSatisfied(installedPlugin.version, dep.minVersion)) {
|
|
269
|
+
return Promise.resolve({
|
|
270
|
+
satisfied: false,
|
|
271
|
+
currentVersion: installedPlugin.version,
|
|
272
|
+
message: `Stripe CLI plugin '${dep.name}' version ${installedPlugin.version} is below minimum ${dep.minVersion}`
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const result = {
|
|
277
|
+
satisfied: true,
|
|
278
|
+
message: `Stripe CLI plugin '${dep.name}' is installed${installedPlugin.version ? ` (version ${installedPlugin.version})` : ""}`
|
|
279
|
+
};
|
|
280
|
+
if (installedPlugin.version) {
|
|
281
|
+
result.currentVersion = installedPlugin.version;
|
|
282
|
+
}
|
|
283
|
+
return Promise.resolve(result);
|
|
284
|
+
}
|
|
285
|
+
/** Check whether the installed plugin version conflicts with the requested minimum. */
|
|
286
|
+
async checkConflict(dep) {
|
|
287
|
+
const checkResult = await this.check(dep);
|
|
288
|
+
if (checkResult.satisfied) {
|
|
289
|
+
return { hasConflict: false };
|
|
290
|
+
}
|
|
291
|
+
if (checkResult.currentVersion && dep.minVersion) {
|
|
292
|
+
return {
|
|
293
|
+
hasConflict: true,
|
|
294
|
+
existingVersion: checkResult.currentVersion,
|
|
295
|
+
requestedVersion: dep.minVersion,
|
|
296
|
+
message: `Installed version ${checkResult.currentVersion} is below minimum ${dep.minVersion}`
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
return { hasConflict: false };
|
|
300
|
+
}
|
|
301
|
+
/** Stage a Stripe CLI plugin for installation; the actual install happens in `install`. */
|
|
302
|
+
async add(dep) {
|
|
303
|
+
const checkResult = await this.check(dep);
|
|
304
|
+
const isUpdate = checkResult.currentVersion !== void 0;
|
|
305
|
+
const action = isUpdate ? "update" : "add";
|
|
306
|
+
const message = isUpdate ? `Will update Stripe CLI plugin '${dep.name}'` : `Will install Stripe CLI plugin '${dep.name}'`;
|
|
307
|
+
return { action, message };
|
|
308
|
+
}
|
|
309
|
+
/** Install Stripe CLI plugins using `stripe plugin install`. */
|
|
310
|
+
async install(deps) {
|
|
311
|
+
for (const dep of deps) {
|
|
312
|
+
const checkResult = await this.check(dep);
|
|
313
|
+
const isUpdate = checkResult.currentVersion !== void 0;
|
|
314
|
+
try {
|
|
315
|
+
(0, import_node_child_process.execSync)(`stripe plugin install ${dep.name}`, {
|
|
316
|
+
stdio: this.quiet ? "pipe" : "inherit"
|
|
317
|
+
});
|
|
318
|
+
const message = isUpdate ? `Updated Stripe CLI plugin '${dep.name}'` : `Installed Stripe CLI plugin '${dep.name}'`;
|
|
319
|
+
if (!this.quiet) {
|
|
320
|
+
this.context.ux.log(`\u2713 ${message}`);
|
|
321
|
+
}
|
|
322
|
+
} catch (error) {
|
|
323
|
+
const baseMessage = `Failed to install Stripe CLI plugin '${dep.name}'`;
|
|
324
|
+
if (error instanceof Error) {
|
|
325
|
+
throw new Error(`${baseMessage}: ${error.message}`);
|
|
326
|
+
}
|
|
327
|
+
throw new Error(`${baseMessage}: ${String(error)}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
findPluginInToml(content, pluginName) {
|
|
332
|
+
try {
|
|
333
|
+
const parsed = (0, import_js_toml.load)(content);
|
|
334
|
+
const plugins = parsed.Plugin;
|
|
335
|
+
if (!plugins || !Array.isArray(plugins)) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
const plugin = plugins.find((p) => p.Shortname === pluginName);
|
|
339
|
+
if (!plugin) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
if (plugin.Release && Array.isArray(plugin.Release) && plugin.Release.length > 0) {
|
|
343
|
+
const currentArch = import_node_os.default.arch();
|
|
344
|
+
const currentOS = import_node_os.default.platform();
|
|
345
|
+
const normalizedArch = currentArch === "x64" ? "amd64" : currentArch;
|
|
346
|
+
const normalizedOS = currentOS === "win32" ? "windows" : currentOS;
|
|
347
|
+
const platformReleases = plugin.Release.filter(
|
|
348
|
+
(r) => r.Arch === normalizedArch && r.OS === normalizedOS
|
|
349
|
+
);
|
|
350
|
+
if (platformReleases.length === 0) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
const localBuildRelease = platformReleases.find(
|
|
354
|
+
(r) => r.Version === "local.build.dev"
|
|
355
|
+
);
|
|
356
|
+
if (localBuildRelease) {
|
|
357
|
+
return { version: "local.build.dev" };
|
|
358
|
+
}
|
|
359
|
+
const versions = platformReleases.map((r) => r.Version).filter((v) => v !== void 0);
|
|
360
|
+
if (versions.length === 0) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
const sortedVersions = versions.map((v) => ({ original: v, parsed: semver.coerce(v) })).filter(
|
|
364
|
+
(v) => v.parsed !== null
|
|
365
|
+
).sort((a, b) => semver.rcompare(a.parsed, b.parsed));
|
|
366
|
+
if (sortedVersions.length === 0) {
|
|
367
|
+
return { version: versions[0] };
|
|
368
|
+
}
|
|
369
|
+
return { version: sortedVersions[0]?.original };
|
|
370
|
+
}
|
|
371
|
+
return { version: plugin.Version };
|
|
372
|
+
} catch {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Check if installed version satisfies the required minimum version.
|
|
378
|
+
*
|
|
379
|
+
* Permissive policy: Non-semver versions (e.g., "local.build.dev", "dev", "canary-abc123")
|
|
380
|
+
* always satisfy version requirements. This avoids forcing upgrades during local development
|
|
381
|
+
* when developers are using locally built plugins.
|
|
382
|
+
*
|
|
383
|
+
* For valid semver versions, uses the semver library for proper semantic version comparison.
|
|
384
|
+
*
|
|
385
|
+
* @param installed - The installed version string
|
|
386
|
+
* @param required - The required minimum version string
|
|
387
|
+
* @returns true if installed version satisfies requirement
|
|
388
|
+
*/
|
|
389
|
+
isVersionSatisfied(installed, required) {
|
|
390
|
+
const installedSemver = semver.coerce(installed);
|
|
391
|
+
const requiredSemver = semver.coerce(required);
|
|
392
|
+
if (!installedSemver) {
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
if (!requiredSemver) {
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
return semver.gte(installedSemver, requiredSemver);
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
function dependencyKey(dep) {
|
|
402
|
+
return `${dep.type}:${dep.name}`;
|
|
403
|
+
}
|
|
404
|
+
var _DependencyManager = class {
|
|
405
|
+
cwd;
|
|
406
|
+
stripeConfigDir;
|
|
407
|
+
quiet;
|
|
408
|
+
context;
|
|
409
|
+
npmHandler;
|
|
410
|
+
devNpmHandler;
|
|
411
|
+
stripeCliPluginHandler;
|
|
412
|
+
/** Internal state: pending changes indexed by dependency key */
|
|
413
|
+
pendingChanges = /* @__PURE__ */ new Map();
|
|
414
|
+
/** Whether commit() has been called */
|
|
415
|
+
committed = false;
|
|
416
|
+
constructor(options = {}) {
|
|
417
|
+
this.cwd = options.cwd ?? process.cwd();
|
|
418
|
+
this.stripeConfigDir = options.stripeConfigDir ?? import_node_path.default.join(import_node_os.default.homedir(), ".config", "stripe");
|
|
419
|
+
this.quiet = options.quiet ?? false;
|
|
420
|
+
this.context = options.context ?? (0, import_extensibility_tool_utils._createCliContext)();
|
|
421
|
+
this.npmHandler = new _NpmDependencyHandler({
|
|
422
|
+
cwd: this.cwd,
|
|
423
|
+
quiet: this.quiet,
|
|
424
|
+
devDependencies: false,
|
|
425
|
+
context: this.context
|
|
426
|
+
});
|
|
427
|
+
this.devNpmHandler = new _NpmDependencyHandler({
|
|
428
|
+
cwd: this.cwd,
|
|
429
|
+
quiet: this.quiet,
|
|
430
|
+
devDependencies: true,
|
|
431
|
+
context: this.context
|
|
432
|
+
});
|
|
433
|
+
this.stripeCliPluginHandler = new _StripeCliPluginHandler({
|
|
434
|
+
stripeConfigDir: this.stripeConfigDir,
|
|
435
|
+
quiet: this.quiet,
|
|
436
|
+
context: this.context
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Analyze dependencies and accumulate changes.
|
|
441
|
+
* Can be called multiple times before commit().
|
|
442
|
+
* Returns the changes for this specific call.
|
|
443
|
+
*/
|
|
444
|
+
async ensureDependencies(input) {
|
|
445
|
+
if (this.committed) {
|
|
446
|
+
throw new Error("DependencyManager has already been committed");
|
|
447
|
+
}
|
|
448
|
+
const { required, optional = [] } = input;
|
|
449
|
+
const changes = [];
|
|
450
|
+
for (const dep of [...required, ...optional]) {
|
|
451
|
+
const change = await this.analyzeDependency(dep);
|
|
452
|
+
changes.push(change);
|
|
453
|
+
this.pendingChanges.set(dependencyKey(dep), change);
|
|
454
|
+
}
|
|
455
|
+
return changes;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Resolve a conflict by marking it for force install.
|
|
459
|
+
* Throws if the dependency is not in conflict state.
|
|
460
|
+
*/
|
|
461
|
+
resolveConflict(dep) {
|
|
462
|
+
if (this.committed) {
|
|
463
|
+
throw new Error("DependencyManager has already been committed");
|
|
464
|
+
}
|
|
465
|
+
const key = dependencyKey(dep);
|
|
466
|
+
const change = this.pendingChanges.get(key);
|
|
467
|
+
if (!change) {
|
|
468
|
+
throw new Error(`Dependency '${dep.name}' not found in pending changes`);
|
|
469
|
+
}
|
|
470
|
+
if (change.action !== "conflict") {
|
|
471
|
+
throw new Error(
|
|
472
|
+
`Dependency '${dep.name}' is not in conflict (action: ${change.action})`
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
change.conflictResolved = true;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Get all pending changes (accumulated across all ensureDependencies calls).
|
|
479
|
+
*/
|
|
480
|
+
getPendingChanges() {
|
|
481
|
+
return Array.from(this.pendingChanges.values());
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Commit all accumulated changes.
|
|
485
|
+
* - For npm deps: updates package.json, optionally runs package manager install
|
|
486
|
+
* - For Stripe CLI deps: always installs (no separate add step)
|
|
487
|
+
*
|
|
488
|
+
* @param options - Commit options. `options.runInstall`: if true, run package manager install for npm deps (default: true)
|
|
489
|
+
* @returns Summary of changes made
|
|
490
|
+
* @throws If called more than once
|
|
491
|
+
*/
|
|
492
|
+
async commit(options = {}) {
|
|
493
|
+
if (this.committed) {
|
|
494
|
+
throw new Error("DependencyManager has already been committed");
|
|
495
|
+
}
|
|
496
|
+
this.committed = true;
|
|
497
|
+
const { runInstall = true } = options;
|
|
498
|
+
let added = 0;
|
|
499
|
+
let updated = 0;
|
|
500
|
+
const npmDeps = [];
|
|
501
|
+
const stripeCliDeps = [];
|
|
502
|
+
for (const change of this.pendingChanges.values()) {
|
|
503
|
+
if (change.action === "already-satisfied") {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
if (change.action === "conflict" && !change.conflictResolved) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
const dep = change.dependency;
|
|
510
|
+
if (dep.type === "npm" || dep.type === "dev-npm") {
|
|
511
|
+
npmDeps.push(dep);
|
|
512
|
+
} else {
|
|
513
|
+
stripeCliDeps.push(dep);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
for (const dep of npmDeps) {
|
|
517
|
+
const handler = dep.type === "npm" ? this.npmHandler : this.devNpmHandler;
|
|
518
|
+
const result = await handler.add(dep);
|
|
519
|
+
if (result.action === "add") {
|
|
520
|
+
added++;
|
|
521
|
+
} else {
|
|
522
|
+
updated++;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (runInstall && npmDeps.length > 0) {
|
|
526
|
+
await this.npmHandler.install(npmDeps);
|
|
527
|
+
}
|
|
528
|
+
if (stripeCliDeps.length > 0) {
|
|
529
|
+
await this.stripeCliPluginHandler.install(stripeCliDeps);
|
|
530
|
+
for (const dep of stripeCliDeps) {
|
|
531
|
+
const change = this.pendingChanges.get(dependencyKey(dep));
|
|
532
|
+
if (change?.action === "add" || change?.action === "conflict" && !change.previousVersion) {
|
|
533
|
+
added++;
|
|
534
|
+
} else {
|
|
535
|
+
updated++;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return { added, updated };
|
|
540
|
+
}
|
|
541
|
+
async analyzeDependency(dep) {
|
|
542
|
+
const handler = this.getHandler(dep);
|
|
543
|
+
const checkResult = await handler.check(dep);
|
|
544
|
+
if (checkResult.satisfied) {
|
|
545
|
+
return {
|
|
546
|
+
dependency: dep,
|
|
547
|
+
action: "already-satisfied",
|
|
548
|
+
message: checkResult.message
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
const conflictResult = await handler.checkConflict(dep);
|
|
552
|
+
if (conflictResult.hasConflict) {
|
|
553
|
+
const result2 = {
|
|
554
|
+
dependency: dep,
|
|
555
|
+
action: "conflict",
|
|
556
|
+
message: conflictResult.message ?? "Dependency conflict detected",
|
|
557
|
+
conflict: conflictResult
|
|
558
|
+
};
|
|
559
|
+
if (conflictResult.existingVersion) {
|
|
560
|
+
result2.previousVersion = conflictResult.existingVersion;
|
|
561
|
+
}
|
|
562
|
+
return result2;
|
|
563
|
+
}
|
|
564
|
+
const isUpdate = checkResult.currentVersion !== void 0;
|
|
565
|
+
const result = {
|
|
566
|
+
dependency: dep,
|
|
567
|
+
action: isUpdate ? "update" : "add",
|
|
568
|
+
message: isUpdate ? `Will update ${dep.name} from ${String(checkResult.currentVersion)}` : `Will add ${dep.name}`
|
|
569
|
+
};
|
|
570
|
+
if (checkResult.currentVersion) {
|
|
571
|
+
result.previousVersion = checkResult.currentVersion;
|
|
572
|
+
}
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
getHandler(dep) {
|
|
576
|
+
switch (dep.type) {
|
|
577
|
+
case "npm":
|
|
578
|
+
return this.npmHandler;
|
|
579
|
+
case "dev-npm":
|
|
580
|
+
return this.devNpmHandler;
|
|
581
|
+
case "stripe-cli-plugin":
|
|
582
|
+
return this.stripeCliPluginHandler;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
// src/workspace/index.ts
|
|
588
|
+
var import_internal = require("@stripe/extensibility-custom-objects-tools/internal");
|
|
589
|
+
var import_extensibility_tool_utils11 = require("@stripe/extensibility-tool-utils");
|
|
590
|
+
|
|
591
|
+
// src/manifest/stripe-app-manifest.ts
|
|
592
|
+
var import_extensibility_tool_utils3 = require("@stripe/extensibility-tool-utils");
|
|
593
|
+
|
|
594
|
+
// src/manifest/manifest-v1.ts
|
|
595
|
+
var import_fs = require("fs");
|
|
596
|
+
function formatCustomObjectEntry(entry) {
|
|
597
|
+
return `${entry.filePath}#${entry.className}`;
|
|
598
|
+
}
|
|
599
|
+
function parseCustomObjectEntry(entryString) {
|
|
600
|
+
const parts = entryString.split("#");
|
|
601
|
+
if (parts.length !== 2) {
|
|
602
|
+
throw new Error(
|
|
603
|
+
`Invalid object entry format: ${entryString}. Expected format: "path/to/file.d.ts#ClassName"`
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
const [filePath, className] = parts;
|
|
607
|
+
if (!filePath || !className) {
|
|
608
|
+
throw new Error(
|
|
609
|
+
`Invalid object entry format: ${entryString}. Expected format: "path/to/file.d.ts#ClassName"`
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
return { filePath, className };
|
|
613
|
+
}
|
|
614
|
+
var _ManifestV1 = class __ManifestV1 {
|
|
615
|
+
constructor(filePath, data) {
|
|
616
|
+
this.filePath = filePath;
|
|
617
|
+
this.data = data;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Load stripe-app.json from disk
|
|
621
|
+
* @param filePath - Path to stripe-app.json
|
|
622
|
+
* @throws Error if file is invalid or cannot be read
|
|
623
|
+
*/
|
|
624
|
+
static load(filePath) {
|
|
625
|
+
if (!(0, import_fs.existsSync)(filePath)) {
|
|
626
|
+
throw new Error(`Manifest file not found: ${filePath}`);
|
|
627
|
+
}
|
|
628
|
+
const content = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
629
|
+
const data = JSON.parse(content);
|
|
630
|
+
if (!data.id) {
|
|
631
|
+
throw new Error('Manifest must contain "id" field');
|
|
632
|
+
}
|
|
633
|
+
return new __ManifestV1(filePath, data);
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Get the app ID from the manifest
|
|
637
|
+
*/
|
|
638
|
+
getAppId() {
|
|
639
|
+
return this.data.id;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Get the app name from the manifest
|
|
643
|
+
*/
|
|
644
|
+
getAppName() {
|
|
645
|
+
return this.data.name;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Get all custom objects as structured entries
|
|
649
|
+
*/
|
|
650
|
+
getCustomObjects() {
|
|
651
|
+
const entries = [];
|
|
652
|
+
if (this.data.exported_custom_objects) {
|
|
653
|
+
for (const entry of this.data.exported_custom_objects) {
|
|
654
|
+
entries.push(parseCustomObjectEntry(entry));
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return entries;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Check if a custom object entry exists in the manifest
|
|
661
|
+
*/
|
|
662
|
+
hasCustomObject(entry) {
|
|
663
|
+
const objects = this.getCustomObjects();
|
|
664
|
+
return objects.some(
|
|
665
|
+
(existing) => existing.filePath === entry.filePath && existing.className === entry.className
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Add a custom object entry to the manifest
|
|
670
|
+
* Does nothing if entry already exists
|
|
671
|
+
* @returns true if entry was added, false if it already existed
|
|
672
|
+
*/
|
|
673
|
+
addCustomObject(entry) {
|
|
674
|
+
if (this.hasCustomObject(entry)) {
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
const entryString = formatCustomObjectEntry(entry);
|
|
678
|
+
this.data.exported_custom_objects ??= [];
|
|
679
|
+
this.data.exported_custom_objects.push(entryString);
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Remove a custom object entry from the manifest
|
|
684
|
+
* @returns true if entry was removed, false if it didn't exist
|
|
685
|
+
*/
|
|
686
|
+
removeCustomObject(entry) {
|
|
687
|
+
if (!this.data.exported_custom_objects) {
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
const entryString = formatCustomObjectEntry(entry);
|
|
691
|
+
const originalLength = this.data.exported_custom_objects.length;
|
|
692
|
+
this.data.exported_custom_objects = this.data.exported_custom_objects.filter(
|
|
693
|
+
(e) => e !== entryString
|
|
694
|
+
);
|
|
695
|
+
if (this.data.exported_custom_objects.length === 0) {
|
|
696
|
+
delete this.data.exported_custom_objects;
|
|
697
|
+
}
|
|
698
|
+
return this.data.exported_custom_objects?.length !== originalLength;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Get the raw manifest data (for reading other fields)
|
|
702
|
+
*/
|
|
703
|
+
getRawData() {
|
|
704
|
+
return this.data;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Save changes back to disk
|
|
708
|
+
*/
|
|
709
|
+
save() {
|
|
710
|
+
(0, import_fs.writeFileSync)(this.filePath, JSON.stringify(this.data, null, 2) + "\n");
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
// src/manifest/manifest-v2.ts
|
|
715
|
+
var import_promises = require("fs/promises");
|
|
716
|
+
var yaml = __toESM(require("yaml"), 1);
|
|
717
|
+
var import_extensibility_tool_utils2 = require("@stripe/extensibility-tool-utils");
|
|
718
|
+
function isNonEmptyString(value) {
|
|
719
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
720
|
+
}
|
|
721
|
+
function isYamlObject(value) {
|
|
722
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
723
|
+
}
|
|
724
|
+
function validateManifestV2Data(data) {
|
|
725
|
+
if (!isNonEmptyString(data.id)) {
|
|
726
|
+
throw new Error('Manifest must contain non-empty "id" field');
|
|
727
|
+
}
|
|
728
|
+
if (!isNonEmptyString(data.name)) {
|
|
729
|
+
throw new Error('Manifest must contain non-empty "name" field');
|
|
730
|
+
}
|
|
731
|
+
if (!isNonEmptyString(data.version)) {
|
|
732
|
+
throw new Error('Manifest must contain non-empty "version" field');
|
|
733
|
+
}
|
|
734
|
+
if ("extensions" in data && !Array.isArray(data.extensions)) {
|
|
735
|
+
throw new Error('Manifest field "extensions" must be an array when provided');
|
|
736
|
+
}
|
|
737
|
+
if ("custom_object_definitions" in data && !isYamlObject(data.custom_object_definitions)) {
|
|
738
|
+
throw new Error(
|
|
739
|
+
'Manifest field "custom_object_definitions" must be an object with a "definitions" array'
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
const coBlock = data.custom_object_definitions;
|
|
743
|
+
const definitions = isYamlObject(coBlock) ? coBlock.definitions : void 0;
|
|
744
|
+
if (!definitions) {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (!Array.isArray(definitions)) {
|
|
748
|
+
throw new Error(
|
|
749
|
+
'Manifest field "custom_object_definitions.definitions" must be an array'
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
for (let i = 0; i < definitions.length; i++) {
|
|
753
|
+
const def = definitions[i];
|
|
754
|
+
const prefix = `custom_object_definitions.definitions[${String(i)}]`;
|
|
755
|
+
if (!isYamlObject(def) || !isNonEmptyString(def.id)) {
|
|
756
|
+
throw new Error(
|
|
757
|
+
`${prefix}: missing or invalid "id" field. Got: ${JSON.stringify(def)}`
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
if (!isYamlObject(def.specification)) {
|
|
761
|
+
throw new Error(
|
|
762
|
+
`${prefix}: missing or invalid "specification" field. Expected: { type: "typescript", content: "path/to/file.ts" }. Got: ${JSON.stringify(def)}`
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
if (!isNonEmptyString(def.specification.type)) {
|
|
766
|
+
throw new Error(
|
|
767
|
+
`${prefix}: missing or invalid "specification.type" field. Got: ${JSON.stringify(def)}`
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
if (!isNonEmptyString(def.specification.content)) {
|
|
771
|
+
throw new Error(
|
|
772
|
+
`${prefix}: missing or invalid "specification.content" field. Got: ${JSON.stringify(def)}`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
var _ManifestV2 = class __ManifestV2 {
|
|
778
|
+
constructor(filePath, data) {
|
|
779
|
+
this.filePath = filePath;
|
|
780
|
+
this.data = data;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Load stripe-app.yaml from disk
|
|
784
|
+
* @param filePath - Path to stripe-app.yaml
|
|
785
|
+
* @throws Error if file is invalid or cannot be read
|
|
786
|
+
*/
|
|
787
|
+
static async load(filePath) {
|
|
788
|
+
try {
|
|
789
|
+
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
790
|
+
const parsed = yaml.parse(content);
|
|
791
|
+
if (!isYamlObject(parsed)) {
|
|
792
|
+
throw new Error("Invalid stripe-app.yaml format: root must be an object");
|
|
793
|
+
}
|
|
794
|
+
validateManifestV2Data(parsed);
|
|
795
|
+
const data = parsed;
|
|
796
|
+
data.extensions ??= [];
|
|
797
|
+
return new __ManifestV2(filePath, data);
|
|
798
|
+
} catch (err) {
|
|
799
|
+
if (err instanceof Error) {
|
|
800
|
+
throw new Error(`Error reading stripe-app.yaml: ${err.message}`);
|
|
801
|
+
}
|
|
802
|
+
throw new Error(`Error reading stripe-app.yaml: ${String(err)}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Get the app ID from the manifest
|
|
807
|
+
*/
|
|
808
|
+
getAppId() {
|
|
809
|
+
return this.data.id;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Get the app name from the manifest
|
|
813
|
+
*/
|
|
814
|
+
getAppName() {
|
|
815
|
+
return this.data.name;
|
|
816
|
+
}
|
|
817
|
+
// ============================================================
|
|
818
|
+
// Custom Objects
|
|
819
|
+
// ============================================================
|
|
820
|
+
/**
|
|
821
|
+
* Get all custom object definitions, parsed into internal format.
|
|
822
|
+
* Entries are guaranteed structurally valid by load-time validation.
|
|
823
|
+
*/
|
|
824
|
+
getCustomObjects() {
|
|
825
|
+
const definitions = this.data.custom_object_definitions?.definitions;
|
|
826
|
+
if (!definitions || definitions.length === 0) {
|
|
827
|
+
return [];
|
|
828
|
+
}
|
|
829
|
+
return definitions.map((def) => ({
|
|
830
|
+
id: def.id,
|
|
831
|
+
type: def.specification.type,
|
|
832
|
+
path: def.specification.content,
|
|
833
|
+
name: (0, import_extensibility_tool_utils2._toPascalCase)(def.id)
|
|
834
|
+
}));
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Check if a custom object exists by id
|
|
838
|
+
*/
|
|
839
|
+
hasCustomObject(id) {
|
|
840
|
+
const definitions = this.data.custom_object_definitions?.definitions;
|
|
841
|
+
if (!definitions) return false;
|
|
842
|
+
return definitions.some((def) => def.id === id);
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Add a custom object definition.
|
|
846
|
+
*
|
|
847
|
+
* Identity is keyed on `id` alone. The export name is derived via
|
|
848
|
+
* `_toPascalCase(id)` at read time, so `id` should be valid snake_case
|
|
849
|
+
* (e.g., "device", "loyalty_card", "http_request").
|
|
850
|
+
*
|
|
851
|
+
* @param id - Object identifier (e.g., "device", "loyalty_card")
|
|
852
|
+
* @param content - Path to the definition file (e.g., "src/device.object.ts")
|
|
853
|
+
* @param type - Specification type, defaults to "typescript"
|
|
854
|
+
* @returns true if added, false if already exists
|
|
855
|
+
*/
|
|
856
|
+
addCustomObject(id, content, type = "typescript") {
|
|
857
|
+
if (this.hasCustomObject(id)) {
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
860
|
+
this.data.custom_object_definitions ??= {};
|
|
861
|
+
this.data.custom_object_definitions.definitions ??= [];
|
|
862
|
+
this.data.custom_object_definitions.definitions.push({
|
|
863
|
+
id,
|
|
864
|
+
specification: { type, content }
|
|
865
|
+
});
|
|
866
|
+
return true;
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Remove a custom object definition by id
|
|
870
|
+
* @param id - Object identifier to remove
|
|
871
|
+
* @returns true if removed, false if not found
|
|
872
|
+
*/
|
|
873
|
+
removeCustomObject(id) {
|
|
874
|
+
const block = this.data.custom_object_definitions;
|
|
875
|
+
if (!block?.definitions) {
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
const originalLength = block.definitions.length;
|
|
879
|
+
block.definitions = block.definitions.filter((def) => def.id !== id);
|
|
880
|
+
const newLength = block.definitions.length;
|
|
881
|
+
if (newLength === 0) {
|
|
882
|
+
delete this.data.custom_object_definitions;
|
|
883
|
+
}
|
|
884
|
+
return newLength < originalLength;
|
|
885
|
+
}
|
|
886
|
+
// ============================================================
|
|
887
|
+
// Extensions
|
|
888
|
+
// ============================================================
|
|
889
|
+
/**
|
|
890
|
+
* Add a new extension or update an existing one by ID
|
|
891
|
+
* If an extension with the provided ID exists, it will be replaced entirely
|
|
892
|
+
* @param extension - Extension configuration with all required fields including id
|
|
893
|
+
* @returns The extension ID
|
|
894
|
+
*/
|
|
895
|
+
addOrUpdateExtension(extension) {
|
|
896
|
+
const newExtension = {
|
|
897
|
+
id: extension.id,
|
|
898
|
+
name: extension.name,
|
|
899
|
+
interface_id: extension.interface_id,
|
|
900
|
+
version: extension.version,
|
|
901
|
+
...extension.description !== void 0 && {
|
|
902
|
+
description: extension.description
|
|
903
|
+
},
|
|
904
|
+
...extension.stripe_version !== void 0 && {
|
|
905
|
+
stripe_version: extension.stripe_version
|
|
906
|
+
},
|
|
907
|
+
...extension.script_entry_point !== void 0 && {
|
|
908
|
+
script_entry_point: extension.script_entry_point
|
|
909
|
+
},
|
|
910
|
+
...extension.script !== void 0 && { script: extension.script },
|
|
911
|
+
permissions: extension.permissions,
|
|
912
|
+
methods: extension.methods,
|
|
913
|
+
...extension.configuration !== void 0 && {
|
|
914
|
+
configuration: extension.configuration
|
|
915
|
+
},
|
|
916
|
+
...extension.endpoints !== void 0 && {
|
|
917
|
+
endpoints: extension.endpoints
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
this.data.extensions ??= [];
|
|
921
|
+
const existingIndex = this.data.extensions.findIndex(
|
|
922
|
+
(ext) => ext.id === extension.id
|
|
923
|
+
);
|
|
924
|
+
if (existingIndex !== -1) {
|
|
925
|
+
this.data.extensions[existingIndex] = newExtension;
|
|
926
|
+
} else {
|
|
927
|
+
this.data.extensions.push(newExtension);
|
|
928
|
+
}
|
|
929
|
+
return extension.id;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Get a read-only view of all extensions
|
|
933
|
+
*/
|
|
934
|
+
getExtensions() {
|
|
935
|
+
return this.data.extensions ?? [];
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Get the raw manifest data (for reading other fields)
|
|
939
|
+
*/
|
|
940
|
+
getRawData() {
|
|
941
|
+
return this.data;
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Save changes back to disk
|
|
945
|
+
* @throws Error if file cannot be written
|
|
946
|
+
*/
|
|
947
|
+
async save() {
|
|
948
|
+
try {
|
|
949
|
+
const updatedContent = yaml.stringify(this.data);
|
|
950
|
+
await (0, import_promises.writeFile)(this.filePath, updatedContent, "utf-8");
|
|
951
|
+
} catch (err) {
|
|
952
|
+
if (err instanceof Error) {
|
|
953
|
+
throw new Error(`Error writing stripe-app.yaml: ${err.message}`);
|
|
954
|
+
}
|
|
955
|
+
throw new Error(`Error writing stripe-app.yaml: ${String(err)}`);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
// src/manifest/stripe-app-manifest.ts
|
|
961
|
+
var _StripeAppManifest = class __StripeAppManifest {
|
|
962
|
+
constructor(manifestPath, version, v1, v2) {
|
|
963
|
+
this.manifestPath = manifestPath;
|
|
964
|
+
this.version = version;
|
|
965
|
+
this.v1 = v1;
|
|
966
|
+
this.v2 = v2;
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Load a manifest file, automatically detecting the version.
|
|
970
|
+
* @param manifestPath - Path to stripe-app.json or stripe-app.yaml
|
|
971
|
+
* @returns Loaded manifest instance
|
|
972
|
+
*/
|
|
973
|
+
static async load(manifestPath) {
|
|
974
|
+
const version = __StripeAppManifest.detectVersion(manifestPath);
|
|
975
|
+
if (version === "v1") {
|
|
976
|
+
const reader = _ManifestV1.load(manifestPath);
|
|
977
|
+
return new __StripeAppManifest(manifestPath, version, reader, null);
|
|
978
|
+
} else {
|
|
979
|
+
const reader = await _ManifestV2.load(manifestPath);
|
|
980
|
+
return new __StripeAppManifest(manifestPath, version, null, reader);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Detect manifest version from file path.
|
|
985
|
+
* TODO: Add content-based detection if needed.
|
|
986
|
+
*/
|
|
987
|
+
static detectVersion(manifestPath) {
|
|
988
|
+
const lowerPath = manifestPath.toLowerCase();
|
|
989
|
+
if (lowerPath.endsWith(".yaml") || lowerPath.endsWith(".yml")) {
|
|
990
|
+
return "v2";
|
|
991
|
+
}
|
|
992
|
+
if (lowerPath.endsWith(".json")) {
|
|
993
|
+
return "v1";
|
|
994
|
+
}
|
|
995
|
+
throw new Error(
|
|
996
|
+
`Cannot detect manifest version from path: ${manifestPath}. Expected .json (v1) or .yaml/.yml (v2) extension.`
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Get the detected manifest version
|
|
1001
|
+
*/
|
|
1002
|
+
getVersion() {
|
|
1003
|
+
return this.version;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Get the manifest file path
|
|
1007
|
+
*/
|
|
1008
|
+
getManifestPath() {
|
|
1009
|
+
return this.manifestPath;
|
|
1010
|
+
}
|
|
1011
|
+
// ============================================================
|
|
1012
|
+
// App Metadata (supported by both V1 and V2)
|
|
1013
|
+
// ============================================================
|
|
1014
|
+
/**
|
|
1015
|
+
* Get the app ID from the manifest
|
|
1016
|
+
*/
|
|
1017
|
+
getAppId() {
|
|
1018
|
+
if (this.v1) {
|
|
1019
|
+
return this.v1.getAppId();
|
|
1020
|
+
}
|
|
1021
|
+
if (this.v2) {
|
|
1022
|
+
return this.v2.getAppId();
|
|
1023
|
+
}
|
|
1024
|
+
return void 0;
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Get the app name from the manifest
|
|
1028
|
+
*/
|
|
1029
|
+
getAppName() {
|
|
1030
|
+
if (this.v1) {
|
|
1031
|
+
return this.v1.getAppName();
|
|
1032
|
+
}
|
|
1033
|
+
if (this.v2) {
|
|
1034
|
+
return this.v2.getAppName();
|
|
1035
|
+
}
|
|
1036
|
+
return void 0;
|
|
1037
|
+
}
|
|
1038
|
+
// ============================================================
|
|
1039
|
+
// Custom Objects (supported by both V1 and V2)
|
|
1040
|
+
// ============================================================
|
|
1041
|
+
/**
|
|
1042
|
+
* Get all custom objects from the manifest
|
|
1043
|
+
*/
|
|
1044
|
+
getCustomObjects() {
|
|
1045
|
+
if (this.v1) {
|
|
1046
|
+
return this.v1.getCustomObjects();
|
|
1047
|
+
}
|
|
1048
|
+
if (this.v2) {
|
|
1049
|
+
return this.v2.getCustomObjects().map((obj) => ({
|
|
1050
|
+
filePath: obj.path,
|
|
1051
|
+
className: obj.name
|
|
1052
|
+
}));
|
|
1053
|
+
}
|
|
1054
|
+
return [];
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Check if a custom object entry exists in the manifest.
|
|
1058
|
+
*
|
|
1059
|
+
* **V2 note:** Identity is keyed on `_toSnakeCase(className)` alone — `filePath`
|
|
1060
|
+
* is not considered. Two entries with the same class name but different file
|
|
1061
|
+
* paths are treated as the same object. The `_toSnakeCase` → `_toPascalCase`
|
|
1062
|
+
* round-trip is lossless for typical class names but lossy for consecutive
|
|
1063
|
+
* uppercase (e.g., `HTTPClient` → `http_client` → `HttpClient`). The manifest
|
|
1064
|
+
* `id` field is the source of truth; see `_CustomObjectDefinitionYaml` docs.
|
|
1065
|
+
*/
|
|
1066
|
+
hasCustomObject(entry) {
|
|
1067
|
+
if (this.v1) {
|
|
1068
|
+
return this.v1.hasCustomObject(entry);
|
|
1069
|
+
}
|
|
1070
|
+
if (this.v2) {
|
|
1071
|
+
return this.v2.hasCustomObject((0, import_extensibility_tool_utils3._toSnakeCase)(entry.className));
|
|
1072
|
+
}
|
|
1073
|
+
return false;
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Add a custom object entry to the manifest.
|
|
1077
|
+
*
|
|
1078
|
+
* **V2 note:** The manifest `id` is derived as `_toSnakeCase(className)` and
|
|
1079
|
+
* `filePath` is stored as the specification `content` path. Identity/dedup
|
|
1080
|
+
* is based on `id` alone — see `hasCustomObject` for details.
|
|
1081
|
+
*
|
|
1082
|
+
* @param entry - The custom object entry to add
|
|
1083
|
+
* @returns true if entry was added, false if it already existed
|
|
1084
|
+
*/
|
|
1085
|
+
addCustomObject(entry) {
|
|
1086
|
+
if (this.v1) {
|
|
1087
|
+
return this.v1.addCustomObject(entry);
|
|
1088
|
+
}
|
|
1089
|
+
if (this.v2) {
|
|
1090
|
+
return this.v2.addCustomObject((0, import_extensibility_tool_utils3._toSnakeCase)(entry.className), entry.filePath);
|
|
1091
|
+
}
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Remove a custom object entry from the manifest.
|
|
1096
|
+
*
|
|
1097
|
+
* **V2 note:** Removal is keyed on `_toSnakeCase(className)` — `filePath` is
|
|
1098
|
+
* not considered. See `hasCustomObject` for identity semantics.
|
|
1099
|
+
*
|
|
1100
|
+
* @param entry - The custom object entry to remove
|
|
1101
|
+
* @returns true if entry was removed, false if it didn't exist
|
|
1102
|
+
*/
|
|
1103
|
+
removeCustomObject(entry) {
|
|
1104
|
+
if (this.v1) {
|
|
1105
|
+
return this.v1.removeCustomObject(entry);
|
|
1106
|
+
}
|
|
1107
|
+
if (this.v2) {
|
|
1108
|
+
return this.v2.removeCustomObject((0, import_extensibility_tool_utils3._toSnakeCase)(entry.className));
|
|
1109
|
+
}
|
|
1110
|
+
return false;
|
|
1111
|
+
}
|
|
1112
|
+
// ============================================================
|
|
1113
|
+
// Extensions (V2 only)
|
|
1114
|
+
// ============================================================
|
|
1115
|
+
/**
|
|
1116
|
+
* Get all extensions from the manifest
|
|
1117
|
+
* @throws Error if manifest version doesn't support extensions
|
|
1118
|
+
*/
|
|
1119
|
+
getExtensions() {
|
|
1120
|
+
if (this.v2) {
|
|
1121
|
+
return this.v2.getExtensions();
|
|
1122
|
+
}
|
|
1123
|
+
throw new Error("Extensions are not supported in V1 manifests (stripe-app.json)");
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Add or update an extension in the manifest.
|
|
1127
|
+
* If an extension with the same ID exists, it will be replaced.
|
|
1128
|
+
*
|
|
1129
|
+
* @param config - Full extension configuration
|
|
1130
|
+
* @returns The extension ID
|
|
1131
|
+
* @throws Error if manifest version doesn't support extensions
|
|
1132
|
+
*/
|
|
1133
|
+
addOrUpdateExtension(config) {
|
|
1134
|
+
if (this.v2) {
|
|
1135
|
+
return this.v2.addOrUpdateExtension(config);
|
|
1136
|
+
}
|
|
1137
|
+
throw new Error("Extensions are not supported in V1 manifests (stripe-app.json)");
|
|
1138
|
+
}
|
|
1139
|
+
// ============================================================
|
|
1140
|
+
// Persistence
|
|
1141
|
+
// ============================================================
|
|
1142
|
+
/**
|
|
1143
|
+
* Save changes back to disk
|
|
1144
|
+
*/
|
|
1145
|
+
async save() {
|
|
1146
|
+
if (this.v1) {
|
|
1147
|
+
this.v1.save();
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
if (this.v2) {
|
|
1151
|
+
await this.v2.save();
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
throw new Error("No reader loaded - this should never happen");
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
|
|
1158
|
+
// src/templates/template-manager.ts
|
|
1159
|
+
var import_extensibility_tool_utils4 = require("@stripe/extensibility-tool-utils");
|
|
1160
|
+
|
|
1161
|
+
// src/templates/fs/in-memory.ts
|
|
1162
|
+
var import_extensibility_tool_utils5 = require("@stripe/extensibility-tool-utils");
|
|
1163
|
+
|
|
1164
|
+
// templates-virtual:./_impl.js
|
|
1165
|
+
var TEMPLATE_FS_IMAGE = [
|
|
1166
|
+
{
|
|
1167
|
+
path: "extensions/billing.bill.discount_calculation/index.test.ts",
|
|
1168
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
1169
|
+
|
|
1170
|
+
import MyDiscountCalculation from './index.js';
|
|
1171
|
+
|
|
1172
|
+
describe('MyDiscountCalculation', () => {
|
|
1173
|
+
let instance: MyDiscountCalculation;
|
|
1174
|
+
|
|
1175
|
+
beforeEach(() => {
|
|
1176
|
+
instance = new MyDiscountCalculation();
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
it('should be constructable', () => {
|
|
1180
|
+
expect(instance).toBeInstanceOf(MyDiscountCalculation);
|
|
1181
|
+
});
|
|
1182
|
+
});
|
|
1183
|
+
`
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
path: "extensions/billing.bill.discount_calculation/index.ts",
|
|
1187
|
+
content: `import type { Billing } from '@stripe/extensibility-sdk/extensions';
|
|
1188
|
+
import type { Context } from '@stripe/extensibility-sdk/extensions';
|
|
1189
|
+
|
|
1190
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
1191
|
+
interface MyDiscountCalculationConfig extends Record<string, unknown> {}
|
|
1192
|
+
|
|
1193
|
+
export default class MyDiscountCalculation implements Billing.Bill
|
|
1194
|
+
.DiscountCalculation<MyDiscountCalculationConfig> {
|
|
1195
|
+
computeDiscounts(
|
|
1196
|
+
request: Billing.Bill.DiscountCalculation.DiscountableItem,
|
|
1197
|
+
_config: MyDiscountCalculationConfig,
|
|
1198
|
+
_context: Context
|
|
1199
|
+
) {
|
|
1200
|
+
// TODO: implement your discount logic here
|
|
1201
|
+
|
|
1202
|
+
return {
|
|
1203
|
+
discount: { amount: request.grossAmount },
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
`
|
|
1208
|
+
},
|
|
1209
|
+
{
|
|
1210
|
+
path: "extensions/billing.customer_balance_application/index.test.ts",
|
|
1211
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
1212
|
+
|
|
1213
|
+
import MyCustomerBalanceApplication from './index.js';
|
|
1214
|
+
|
|
1215
|
+
describe('MyCustomerBalanceApplication', () => {
|
|
1216
|
+
let instance: MyCustomerBalanceApplication;
|
|
1217
|
+
|
|
1218
|
+
beforeEach(() => {
|
|
1219
|
+
instance = new MyCustomerBalanceApplication();
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
it('should be constructable', () => {
|
|
1223
|
+
expect(instance).toBeInstanceOf(MyCustomerBalanceApplication);
|
|
1224
|
+
});
|
|
1225
|
+
});
|
|
1226
|
+
`
|
|
1227
|
+
},
|
|
1228
|
+
{
|
|
1229
|
+
path: "extensions/billing.customer_balance_application/index.ts",
|
|
1230
|
+
content: `import type { Billing, Context } from '@stripe/extensibility-sdk/extensions';
|
|
1231
|
+
|
|
1232
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
1233
|
+
interface MyCustomerBalanceApplicationConfig extends Record<string, unknown> {}
|
|
1234
|
+
|
|
1235
|
+
export default class MyCustomerBalanceApplication implements Billing.CustomerBalanceApplication<MyCustomerBalanceApplicationConfig> {
|
|
1236
|
+
computeAppliedCustomerBalance(
|
|
1237
|
+
request: Billing.CustomerBalanceApplication.CustomerBalanceApplicationInput,
|
|
1238
|
+
_config: MyCustomerBalanceApplicationConfig,
|
|
1239
|
+
_context: Context
|
|
1240
|
+
) {
|
|
1241
|
+
// TODO: implement your customer balance logic here
|
|
1242
|
+
|
|
1243
|
+
return {
|
|
1244
|
+
appliedCustomerBalance: request.customerBalance,
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
`
|
|
1249
|
+
},
|
|
1250
|
+
{
|
|
1251
|
+
path: "extensions/billing.invoice_collection_setting/index.test.ts",
|
|
1252
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
1253
|
+
|
|
1254
|
+
import MyInvoiceCollectionSetting from './index.js';
|
|
1255
|
+
|
|
1256
|
+
describe('MyInvoiceCollectionSetting', () => {
|
|
1257
|
+
let instance: MyInvoiceCollectionSetting;
|
|
1258
|
+
|
|
1259
|
+
beforeEach(() => {
|
|
1260
|
+
instance = new MyInvoiceCollectionSetting();
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
it('should be constructable', () => {
|
|
1264
|
+
expect(instance).toBeInstanceOf(MyInvoiceCollectionSetting);
|
|
1265
|
+
});
|
|
1266
|
+
});
|
|
1267
|
+
`
|
|
1268
|
+
},
|
|
1269
|
+
{
|
|
1270
|
+
path: "extensions/billing.invoice_collection_setting/index.ts",
|
|
1271
|
+
content: `import type { Billing, Context } from '@stripe/extensibility-sdk/extensions';
|
|
1272
|
+
|
|
1273
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
1274
|
+
interface MyInvoiceCollectionSettingConfig extends Record<string, unknown> {}
|
|
1275
|
+
|
|
1276
|
+
export default class MyInvoiceCollectionSetting implements Billing.InvoiceCollectionSetting<MyInvoiceCollectionSettingConfig> {
|
|
1277
|
+
collectionOverride(
|
|
1278
|
+
_request: Billing.InvoiceCollectionSetting.InvoiceCollectionRequest,
|
|
1279
|
+
_config: MyInvoiceCollectionSettingConfig,
|
|
1280
|
+
_context: Context
|
|
1281
|
+
) {
|
|
1282
|
+
// TODO: implement your collection setting logic here
|
|
1283
|
+
|
|
1284
|
+
return {};
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
`
|
|
1288
|
+
},
|
|
1289
|
+
{
|
|
1290
|
+
path: "extensions/billing.prorations/index.test.ts",
|
|
1291
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
1292
|
+
|
|
1293
|
+
import MyProrations from './index.js';
|
|
1294
|
+
|
|
1295
|
+
describe('MyProrations', () => {
|
|
1296
|
+
let instance: MyProrations;
|
|
1297
|
+
|
|
1298
|
+
beforeEach(() => {
|
|
1299
|
+
instance = new MyProrations();
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
it('should be constructable', () => {
|
|
1303
|
+
expect(instance).toBeInstanceOf(MyProrations);
|
|
1304
|
+
});
|
|
1305
|
+
});
|
|
1306
|
+
`
|
|
1307
|
+
},
|
|
1308
|
+
{
|
|
1309
|
+
path: "extensions/billing.prorations/index.ts",
|
|
1310
|
+
content: `import type { Billing, Context } from '@stripe/extensibility-sdk/extensions';
|
|
1311
|
+
|
|
1312
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
1313
|
+
interface MyProrationsConfig extends Record<string, unknown> {}
|
|
1314
|
+
|
|
1315
|
+
export default class MyProrations implements Billing.Prorations<MyProrationsConfig> {
|
|
1316
|
+
prorateItems(
|
|
1317
|
+
_request: Billing.Prorations.ProrateItemsInput,
|
|
1318
|
+
_config: MyProrationsConfig,
|
|
1319
|
+
_context: Context
|
|
1320
|
+
) {
|
|
1321
|
+
// TODO: implement your proration logic here
|
|
1322
|
+
|
|
1323
|
+
return {
|
|
1324
|
+
items: [],
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
`
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
path: "extensions/billing.recurring_billing_item_handling/index.test.ts",
|
|
1332
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
1333
|
+
|
|
1334
|
+
import MyRecurringBillingItemHandling from './index.js';
|
|
1335
|
+
|
|
1336
|
+
describe('MyRecurringBillingItemHandling', () => {
|
|
1337
|
+
let instance: MyRecurringBillingItemHandling;
|
|
1338
|
+
|
|
1339
|
+
beforeEach(() => {
|
|
1340
|
+
instance = new MyRecurringBillingItemHandling();
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
it('should be constructable', () => {
|
|
1344
|
+
expect(instance).toBeInstanceOf(MyRecurringBillingItemHandling);
|
|
1345
|
+
});
|
|
1346
|
+
});
|
|
1347
|
+
`
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
path: "extensions/billing.recurring_billing_item_handling/index.ts",
|
|
1351
|
+
content: `import type { Billing, Context } from '@stripe/extensibility-sdk/extensions';
|
|
1352
|
+
|
|
1353
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
1354
|
+
interface MyRecurringBillingItemHandlingConfig extends Record<string, unknown> {}
|
|
1355
|
+
|
|
1356
|
+
export default class MyRecurringBillingItemHandling implements Billing.RecurringBillingItemHandling<MyRecurringBillingItemHandlingConfig> {
|
|
1357
|
+
beforeItemCreation(
|
|
1358
|
+
_request: Billing.RecurringBillingItemHandling.BeforeItemCreationInput,
|
|
1359
|
+
_config: MyRecurringBillingItemHandlingConfig,
|
|
1360
|
+
_context: Context
|
|
1361
|
+
) {
|
|
1362
|
+
// TODO: implement your before-item-creation logic here
|
|
1363
|
+
|
|
1364
|
+
return {
|
|
1365
|
+
items: [],
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
filterItems(
|
|
1370
|
+
_request: Billing.RecurringBillingItemHandling.FilterItemsInput,
|
|
1371
|
+
_config: MyRecurringBillingItemHandlingConfig,
|
|
1372
|
+
_context: Context
|
|
1373
|
+
) {
|
|
1374
|
+
// TODO: implement your filter-items logic here
|
|
1375
|
+
|
|
1376
|
+
return {
|
|
1377
|
+
items: [],
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
groupItems(
|
|
1382
|
+
_request: Billing.RecurringBillingItemHandling.GroupItemsInput,
|
|
1383
|
+
_config: MyRecurringBillingItemHandlingConfig,
|
|
1384
|
+
_context: Context
|
|
1385
|
+
) {
|
|
1386
|
+
// TODO: implement your group-items logic here
|
|
1387
|
+
|
|
1388
|
+
return {
|
|
1389
|
+
groups: [],
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
`
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
path: "extensions/common/.prettierignore",
|
|
1397
|
+
content: `dist
|
|
1398
|
+
generated
|
|
1399
|
+
node_modules
|
|
1400
|
+
`
|
|
1401
|
+
},
|
|
1402
|
+
{
|
|
1403
|
+
path: "extensions/common/eslint.config.mts.mustache",
|
|
1404
|
+
content: `import eslint from '@eslint/js';
|
|
1405
|
+
import { defineConfig } from 'eslint/config';
|
|
1406
|
+
import tseslint from 'typescript-eslint';
|
|
1407
|
+
import eslintConfigPrettier from 'eslint-config-prettier/flat';
|
|
1408
|
+
|
|
1409
|
+
import globals from 'globals';
|
|
1410
|
+
|
|
1411
|
+
import stripeAppsConfig from '@stripe/extensibility-eslint-plugin';
|
|
1412
|
+
import extensionTypeConfig from '@stripe/extensibility-eslint-plugin/{{extensionInterfaceId}}';
|
|
1413
|
+
|
|
1414
|
+
export default defineConfig([
|
|
1415
|
+
eslint.configs.recommended,
|
|
1416
|
+
...tseslint.configs.recommended,
|
|
1417
|
+
...stripeAppsConfig,
|
|
1418
|
+
...extensionTypeConfig,
|
|
1419
|
+
|
|
1420
|
+
// Global ignores
|
|
1421
|
+
{
|
|
1422
|
+
ignores: ['dist', 'generated', 'node_modules'],
|
|
1423
|
+
},
|
|
1424
|
+
|
|
1425
|
+
// TypeScript source files (with type-checking)
|
|
1426
|
+
{
|
|
1427
|
+
name: 'sources',
|
|
1428
|
+
files: ['src/**/*.ts'],
|
|
1429
|
+
ignores: ['**/*.test.ts', '**/__tests__/**'],
|
|
1430
|
+
languageOptions: {
|
|
1431
|
+
globals: {
|
|
1432
|
+
...globals.node,
|
|
1433
|
+
},
|
|
1434
|
+
parserOptions: {
|
|
1435
|
+
projectService: true,
|
|
1436
|
+
tsconfigRootDir: import.meta.dirname,
|
|
1437
|
+
},
|
|
1438
|
+
},
|
|
1439
|
+
},
|
|
1440
|
+
|
|
1441
|
+
// Test files (type-checking, relaxed rules)
|
|
1442
|
+
{
|
|
1443
|
+
name: 'tests',
|
|
1444
|
+
files: ['src/**/*.test.ts', 'src/**/__tests__/**/*.ts'],
|
|
1445
|
+
languageOptions: {
|
|
1446
|
+
globals: {
|
|
1447
|
+
...globals.node,
|
|
1448
|
+
},
|
|
1449
|
+
parserOptions: {
|
|
1450
|
+
projectService: true,
|
|
1451
|
+
tsconfigRootDir: import.meta.dirname,
|
|
1452
|
+
},
|
|
1453
|
+
},
|
|
1454
|
+
rules: {
|
|
1455
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
1456
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
1457
|
+
'no-console': 'off',
|
|
1458
|
+
},
|
|
1459
|
+
},
|
|
1460
|
+
|
|
1461
|
+
// Config files
|
|
1462
|
+
{
|
|
1463
|
+
name: 'ts-configs',
|
|
1464
|
+
files: ['*.config.m?ts', 'eslint.config.mts'],
|
|
1465
|
+
languageOptions: {
|
|
1466
|
+
globals: {
|
|
1467
|
+
...globals.node,
|
|
1468
|
+
},
|
|
1469
|
+
parserOptions: {
|
|
1470
|
+
projectService: false,
|
|
1471
|
+
},
|
|
1472
|
+
},
|
|
1473
|
+
rules: {
|
|
1474
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
1475
|
+
},
|
|
1476
|
+
},
|
|
1477
|
+
|
|
1478
|
+
// JavaScript/MJS files (scripts, configs)
|
|
1479
|
+
{
|
|
1480
|
+
name: 'js-configs',
|
|
1481
|
+
files: ['**/*.js', '**/*.mjs'],
|
|
1482
|
+
languageOptions: {
|
|
1483
|
+
globals: {
|
|
1484
|
+
...globals.node,
|
|
1485
|
+
},
|
|
1486
|
+
parserOptions: {
|
|
1487
|
+
projectService: false,
|
|
1488
|
+
},
|
|
1489
|
+
},
|
|
1490
|
+
rules: {
|
|
1491
|
+
'@typescript-eslint/no-require-imports': 'off',
|
|
1492
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
1493
|
+
'no-unused-vars': 'off',
|
|
1494
|
+
},
|
|
1495
|
+
},
|
|
1496
|
+
|
|
1497
|
+
eslintConfigPrettier,
|
|
1498
|
+
]);
|
|
1499
|
+
`
|
|
1500
|
+
},
|
|
1501
|
+
{
|
|
1502
|
+
path: "extensions/common/package.json.mustache",
|
|
1503
|
+
content: `{
|
|
1504
|
+
"name": "{{id}}",
|
|
1505
|
+
"version": "{{version}}",
|
|
1506
|
+
"description": "{{name}}",
|
|
1507
|
+
"private": true,
|
|
1508
|
+
"license": "~~proprietary~~",
|
|
1509
|
+
"type": "module",
|
|
1510
|
+
"scripts": {
|
|
1511
|
+
"build": "tsc -p tsconfig.build.json && pnpm build:schemas",
|
|
1512
|
+
"build:schemas": "gen-schemas --root ../.. --extension-id \\"$npm_package_name\\" --out-dir generated --schema-name config",
|
|
1513
|
+
"lint": "pnpm lint:types && pnpm lint:eslint && pnpm lint:format",
|
|
1514
|
+
"lint:types": "tsc -p tsconfig.build.json --noEmit && tsc -p tsconfig.json --noEmit",
|
|
1515
|
+
"lint:eslint": "eslint .",
|
|
1516
|
+
"lint:format": "prettier --check .",
|
|
1517
|
+
"fix:lint": "eslint --fix .",
|
|
1518
|
+
"fix:format": "prettier --write .",
|
|
1519
|
+
"test": "vitest --root ../.. run --project \\"$npm_package_name\\"",
|
|
1520
|
+
"test:watch": "pnpm test --watch",
|
|
1521
|
+
"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'",
|
|
1522
|
+
"stripe:regen": "gen-workspace --root ../.. --package-dir . --template-id {{extensionInterfaceId}}"
|
|
1523
|
+
},
|
|
1524
|
+
"lint-staged": {
|
|
1525
|
+
"*.{ts,mts,mjs,js}": ["eslint --fix", "prettier --write"],
|
|
1526
|
+
"*.{json,md,yaml}": "prettier --write"
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
`
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
path: "extensions/common/tsconfig.build.json.mustache",
|
|
1533
|
+
content: `{
|
|
1534
|
+
"extends": "../../tsconfig.base.json",
|
|
1535
|
+
"compilerOptions": {
|
|
1536
|
+
"lib": null,
|
|
1537
|
+
"noLib": true,
|
|
1538
|
+
"outDir": "./dist",
|
|
1539
|
+
"rootDir": "./src"
|
|
1540
|
+
},
|
|
1541
|
+
"include": [
|
|
1542
|
+
"src/**/*.ts",
|
|
1543
|
+
"node_modules/@stripe/extensibility-sdk/tslibs/5.9.3/lib.es2022.{{executionProfile}}.d.ts",
|
|
1544
|
+
"node_modules/@stripe/extensibility-sdk/tslibs/lib.{{executionProfile}}.globals.d.ts"
|
|
1545
|
+
],
|
|
1546
|
+
"exclude": ["src/**/*.test.ts", "src/**/__tests__"]
|
|
1547
|
+
}
|
|
1548
|
+
`
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
path: "extensions/common/tsconfig.json.mustache",
|
|
1552
|
+
content: `{
|
|
1553
|
+
"extends": "../../tsconfig.json",
|
|
1554
|
+
"compilerOptions": {
|
|
1555
|
+
"rootDir": ".",
|
|
1556
|
+
"plugins": [
|
|
1557
|
+
{
|
|
1558
|
+
"name": "@stripe/extensibility-language-server/plugin"
|
|
1559
|
+
}
|
|
1560
|
+
]
|
|
1561
|
+
},
|
|
1562
|
+
"include": [
|
|
1563
|
+
"src/**/*.ts",
|
|
1564
|
+
"node_modules/@stripe/extensibility-sdk/tslibs/lib.{{executionProfile}}.globals.d.ts"
|
|
1565
|
+
],
|
|
1566
|
+
"exclude": ["dist"]
|
|
1567
|
+
}
|
|
1568
|
+
`
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
path: "extensions/core.workflows.custom_action/custom_input.schema.json",
|
|
1572
|
+
content: `{
|
|
1573
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
1574
|
+
"type": "object",
|
|
1575
|
+
"properties": {},
|
|
1576
|
+
"additionalProperties": false
|
|
1577
|
+
}
|
|
1578
|
+
`
|
|
1579
|
+
},
|
|
1580
|
+
{
|
|
1581
|
+
path: "extensions/core.workflows.custom_action/index.test.ts",
|
|
1582
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
1583
|
+
|
|
1584
|
+
import MyCustomAction from './index.js';
|
|
1585
|
+
|
|
1586
|
+
describe('MyCustomAction', () => {
|
|
1587
|
+
let instance: MyCustomAction;
|
|
1588
|
+
|
|
1589
|
+
beforeEach(() => {
|
|
1590
|
+
instance = new MyCustomAction();
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
it('should be constructable', () => {
|
|
1594
|
+
expect(instance).toBeInstanceOf(MyCustomAction);
|
|
1595
|
+
});
|
|
1596
|
+
});
|
|
1597
|
+
`
|
|
1598
|
+
},
|
|
1599
|
+
{
|
|
1600
|
+
path: "extensions/core.workflows.custom_action/index.ts",
|
|
1601
|
+
content: `import type { Core } from '@stripe/extensibility-sdk/extensions';
|
|
1602
|
+
import type { Context } from '@stripe/extensibility-sdk/extensions';
|
|
1603
|
+
|
|
1604
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
1605
|
+
interface MyCustomActionConfig extends Record<string, unknown> {}
|
|
1606
|
+
|
|
1607
|
+
export default class MyCustomAction implements Core.Workflows
|
|
1608
|
+
.CustomAction<MyCustomActionConfig> {
|
|
1609
|
+
execute(
|
|
1610
|
+
_request: Core.Workflows.CustomAction.ExecuteCustomActionRequest,
|
|
1611
|
+
_config: MyCustomActionConfig,
|
|
1612
|
+
_context: Context
|
|
1613
|
+
) {
|
|
1614
|
+
// TODO: implement your action logic here
|
|
1615
|
+
|
|
1616
|
+
return {};
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
getFormState(
|
|
1620
|
+
_request: Core.Workflows.CustomAction.GetFormStateRequest,
|
|
1621
|
+
_config: MyCustomActionConfig,
|
|
1622
|
+
_context: Context
|
|
1623
|
+
) {
|
|
1624
|
+
// TODO: implement your logic here
|
|
1625
|
+
|
|
1626
|
+
return {
|
|
1627
|
+
values: {},
|
|
1628
|
+
config: {},
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
`
|
|
1633
|
+
},
|
|
1634
|
+
{
|
|
1635
|
+
path: "extensions/extend.workflows.custom_action/custom_input.schema.json",
|
|
1636
|
+
content: `{
|
|
1637
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
1638
|
+
"type": "object",
|
|
1639
|
+
"properties": {},
|
|
1640
|
+
"additionalProperties": false
|
|
1641
|
+
}
|
|
1642
|
+
`
|
|
1643
|
+
},
|
|
1644
|
+
{
|
|
1645
|
+
path: "extensions/extend.workflows.custom_action/index.test.ts",
|
|
1646
|
+
content: `import { beforeEach, describe, it, expect } from 'vitest';
|
|
1647
|
+
|
|
1648
|
+
import MyCustomAction from './index.js';
|
|
1649
|
+
|
|
1650
|
+
describe('MyCustomAction', () => {
|
|
1651
|
+
let instance: MyCustomAction;
|
|
1652
|
+
|
|
1653
|
+
beforeEach(() => {
|
|
1654
|
+
instance = new MyCustomAction();
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
it('should be constructable', () => {
|
|
1658
|
+
expect(instance).toBeInstanceOf(MyCustomAction);
|
|
1659
|
+
});
|
|
1660
|
+
});
|
|
1661
|
+
`
|
|
1662
|
+
},
|
|
1663
|
+
{
|
|
1664
|
+
path: "extensions/extend.workflows.custom_action/index.ts",
|
|
1665
|
+
content: `import type { Extend } from '@stripe/extensibility-sdk/extensions';
|
|
1666
|
+
import type { Context } from '@stripe/extensibility-sdk/extensions';
|
|
1667
|
+
|
|
1668
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
1669
|
+
interface MyCustomActionConfig extends Record<string, unknown> {}
|
|
1670
|
+
|
|
1671
|
+
export default class MyCustomAction implements Extend.Workflows
|
|
1672
|
+
.CustomAction<MyCustomActionConfig> {
|
|
1673
|
+
execute(
|
|
1674
|
+
_request: Extend.Workflows.CustomAction.ExecuteCustomActionRequest,
|
|
1675
|
+
_config: MyCustomActionConfig,
|
|
1676
|
+
_context: Context
|
|
1677
|
+
) {
|
|
1678
|
+
// TODO: implement your action logic here
|
|
1679
|
+
|
|
1680
|
+
return {};
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
getFormState(
|
|
1684
|
+
_request: Extend.Workflows.CustomAction.GetFormStateRequest,
|
|
1685
|
+
_config: MyCustomActionConfig,
|
|
1686
|
+
_context: Context
|
|
1687
|
+
) {
|
|
1688
|
+
// TODO: implement your logic here
|
|
1689
|
+
|
|
1690
|
+
return {
|
|
1691
|
+
values: {},
|
|
1692
|
+
config: {},
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
`
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
path: "root/.husky/pre-commit",
|
|
1700
|
+
content: `pnpm lint-staged
|
|
1701
|
+
`
|
|
1702
|
+
},
|
|
1703
|
+
{
|
|
1704
|
+
path: "root/.prettierignore",
|
|
1705
|
+
content: `dist
|
|
1706
|
+
generated
|
|
1707
|
+
node_modules
|
|
1708
|
+
pnpm-lock.yaml
|
|
1709
|
+
.build
|
|
1710
|
+
`
|
|
1711
|
+
},
|
|
1712
|
+
{
|
|
1713
|
+
path: "root/.prettierrc",
|
|
1714
|
+
content: `{
|
|
1715
|
+
"semi": true,
|
|
1716
|
+
"singleQuote": true,
|
|
1717
|
+
"tabWidth": 2,
|
|
1718
|
+
"trailingComma": "es5",
|
|
1719
|
+
"printWidth": 90
|
|
1720
|
+
}
|
|
1721
|
+
`
|
|
1722
|
+
},
|
|
1723
|
+
{
|
|
1724
|
+
path: "root/_gitignore",
|
|
1725
|
+
content: `# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
1726
|
+
|
|
1727
|
+
*~
|
|
1728
|
+
|
|
1729
|
+
# dependencies
|
|
1730
|
+
node_modules
|
|
1731
|
+
dist
|
|
1732
|
+
|
|
1733
|
+
# testing
|
|
1734
|
+
/coverage
|
|
1735
|
+
|
|
1736
|
+
# production
|
|
1737
|
+
/.build
|
|
1738
|
+
|
|
1739
|
+
# misc
|
|
1740
|
+
.DS_Store
|
|
1741
|
+
.env.local
|
|
1742
|
+
.env.development.local
|
|
1743
|
+
.env.test.local
|
|
1744
|
+
.env.production.local
|
|
1745
|
+
|
|
1746
|
+
npm-debug.log*
|
|
1747
|
+
yarn-debug.log*
|
|
1748
|
+
yarn-error.log*
|
|
1749
|
+
install-deps.log
|
|
1750
|
+
|
|
1751
|
+
# generated schemas
|
|
1752
|
+
generated
|
|
1753
|
+
`
|
|
1754
|
+
},
|
|
1755
|
+
{
|
|
1756
|
+
path: "root/custom-objects/package.json",
|
|
1757
|
+
content: `{
|
|
1758
|
+
"name": "custom-objects",
|
|
1759
|
+
"type": "module",
|
|
1760
|
+
"version": "0.0.1",
|
|
1761
|
+
"license": "UNLICENSED",
|
|
1762
|
+
"private": true,
|
|
1763
|
+
"scripts": {
|
|
1764
|
+
"build": "test -d src && custom-objects-build --input src --output dist || true",
|
|
1765
|
+
"lint:types": "test ! -d src || tsc --noEmit",
|
|
1766
|
+
"test": "vitest run"
|
|
1767
|
+
},
|
|
1768
|
+
"dependencies": {
|
|
1769
|
+
"@stripe/extensibility-custom-objects": "0.7.4",
|
|
1770
|
+
"@stripe/extensibility-sdk": "0.22.4"
|
|
1771
|
+
},
|
|
1772
|
+
"devDependencies": {
|
|
1773
|
+
"@stripe/extensibility-custom-objects-tools": "0.40.0",
|
|
1774
|
+
"@stripe/extensibility-test-helpers": "0.2.7"
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
`
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
path: "root/custom-objects/tsconfig.json",
|
|
1781
|
+
content: `{
|
|
1782
|
+
"extends": "../tsconfig.base.json",
|
|
1783
|
+
"compilerOptions": {
|
|
1784
|
+
"module": "ESNext",
|
|
1785
|
+
"moduleResolution": "bundler",
|
|
1786
|
+
"types": ["vitest/globals"]
|
|
1787
|
+
},
|
|
1788
|
+
"exclude": ["dist"]
|
|
1789
|
+
}
|
|
1790
|
+
`
|
|
1791
|
+
},
|
|
1792
|
+
{
|
|
1793
|
+
path: "root/eslint.config.mts",
|
|
1794
|
+
content: `import { readFileSync } from 'node:fs';
|
|
1795
|
+
import { dirname, resolve } from 'node:path';
|
|
1796
|
+
import { fileURLToPath } from 'node:url';
|
|
1797
|
+
import eslint from '@eslint/js';
|
|
1798
|
+
import { defineConfig } from 'eslint/config';
|
|
1799
|
+
import tseslint from 'typescript-eslint';
|
|
1800
|
+
import workspaces from 'eslint-plugin-workspaces';
|
|
1801
|
+
import eslintConfigPrettier from 'eslint-config-prettier/flat';
|
|
1802
|
+
|
|
1803
|
+
import globals from 'globals';
|
|
1804
|
+
|
|
1805
|
+
import stripeAppsConfig from '@stripe/extensibility-eslint-plugin';
|
|
1806
|
+
|
|
1807
|
+
// Read additional ignore globs from package.json (written by the generate plugin
|
|
1808
|
+
// to exclude generated SDK directories from linting).
|
|
1809
|
+
let stripeGlobsToIgnore: string[] = [];
|
|
1810
|
+
try {
|
|
1811
|
+
const configDir = dirname(fileURLToPath(import.meta.url));
|
|
1812
|
+
const packageJsonPath = resolve(configDir, './package.json');
|
|
1813
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
1814
|
+
const globs = pkg?.stripe?.eslintIgnoreGlobs;
|
|
1815
|
+
if (Array.isArray(globs)) {
|
|
1816
|
+
stripeGlobsToIgnore = globs.filter(
|
|
1817
|
+
(g: unknown): g is string => typeof g === 'string'
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
} catch {
|
|
1821
|
+
// package.json not found or unparseable \u2014 ignore
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
export default defineConfig([
|
|
1825
|
+
eslint.configs.recommended,
|
|
1826
|
+
...tseslint.configs.recommended,
|
|
1827
|
+
...stripeAppsConfig,
|
|
1828
|
+
|
|
1829
|
+
// Global ignores
|
|
1830
|
+
{
|
|
1831
|
+
ignores: [
|
|
1832
|
+
'.build',
|
|
1833
|
+
'**/dist',
|
|
1834
|
+
'**/generated',
|
|
1835
|
+
'**/node_modules',
|
|
1836
|
+
'extensions/**',
|
|
1837
|
+
'custom-objects',
|
|
1838
|
+
'ui',
|
|
1839
|
+
...stripeGlobsToIgnore,
|
|
1840
|
+
],
|
|
1841
|
+
},
|
|
1842
|
+
|
|
1843
|
+
// Common rules for all files
|
|
1844
|
+
{
|
|
1845
|
+
plugins: { workspaces },
|
|
1846
|
+
rules: {
|
|
1847
|
+
...workspaces.configs.recommended.rules,
|
|
1848
|
+
},
|
|
1849
|
+
},
|
|
1850
|
+
|
|
1851
|
+
// Config files (vitest.config.ts, eslint.config.ts, etc.)
|
|
1852
|
+
{
|
|
1853
|
+
name: 'ts-configs',
|
|
1854
|
+
files: ['extensions/*/*.config.m?ts', '*.config.m?ts', 'eslint.config.mts'],
|
|
1855
|
+
languageOptions: {
|
|
1856
|
+
globals: {
|
|
1857
|
+
...globals.node,
|
|
1858
|
+
},
|
|
1859
|
+
parserOptions: {
|
|
1860
|
+
projectService: false,
|
|
1861
|
+
},
|
|
1862
|
+
},
|
|
1863
|
+
rules: {
|
|
1864
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
1865
|
+
},
|
|
1866
|
+
},
|
|
1867
|
+
|
|
1868
|
+
// JavaScript/MJS files (scripts, configs)
|
|
1869
|
+
{
|
|
1870
|
+
name: 'js-configs',
|
|
1871
|
+
files: ['**/*.js', '**/*.mjs'],
|
|
1872
|
+
languageOptions: {
|
|
1873
|
+
globals: {
|
|
1874
|
+
...globals.node,
|
|
1875
|
+
},
|
|
1876
|
+
parserOptions: {
|
|
1877
|
+
projectService: false,
|
|
1878
|
+
},
|
|
1879
|
+
},
|
|
1880
|
+
rules: {
|
|
1881
|
+
'@typescript-eslint/no-require-imports': 'off',
|
|
1882
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
1883
|
+
'no-unused-vars': 'off',
|
|
1884
|
+
},
|
|
1885
|
+
},
|
|
1886
|
+
|
|
1887
|
+
eslintConfigPrettier,
|
|
1888
|
+
]);
|
|
1889
|
+
`
|
|
1890
|
+
},
|
|
1891
|
+
{
|
|
1892
|
+
path: "root/package.json.mustache",
|
|
1893
|
+
content: `{
|
|
1894
|
+
"name": "{{ appId }}",
|
|
1895
|
+
"version": "{{ version }}",
|
|
1896
|
+
"description": "{{ appName }}",
|
|
1897
|
+
"private": true,
|
|
1898
|
+
"license": "~~proprietary~~",
|
|
1899
|
+
"engines": {
|
|
1900
|
+
"node": ">=20.0.0"
|
|
1901
|
+
},
|
|
1902
|
+
"packageManager": "pnpm@10.30.3",
|
|
1903
|
+
"scripts": {
|
|
1904
|
+
"build": "pnpm -r --if-present build",
|
|
1905
|
+
"lint": "pnpm lint:types && pnpm lint:eslint && pnpm lint:format",
|
|
1906
|
+
"lint:types": "pnpm -r --if-present lint:types",
|
|
1907
|
+
"lint:eslint": "eslint . --ignore-pattern 'extensions/**' && pnpm -r --filter './extensions/*' --if-present lint:eslint",
|
|
1908
|
+
"lint:format": "prettier --check .",
|
|
1909
|
+
"fix:lint": "eslint --fix . --ignore-pattern 'extensions/**' && pnpm -r --filter './extensions/*' --if-present fix:lint",
|
|
1910
|
+
"fix:format": "prettier --write .",
|
|
1911
|
+
"test": "tsx tools/test.mts",
|
|
1912
|
+
"test:watch": "vitest watch",
|
|
1913
|
+
"check": "pnpm build && pnpm lint && pnpm test",
|
|
1914
|
+
"prepare": "husky",
|
|
1915
|
+
"preimage": "pnpm install --lockfile-only && pnpm build && pnpm lint:types && pnpm lint:eslint && pnpm test",
|
|
1916
|
+
"image": "rm -rf .build && create-upload-image .build",
|
|
1917
|
+
"postimage": "cp pnpm-lock.yaml .build/",
|
|
1918
|
+
"stripe:regen": "gen-workspace --package-dir ."
|
|
1919
|
+
},
|
|
1920
|
+
"lint-staged": {
|
|
1921
|
+
"*.{ts,mts,mjs,js}": ["eslint --fix", "prettier --write"],
|
|
1922
|
+
"*.{json,md,yaml}": "prettier --write"
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
`
|
|
1926
|
+
},
|
|
1927
|
+
{
|
|
1928
|
+
path: "root/pnpm-workspace.yaml",
|
|
1929
|
+
content: `packages:
|
|
1930
|
+
- extensions/*
|
|
1931
|
+
- custom-objects
|
|
1932
|
+
- ui
|
|
1933
|
+
|
|
1934
|
+
overrides:
|
|
1935
|
+
vite: ^6.0.0
|
|
1936
|
+
`
|
|
1937
|
+
},
|
|
1938
|
+
{
|
|
1939
|
+
path: "root/stripe-app.yaml.mustache",
|
|
1940
|
+
content: `$schema: https://stripe.com/stripe-app/v2.0.0/schema
|
|
1941
|
+
id: '{{ appId }}'
|
|
1942
|
+
name: '{{ appName }}'
|
|
1943
|
+
version: 0.0.1
|
|
1944
|
+
declarations:
|
|
1945
|
+
distribution_type: private
|
|
1946
|
+
`
|
|
1947
|
+
},
|
|
1948
|
+
{
|
|
1949
|
+
path: "root/tools/test.mts",
|
|
1950
|
+
content: `#!/usr/bin/env tsx
|
|
1951
|
+
/**
|
|
1952
|
+
* Runs tests across the workspace:
|
|
1953
|
+
* - vitest for script extensions and custom objects (extensions/*)
|
|
1954
|
+
* - jest for UI extensions (ui/)
|
|
1955
|
+
*/
|
|
1956
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
1957
|
+
import { execSync } from 'node:child_process';
|
|
1958
|
+
|
|
1959
|
+
const hasExtensions =
|
|
1960
|
+
existsSync('extensions') &&
|
|
1961
|
+
readdirSync('extensions').some((name) => existsSync(\`extensions/\${name}/package.json\`));
|
|
1962
|
+
|
|
1963
|
+
const hasUI = existsSync('ui/package.json');
|
|
1964
|
+
|
|
1965
|
+
let exitCode = 0;
|
|
1966
|
+
|
|
1967
|
+
function run(cmd: string): void {
|
|
1968
|
+
try {
|
|
1969
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
1970
|
+
} catch (e: unknown) {
|
|
1971
|
+
exitCode = (e as NodeJS.ErrnoException & { status?: number }).status ?? 1;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
if (hasExtensions) {
|
|
1976
|
+
run('vitest run');
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
if (hasUI) {
|
|
1980
|
+
try {
|
|
1981
|
+
execSync('pnpm --filter "./ui" test', { stdio: 'inherit' });
|
|
1982
|
+
} catch {
|
|
1983
|
+
// UI test failures are non-fatal
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
process.exit(exitCode);
|
|
1988
|
+
`
|
|
1989
|
+
},
|
|
1990
|
+
{
|
|
1991
|
+
path: "root/tsconfig.base.json",
|
|
1992
|
+
content: `{
|
|
1993
|
+
"compilerOptions": {
|
|
1994
|
+
"target": "ES2022",
|
|
1995
|
+
"module": "NodeNext",
|
|
1996
|
+
"moduleResolution": "NodeNext",
|
|
1997
|
+
"lib": ["ES2022"],
|
|
1998
|
+
"declaration": true,
|
|
1999
|
+
"declarationMap": true,
|
|
2000
|
+
"esModuleInterop": true,
|
|
2001
|
+
"exactOptionalPropertyTypes": false,
|
|
2002
|
+
"forceConsistentCasingInFileNames": true,
|
|
2003
|
+
"isolatedModules": true,
|
|
2004
|
+
"noFallthroughCasesInSwitch": true,
|
|
2005
|
+
"noImplicitReturns": true,
|
|
2006
|
+
"removeComments": false,
|
|
2007
|
+
"resolveJsonModule": true,
|
|
2008
|
+
"skipLibCheck": true,
|
|
2009
|
+
"sourceMap": true,
|
|
2010
|
+
"strict": true,
|
|
2011
|
+
"types": [],
|
|
2012
|
+
"verbatimModuleSyntax": true
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
`
|
|
2016
|
+
},
|
|
2017
|
+
{
|
|
2018
|
+
path: "root/tsconfig.json",
|
|
2019
|
+
content: `{
|
|
2020
|
+
"extends": "./tsconfig.base.json",
|
|
2021
|
+
"compilerOptions": {
|
|
2022
|
+
"noEmit": true,
|
|
2023
|
+
"plugins": [
|
|
2024
|
+
{
|
|
2025
|
+
"name": "@stripe/extensibility-language-server/plugin"
|
|
2026
|
+
}
|
|
2027
|
+
],
|
|
2028
|
+
"preserveWatchOutput": true,
|
|
2029
|
+
"types": ["vitest/globals", "node"]
|
|
2030
|
+
},
|
|
2031
|
+
"include": ["**/*.ts"],
|
|
2032
|
+
"exclude": ["**/node_modules", "**/dist"]
|
|
2033
|
+
}
|
|
2034
|
+
`
|
|
2035
|
+
},
|
|
2036
|
+
{
|
|
2037
|
+
path: "root/ui/package.json",
|
|
2038
|
+
content: `{
|
|
2039
|
+
"name": "ui",
|
|
2040
|
+
"version": "0.0.1",
|
|
2041
|
+
"license": "UNLICENSED",
|
|
2042
|
+
"private": true,
|
|
2043
|
+
"scripts": {
|
|
2044
|
+
"test": "jest --passWithNoTests"
|
|
2045
|
+
},
|
|
2046
|
+
"dependencies": {
|
|
2047
|
+
"@stripe/ui-extension-sdk": "^9.1.0"
|
|
2048
|
+
},
|
|
2049
|
+
"devDependencies": {
|
|
2050
|
+
"@stripe/ui-extension-tools": "^0.0.1",
|
|
2051
|
+
"@types/jest": "^27.5.2",
|
|
2052
|
+
"@types/react": "^17.0.2"
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
`
|
|
2056
|
+
},
|
|
2057
|
+
{
|
|
2058
|
+
path: "root/vitest.config.mts",
|
|
2059
|
+
content: `import { defineConfig } from 'vitest/config';
|
|
2060
|
+
import { existsSync, readdirSync } from 'fs';
|
|
2061
|
+
import { dirname, resolve } from 'path';
|
|
2062
|
+
import { fileURLToPath } from 'url';
|
|
2063
|
+
|
|
2064
|
+
const rootDir = dirname(fileURLToPath(import.meta.url));
|
|
2065
|
+
const extensionsDir = resolve(rootDir, 'extensions');
|
|
2066
|
+
|
|
2067
|
+
const extensionProjects = existsSync(extensionsDir)
|
|
2068
|
+
? readdirSync(extensionsDir)
|
|
2069
|
+
.filter((name) => existsSync(resolve(extensionsDir, name, 'package.json')))
|
|
2070
|
+
.map((name) => resolve(extensionsDir, name))
|
|
2071
|
+
: [];
|
|
2072
|
+
|
|
2073
|
+
const coProjects = existsSync(resolve(rootDir, 'custom-objects/package.json'))
|
|
2074
|
+
? [resolve(rootDir, 'custom-objects')]
|
|
2075
|
+
: [];
|
|
2076
|
+
|
|
2077
|
+
const projects = [...extensionProjects, ...coProjects];
|
|
2078
|
+
|
|
2079
|
+
if (projects.length === 0) {
|
|
2080
|
+
console.debug(\`No vitest projects detected. This means either:
|
|
2081
|
+
- You have no extension projects defined, in which case this warning is expected.
|
|
2082
|
+
- There is an internal error detecting vitest projects relative to the project root
|
|
2083
|
+
\${rootDir}.
|
|
2084
|
+
\`);
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
export default projects.length > 0
|
|
2088
|
+
? defineConfig({
|
|
2089
|
+
test: {
|
|
2090
|
+
projects,
|
|
2091
|
+
passWithNoTests: true,
|
|
2092
|
+
|
|
2093
|
+
// Only run tests from src, not compiled dist
|
|
2094
|
+
exclude: ['**/node_modules', '**/dist'],
|
|
2095
|
+
// Place snapshots alongside test files instead of in __snapshots__
|
|
2096
|
+
snapshotFormat: {
|
|
2097
|
+
escapeString: false,
|
|
2098
|
+
printBasicPrototype: false,
|
|
2099
|
+
},
|
|
2100
|
+
resolveSnapshotPath: (testPath, snapExtension) => {
|
|
2101
|
+
return testPath.replace(/\\.test\\.ts$/, \`.test\${snapExtension}\`);
|
|
2102
|
+
},
|
|
2103
|
+
},
|
|
2104
|
+
})
|
|
2105
|
+
: defineConfig({ test: { passWithNoTests: true, include: [] } });
|
|
2106
|
+
`
|
|
2107
|
+
}
|
|
2108
|
+
];
|
|
2109
|
+
var _fs = (0, import_extensibility_tool_utils5._createInMemoryTemplateFS)(TEMPLATE_FS_IMAGE);
|
|
2110
|
+
|
|
2111
|
+
// src/templates/simple-templates.ts
|
|
2112
|
+
var import_extensibility_tool_utils6 = require("@stripe/extensibility-tool-utils");
|
|
2113
|
+
|
|
2114
|
+
// src/templates/file-writer.ts
|
|
2115
|
+
var import_path = __toESM(require("path"), 1);
|
|
2116
|
+
var import_promises3 = require("fs/promises");
|
|
2117
|
+
var import_node_readline = require("readline");
|
|
2118
|
+
var import_extensibility_tool_utils7 = require("@stripe/extensibility-tool-utils");
|
|
2119
|
+
|
|
2120
|
+
// src/templates/fs-utils.ts
|
|
2121
|
+
var import_promises2 = require("fs/promises");
|
|
2122
|
+
async function pathExists(filePath, predicate) {
|
|
2123
|
+
try {
|
|
2124
|
+
const stats = await (0, import_promises2.stat)(filePath);
|
|
2125
|
+
return predicate(stats);
|
|
2126
|
+
} catch (error) {
|
|
2127
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
2128
|
+
return false;
|
|
2129
|
+
}
|
|
2130
|
+
throw error;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
async function fileExists(filePath) {
|
|
2134
|
+
return pathExists(filePath, (stats) => stats.isFile());
|
|
2135
|
+
}
|
|
2136
|
+
async function directoryExists(dirPath) {
|
|
2137
|
+
return pathExists(dirPath, (stats) => stats.isDirectory());
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
// src/templates/file-writer.ts
|
|
2141
|
+
var logger = (0, import_extensibility_tool_utils7._createLogger)({ name: "file-writer" });
|
|
2142
|
+
async function _promptOverwrite(filePath) {
|
|
2143
|
+
const prompt = `${filePath} exists. Overwrite [yNaq]? `;
|
|
2144
|
+
if (!process.stdin.isTTY) {
|
|
2145
|
+
const rl = (0, import_node_readline.createInterface)({
|
|
2146
|
+
input: process.stdin,
|
|
2147
|
+
output: process.stdout
|
|
2148
|
+
});
|
|
2149
|
+
return new Promise((resolve) => {
|
|
2150
|
+
rl.question(prompt, (answer) => {
|
|
2151
|
+
rl.close();
|
|
2152
|
+
const normalized = answer.toLowerCase().trim();
|
|
2153
|
+
switch (normalized) {
|
|
2154
|
+
case "y":
|
|
2155
|
+
case "yes":
|
|
2156
|
+
resolve("overwrite");
|
|
2157
|
+
break;
|
|
2158
|
+
case "n":
|
|
2159
|
+
case "no":
|
|
2160
|
+
case "":
|
|
2161
|
+
resolve("skip");
|
|
2162
|
+
break;
|
|
2163
|
+
case "a":
|
|
2164
|
+
case "all":
|
|
2165
|
+
resolve("overwrite-all");
|
|
2166
|
+
break;
|
|
2167
|
+
case "q":
|
|
2168
|
+
case "quit":
|
|
2169
|
+
resolve("abort");
|
|
2170
|
+
break;
|
|
2171
|
+
default:
|
|
2172
|
+
logger.warn("Invalid input '%s', treating as 'no'", answer);
|
|
2173
|
+
resolve("skip");
|
|
2174
|
+
}
|
|
2175
|
+
});
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
process.stdout.write(prompt);
|
|
2179
|
+
return new Promise((resolve) => {
|
|
2180
|
+
const stdin = process.stdin;
|
|
2181
|
+
const wasRaw = stdin.isRaw;
|
|
2182
|
+
stdin.setRawMode(true);
|
|
2183
|
+
stdin.resume();
|
|
2184
|
+
const onData = (buffer) => {
|
|
2185
|
+
stdin.setRawMode(wasRaw || false);
|
|
2186
|
+
stdin.pause();
|
|
2187
|
+
stdin.removeListener("data", onData);
|
|
2188
|
+
const key = buffer.toString();
|
|
2189
|
+
const code = buffer[0];
|
|
2190
|
+
if (code === 3) {
|
|
2191
|
+
process.stdout.write("\nCancelled.\n");
|
|
2192
|
+
setTimeout(() => process.exit(0), 100);
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
if (code === 13 || code === 10) {
|
|
2196
|
+
process.stdout.write("n\n");
|
|
2197
|
+
resolve("skip");
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
const normalized = key.toLowerCase();
|
|
2201
|
+
let decision;
|
|
2202
|
+
switch (normalized) {
|
|
2203
|
+
case "y":
|
|
2204
|
+
process.stdout.write("y\n");
|
|
2205
|
+
decision = "overwrite";
|
|
2206
|
+
break;
|
|
2207
|
+
case "n":
|
|
2208
|
+
process.stdout.write("n\n");
|
|
2209
|
+
decision = "skip";
|
|
2210
|
+
break;
|
|
2211
|
+
case "a":
|
|
2212
|
+
process.stdout.write("a\n");
|
|
2213
|
+
decision = "overwrite-all";
|
|
2214
|
+
break;
|
|
2215
|
+
case "q":
|
|
2216
|
+
process.stdout.write("q\n");
|
|
2217
|
+
decision = "abort";
|
|
2218
|
+
break;
|
|
2219
|
+
default:
|
|
2220
|
+
process.stdout.write(`${key}
|
|
2221
|
+
`);
|
|
2222
|
+
logger.warn("Invalid input '%s', treating as 'no'", key);
|
|
2223
|
+
decision = "skip";
|
|
2224
|
+
}
|
|
2225
|
+
resolve(decision);
|
|
2226
|
+
};
|
|
2227
|
+
stdin.on("data", onData);
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
async function _writeGeneratedFiles(files, options = {}) {
|
|
2231
|
+
const { cwd = process.cwd(), onFileWritten, onFileSkipped, onFileIdentical } = options;
|
|
2232
|
+
let write = options.write ?? "abort-if-existing";
|
|
2233
|
+
const existing = [];
|
|
2234
|
+
for (const file of files) {
|
|
2235
|
+
const fullPath = import_path.default.join(cwd, file.path);
|
|
2236
|
+
if (await fileExists(fullPath)) {
|
|
2237
|
+
existing.push(file.path);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
if (existing.length > 0 && write === "abort-if-existing") {
|
|
2241
|
+
if (onFileSkipped) {
|
|
2242
|
+
for (const file of files) {
|
|
2243
|
+
if (existing.includes(file.path)) {
|
|
2244
|
+
onFileSkipped(file);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
return {
|
|
2249
|
+
written: [],
|
|
2250
|
+
existing,
|
|
2251
|
+
skipped: true,
|
|
2252
|
+
aborted: false
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
const written = [];
|
|
2256
|
+
for (const file of files) {
|
|
2257
|
+
const fullPath = import_path.default.join(cwd, file.path);
|
|
2258
|
+
const fileDir = import_path.default.dirname(fullPath);
|
|
2259
|
+
const isExisting = existing.includes(file.path);
|
|
2260
|
+
if (isExisting) {
|
|
2261
|
+
const existingContent = await (0, import_promises3.readFile)(fullPath, "utf-8");
|
|
2262
|
+
if (existingContent === file.content) {
|
|
2263
|
+
if (onFileIdentical) {
|
|
2264
|
+
onFileIdentical(file);
|
|
2265
|
+
}
|
|
2266
|
+
continue;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
if (isExisting && (write === "if-missing" || file.precious)) {
|
|
2270
|
+
if (onFileSkipped) {
|
|
2271
|
+
onFileSkipped(file);
|
|
2272
|
+
}
|
|
2273
|
+
continue;
|
|
2274
|
+
}
|
|
2275
|
+
if (isExisting && typeof write === "function") {
|
|
2276
|
+
const decision = await write(file.path, cwd, file.content);
|
|
2277
|
+
switch (decision) {
|
|
2278
|
+
case "skip":
|
|
2279
|
+
if (onFileSkipped) {
|
|
2280
|
+
onFileSkipped(file);
|
|
2281
|
+
}
|
|
2282
|
+
continue;
|
|
2283
|
+
case "overwrite":
|
|
2284
|
+
break;
|
|
2285
|
+
case "overwrite-all":
|
|
2286
|
+
write = "overwrite-all";
|
|
2287
|
+
break;
|
|
2288
|
+
case "abort":
|
|
2289
|
+
return {
|
|
2290
|
+
written,
|
|
2291
|
+
existing,
|
|
2292
|
+
skipped: false,
|
|
2293
|
+
aborted: true
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
if (!await directoryExists(fileDir)) {
|
|
2298
|
+
await (0, import_promises3.mkdir)(fileDir, { recursive: true });
|
|
2299
|
+
}
|
|
2300
|
+
await (0, import_promises3.writeFile)(fullPath, file.content, "utf-8");
|
|
2301
|
+
if (onFileWritten) {
|
|
2302
|
+
onFileWritten(file, isExisting);
|
|
2303
|
+
}
|
|
2304
|
+
written.push(file.path);
|
|
2305
|
+
}
|
|
2306
|
+
return {
|
|
2307
|
+
written,
|
|
2308
|
+
existing,
|
|
2309
|
+
// Always populated; write='abort-if-existing' returns early above
|
|
2310
|
+
skipped: false,
|
|
2311
|
+
aborted: false
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
// src/templates/diff-viewer/diff-prompt.ts
|
|
2316
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
2317
|
+
|
|
2318
|
+
// src/templates/diff-viewer/diff-generator.ts
|
|
2319
|
+
var import_diff = require("diff");
|
|
2320
|
+
var import_promises4 = require("fs/promises");
|
|
2321
|
+
async function generateDiff(filePath, oldContent, newContent, contextLines = 3) {
|
|
2322
|
+
if (isBinary(oldContent) || isBinary(newContent)) {
|
|
2323
|
+
return {
|
|
2324
|
+
lines: [{ type: "header", text: "Binary file changed" }],
|
|
2325
|
+
oldFileName: filePath,
|
|
2326
|
+
newFileName: filePath,
|
|
2327
|
+
isBinary: true
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
const patch = (0, import_diff.createPatch)(filePath, oldContent, newContent, "existing", "new", {
|
|
2331
|
+
context: contextLines
|
|
2332
|
+
});
|
|
2333
|
+
const lines = parsePatch(patch);
|
|
2334
|
+
return {
|
|
2335
|
+
lines,
|
|
2336
|
+
oldFileName: filePath,
|
|
2337
|
+
newFileName: filePath,
|
|
2338
|
+
isBinary: false
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
function parsePatch(patch) {
|
|
2342
|
+
const lines = [];
|
|
2343
|
+
const patchLines = patch.split("\n");
|
|
2344
|
+
let oldLine = 0;
|
|
2345
|
+
let newLine = 0;
|
|
2346
|
+
for (const line of patchLines) {
|
|
2347
|
+
if (line.startsWith("@@")) {
|
|
2348
|
+
const match = line.match(/@@ -(\d+),?\d* \+(\d+),?\d* @@/);
|
|
2349
|
+
if (match !== null) {
|
|
2350
|
+
oldLine = parseInt(match[1], 10);
|
|
2351
|
+
newLine = parseInt(match[2], 10);
|
|
2352
|
+
lines.push({ type: "header", text: line });
|
|
2353
|
+
}
|
|
2354
|
+
} else if (line.startsWith("+")) {
|
|
2355
|
+
lines.push({ type: "add", text: line, newLine: newLine++ });
|
|
2356
|
+
} else if (line.startsWith("-")) {
|
|
2357
|
+
lines.push({ type: "remove", text: line, oldLine: oldLine++ });
|
|
2358
|
+
} else if (line.startsWith(" ")) {
|
|
2359
|
+
lines.push({
|
|
2360
|
+
type: "context",
|
|
2361
|
+
text: line,
|
|
2362
|
+
oldLine: oldLine++,
|
|
2363
|
+
newLine: newLine++
|
|
2364
|
+
});
|
|
2365
|
+
} else if (line.startsWith("\\")) {
|
|
2366
|
+
lines.push({ type: "context", text: line });
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
return lines;
|
|
2370
|
+
}
|
|
2371
|
+
function isBinary(content) {
|
|
2372
|
+
return content.includes("\0");
|
|
2373
|
+
}
|
|
2374
|
+
async function readExistingFile(resolvedPath) {
|
|
2375
|
+
try {
|
|
2376
|
+
return await (0, import_promises4.readFile)(resolvedPath, "utf-8");
|
|
2377
|
+
} catch (err) {
|
|
2378
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
2379
|
+
return "";
|
|
2380
|
+
}
|
|
2381
|
+
throw err;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
// src/templates/diff-viewer/terminal-renderer.ts
|
|
2386
|
+
var import_child_process = require("child_process");
|
|
2387
|
+
var import_string_width = __toESM(require("string-width"), 1);
|
|
2388
|
+
var ANSI = {
|
|
2389
|
+
// Colors
|
|
2390
|
+
RED: "\x1B[31m",
|
|
2391
|
+
GREEN: "\x1B[32m",
|
|
2392
|
+
CYAN: "\x1B[36m",
|
|
2393
|
+
GRAY: "\x1B[90m",
|
|
2394
|
+
RESET: "\x1B[0m",
|
|
2395
|
+
// Text styles
|
|
2396
|
+
BOLD: "\x1B[1m",
|
|
2397
|
+
DIM: "\x1B[2m"
|
|
2398
|
+
};
|
|
2399
|
+
var BOX_HORIZONTAL = "\u2500";
|
|
2400
|
+
function render(state, filePath, clearPrevious = false) {
|
|
2401
|
+
const maxTotalHeight = Math.floor(state.terminalHeight * 0.75);
|
|
2402
|
+
const maxContentHeight = maxTotalHeight - 3;
|
|
2403
|
+
const contentHeight = state.diffLines.length;
|
|
2404
|
+
const windowHeight = Math.min(contentHeight, maxContentHeight);
|
|
2405
|
+
const visibleLines = state.diffLines.slice(
|
|
2406
|
+
state.scrollOffset,
|
|
2407
|
+
state.scrollOffset + windowHeight
|
|
2408
|
+
);
|
|
2409
|
+
const totalLines = state.diffLines.length;
|
|
2410
|
+
const startLine = state.scrollOffset + 1;
|
|
2411
|
+
const endLine = state.scrollOffset + visibleLines.length;
|
|
2412
|
+
if (clearPrevious && state.renderedHeight > 0) {
|
|
2413
|
+
clearPreviousRender(state.renderedHeight - 1);
|
|
2414
|
+
}
|
|
2415
|
+
let output = "";
|
|
2416
|
+
output += renderTopBar(filePath, state.terminalWidth) + "\n";
|
|
2417
|
+
for (const line of visibleLines) {
|
|
2418
|
+
output += colorize(line, state.terminalWidth) + "\n";
|
|
2419
|
+
}
|
|
2420
|
+
output += renderBottomBar(startLine, endLine, totalLines, state, state.terminalWidth) + "\n";
|
|
2421
|
+
output += "\n";
|
|
2422
|
+
output += `overwrite ${filePath} [yNaqd]? `;
|
|
2423
|
+
state.renderedHeight = 1 + visibleLines.length + 1 + 1 + 1;
|
|
2424
|
+
process.stdout.write(output);
|
|
2425
|
+
}
|
|
2426
|
+
function clearPreviousRender(lines) {
|
|
2427
|
+
process.stdout.write(`\x1B[${lines}A`);
|
|
2428
|
+
process.stdout.write("\r");
|
|
2429
|
+
process.stdout.write("\x1B[0J");
|
|
2430
|
+
}
|
|
2431
|
+
function renderTopBar(filePath, width) {
|
|
2432
|
+
const prefix = BOX_HORIZONTAL.repeat(3) + " ";
|
|
2433
|
+
const suffix = " " + BOX_HORIZONTAL.repeat(3);
|
|
2434
|
+
const availableWidth = width - (0, import_string_width.default)(prefix) - (0, import_string_width.default)(suffix);
|
|
2435
|
+
let displayName = filePath;
|
|
2436
|
+
if ((0, import_string_width.default)(displayName) + 1 > availableWidth) {
|
|
2437
|
+
while ((0, import_string_width.default)("..." + displayName) + 1 > availableWidth && displayName.length > 0) {
|
|
2438
|
+
displayName = displayName.slice(1);
|
|
2439
|
+
}
|
|
2440
|
+
displayName = "..." + displayName;
|
|
2441
|
+
}
|
|
2442
|
+
const paddingLength = Math.max(0, availableWidth - (0, import_string_width.default)(displayName) - 1);
|
|
2443
|
+
const padding = BOX_HORIZONTAL.repeat(paddingLength);
|
|
2444
|
+
return ANSI.CYAN + prefix + ANSI.BOLD + displayName + ANSI.RESET + " " + ANSI.CYAN + padding + suffix + ANSI.RESET;
|
|
2445
|
+
}
|
|
2446
|
+
function renderBottomBar(startLine, endLine, totalLines, _state, width) {
|
|
2447
|
+
const prefix = BOX_HORIZONTAL.repeat(3) + " ";
|
|
2448
|
+
const positionIndicator = `${startLine}-${endLine}/${totalLines}`;
|
|
2449
|
+
const content = positionIndicator;
|
|
2450
|
+
const suffix = " " + BOX_HORIZONTAL.repeat(3);
|
|
2451
|
+
const availableWidth = width - (0, import_string_width.default)(prefix) - (0, import_string_width.default)(suffix);
|
|
2452
|
+
const paddingLength = Math.max(0, availableWidth - (0, import_string_width.default)(content));
|
|
2453
|
+
const padding = BOX_HORIZONTAL.repeat(paddingLength);
|
|
2454
|
+
return ANSI.CYAN + prefix + ANSI.RESET + ANSI.DIM + content + ANSI.RESET + ANSI.CYAN + padding + suffix + ANSI.RESET;
|
|
2455
|
+
}
|
|
2456
|
+
function clearDiffWindow() {
|
|
2457
|
+
process.stdout.write("\n");
|
|
2458
|
+
}
|
|
2459
|
+
function colorize(line, terminalWidth) {
|
|
2460
|
+
const maxWidth = Math.max(10, terminalWidth - 3);
|
|
2461
|
+
const text = truncateLine(line.text, maxWidth);
|
|
2462
|
+
switch (line.type) {
|
|
2463
|
+
case "add":
|
|
2464
|
+
return ANSI.GREEN + text + ANSI.RESET;
|
|
2465
|
+
case "remove":
|
|
2466
|
+
return ANSI.RED + text + ANSI.RESET;
|
|
2467
|
+
case "header":
|
|
2468
|
+
return ANSI.CYAN + text + ANSI.RESET;
|
|
2469
|
+
case "context":
|
|
2470
|
+
return ANSI.GRAY + text + ANSI.RESET;
|
|
2471
|
+
default:
|
|
2472
|
+
return text;
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
function truncateLine(text, maxWidth) {
|
|
2476
|
+
if ((0, import_string_width.default)(text) <= maxWidth) {
|
|
2477
|
+
return text;
|
|
2478
|
+
}
|
|
2479
|
+
let truncated = text;
|
|
2480
|
+
while ((0, import_string_width.default)(truncated + "...") > maxWidth && truncated.length > 0) {
|
|
2481
|
+
truncated = truncated.slice(0, -1);
|
|
2482
|
+
}
|
|
2483
|
+
return truncated + "...";
|
|
2484
|
+
}
|
|
2485
|
+
function getTerminalSize() {
|
|
2486
|
+
let columns = 80;
|
|
2487
|
+
let rows = 24;
|
|
2488
|
+
try {
|
|
2489
|
+
const sttyOutput = (0, import_child_process.execSync)("stty size </dev/tty", {
|
|
2490
|
+
encoding: "utf-8",
|
|
2491
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2492
|
+
timeout: 200,
|
|
2493
|
+
shell: "/bin/bash"
|
|
2494
|
+
}).trim();
|
|
2495
|
+
const parts = sttyOutput.split(/\s+/);
|
|
2496
|
+
if (parts.length === 2) {
|
|
2497
|
+
const sttyRows = parseInt(parts[0], 10);
|
|
2498
|
+
const sttyCols = parseInt(parts[1], 10);
|
|
2499
|
+
if (!isNaN(sttyRows) && sttyRows > 0) {
|
|
2500
|
+
rows = sttyRows;
|
|
2501
|
+
}
|
|
2502
|
+
if (!isNaN(sttyCols) && sttyCols > 0) {
|
|
2503
|
+
columns = sttyCols;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
} catch {
|
|
2507
|
+
try {
|
|
2508
|
+
const tputCols = (0, import_child_process.execSync)("tput cols </dev/tty", {
|
|
2509
|
+
encoding: "utf-8",
|
|
2510
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2511
|
+
timeout: 100,
|
|
2512
|
+
shell: "/bin/bash"
|
|
2513
|
+
}).trim();
|
|
2514
|
+
const cols = parseInt(tputCols, 10);
|
|
2515
|
+
if (!isNaN(cols) && cols > 0) {
|
|
2516
|
+
columns = cols;
|
|
2517
|
+
}
|
|
2518
|
+
} catch {
|
|
2519
|
+
}
|
|
2520
|
+
try {
|
|
2521
|
+
const tputRows = (0, import_child_process.execSync)("tput lines </dev/tty", {
|
|
2522
|
+
encoding: "utf-8",
|
|
2523
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2524
|
+
timeout: 100,
|
|
2525
|
+
shell: "/bin/bash"
|
|
2526
|
+
}).trim();
|
|
2527
|
+
const lns = parseInt(tputRows, 10);
|
|
2528
|
+
if (!isNaN(lns) && lns > 0) {
|
|
2529
|
+
rows = lns;
|
|
2530
|
+
}
|
|
2531
|
+
} catch {
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
return { rows, columns };
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
// src/templates/diff-viewer/diff-prompt.ts
|
|
2538
|
+
var import_extensibility_tool_utils8 = require("@stripe/extensibility-tool-utils");
|
|
2539
|
+
var logger2 = (0, import_extensibility_tool_utils8._createLogger)({ name: "diff-prompt" });
|
|
2540
|
+
function isInteractiveTerminal() {
|
|
2541
|
+
if (process.env["CI"] === "true" || process.env["CI"] === "1") {
|
|
2542
|
+
return false;
|
|
2543
|
+
}
|
|
2544
|
+
const term = process.env["TERM"];
|
|
2545
|
+
if (!term || term === "dumb") {
|
|
2546
|
+
return false;
|
|
2547
|
+
}
|
|
2548
|
+
if (typeof process.stdin.setRawMode !== "function") {
|
|
2549
|
+
return false;
|
|
2550
|
+
}
|
|
2551
|
+
const termSize = getTerminalSize();
|
|
2552
|
+
if (termSize.rows === 24 && termSize.columns === 80) {
|
|
2553
|
+
}
|
|
2554
|
+
return true;
|
|
2555
|
+
}
|
|
2556
|
+
async function _advancedDiffPrompt(filePath, cwd, newContent) {
|
|
2557
|
+
const resolvedPath = import_node_path2.default.resolve(cwd, filePath);
|
|
2558
|
+
if (!isInteractiveTerminal()) {
|
|
2559
|
+
return await _promptOverwrite(filePath);
|
|
2560
|
+
}
|
|
2561
|
+
try {
|
|
2562
|
+
const existingContent = await readExistingFile(resolvedPath);
|
|
2563
|
+
const diffResult = await generateDiff(filePath, existingContent, newContent);
|
|
2564
|
+
const termSize = getTerminalSize();
|
|
2565
|
+
if (termSize.rows < 20) {
|
|
2566
|
+
return await _promptOverwrite(filePath);
|
|
2567
|
+
}
|
|
2568
|
+
if (diffResult.lines.length > 5e3) {
|
|
2569
|
+
logger2.info("Diff too large to display comfortably");
|
|
2570
|
+
return await _promptOverwrite(filePath);
|
|
2571
|
+
}
|
|
2572
|
+
const wasRaw = process.stdin.isRaw ?? false;
|
|
2573
|
+
try {
|
|
2574
|
+
process.stdin.setRawMode(true);
|
|
2575
|
+
} catch {
|
|
2576
|
+
return await _promptOverwrite(filePath);
|
|
2577
|
+
}
|
|
2578
|
+
process.stdin.resume();
|
|
2579
|
+
const maxTotalHeight = Math.floor(termSize.rows * 0.75);
|
|
2580
|
+
const maxContentHeight = maxTotalHeight - 3;
|
|
2581
|
+
const windowHeight = Math.min(diffResult.lines.length, maxContentHeight);
|
|
2582
|
+
const state = {
|
|
2583
|
+
scrollOffset: 0,
|
|
2584
|
+
maxScroll: Math.max(0, diffResult.lines.length - windowHeight),
|
|
2585
|
+
diffLines: diffResult.lines,
|
|
2586
|
+
terminalHeight: termSize.rows,
|
|
2587
|
+
terminalWidth: termSize.columns,
|
|
2588
|
+
wasRaw,
|
|
2589
|
+
renderedHeight: 0,
|
|
2590
|
+
fullFileMode: false
|
|
2591
|
+
};
|
|
2592
|
+
render(state, filePath, false);
|
|
2593
|
+
return new Promise((resolve) => {
|
|
2594
|
+
const onExit = () => {
|
|
2595
|
+
cleanup(state.wasRaw, onData, onExit);
|
|
2596
|
+
};
|
|
2597
|
+
const onData = (buffer) => {
|
|
2598
|
+
handleKeypress(buffer, state, filePath, newContent, existingContent).then((result) => {
|
|
2599
|
+
if (result !== "continue") {
|
|
2600
|
+
cleanup(state.wasRaw, onData, onExit);
|
|
2601
|
+
resolve(result);
|
|
2602
|
+
}
|
|
2603
|
+
}).catch((err) => {
|
|
2604
|
+
cleanup(state.wasRaw, onData, onExit);
|
|
2605
|
+
logger2.error({ err }, "Error handling keypress");
|
|
2606
|
+
resolve("abort");
|
|
2607
|
+
});
|
|
2608
|
+
};
|
|
2609
|
+
process.stdin.on("data", onData);
|
|
2610
|
+
process.on("exit", onExit);
|
|
2611
|
+
});
|
|
2612
|
+
} catch (err) {
|
|
2613
|
+
logger2.warn({ err }, "Error displaying diff, using basic prompt");
|
|
2614
|
+
return await _promptOverwrite(filePath);
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
async function handleKeypress(buffer, state, filePath, newContent, existingContent) {
|
|
2618
|
+
const code = buffer[0];
|
|
2619
|
+
const key = buffer.toString();
|
|
2620
|
+
if (code === 3) {
|
|
2621
|
+
process.stdout.write("\nCancelled.\n");
|
|
2622
|
+
return "abort";
|
|
2623
|
+
}
|
|
2624
|
+
switch (key.toLowerCase()) {
|
|
2625
|
+
case "y":
|
|
2626
|
+
return "overwrite";
|
|
2627
|
+
case "n":
|
|
2628
|
+
case "\r":
|
|
2629
|
+
case "\n":
|
|
2630
|
+
return "skip";
|
|
2631
|
+
case "a":
|
|
2632
|
+
return "overwrite-all";
|
|
2633
|
+
case "q":
|
|
2634
|
+
return "abort";
|
|
2635
|
+
case "d":
|
|
2636
|
+
await toggleDiffContext(state, filePath, newContent, existingContent);
|
|
2637
|
+
return "continue";
|
|
2638
|
+
}
|
|
2639
|
+
if (buffer.length >= 3 && buffer[0] === 27 && buffer[1] === 91) {
|
|
2640
|
+
const maxTotalHeight = Math.floor(state.terminalHeight * 0.75);
|
|
2641
|
+
const maxContentHeight = maxTotalHeight - 3;
|
|
2642
|
+
const windowHeight = Math.min(state.diffLines.length, maxContentHeight);
|
|
2643
|
+
let scrollDelta = 0;
|
|
2644
|
+
if (buffer[2] === 65) {
|
|
2645
|
+
scrollDelta = -1;
|
|
2646
|
+
} else if (buffer[2] === 66) {
|
|
2647
|
+
scrollDelta = 1;
|
|
2648
|
+
} else if (buffer[2] === 53) {
|
|
2649
|
+
scrollDelta = -windowHeight;
|
|
2650
|
+
} else if (buffer[2] === 54) {
|
|
2651
|
+
scrollDelta = windowHeight;
|
|
2652
|
+
}
|
|
2653
|
+
const newScrollOffset = Math.max(
|
|
2654
|
+
0,
|
|
2655
|
+
Math.min(state.maxScroll, state.scrollOffset + scrollDelta)
|
|
2656
|
+
);
|
|
2657
|
+
if (newScrollOffset !== state.scrollOffset) {
|
|
2658
|
+
state.scrollOffset = newScrollOffset;
|
|
2659
|
+
render(state, filePath, true);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
return "continue";
|
|
2663
|
+
}
|
|
2664
|
+
function cleanup(wasRaw, onData, onExit) {
|
|
2665
|
+
if (process.stdin.setRawMode !== void 0) {
|
|
2666
|
+
process.stdin.setRawMode(wasRaw);
|
|
2667
|
+
}
|
|
2668
|
+
process.stdin.pause();
|
|
2669
|
+
if (onData) {
|
|
2670
|
+
process.stdin.off("data", onData);
|
|
2671
|
+
}
|
|
2672
|
+
if (onExit) {
|
|
2673
|
+
process.off("exit", onExit);
|
|
2674
|
+
}
|
|
2675
|
+
clearDiffWindow();
|
|
2676
|
+
}
|
|
2677
|
+
async function toggleDiffContext(state, filePath, newContent, existingContent) {
|
|
2678
|
+
state.fullFileMode = !state.fullFileMode;
|
|
2679
|
+
const contextLines = state.fullFileMode ? 1e4 : 3;
|
|
2680
|
+
const visibleStartIndex = state.scrollOffset;
|
|
2681
|
+
let referenceLineFinder = null;
|
|
2682
|
+
let skippedLines = 0;
|
|
2683
|
+
for (let i = visibleStartIndex; i < state.diffLines.length; i++) {
|
|
2684
|
+
const line = state.diffLines[i];
|
|
2685
|
+
if (line !== void 0) {
|
|
2686
|
+
if (line.type === "header") {
|
|
2687
|
+
skippedLines++;
|
|
2688
|
+
continue;
|
|
2689
|
+
}
|
|
2690
|
+
if (line.newLine !== void 0) {
|
|
2691
|
+
const targetNewLine = line.newLine;
|
|
2692
|
+
referenceLineFinder = (dl) => dl.newLine !== void 0 && dl.newLine >= targetNewLine - skippedLines;
|
|
2693
|
+
break;
|
|
2694
|
+
} else if (line.oldLine !== void 0) {
|
|
2695
|
+
const targetOldLine = line.oldLine;
|
|
2696
|
+
referenceLineFinder = (dl) => dl.oldLine !== void 0 && dl.oldLine >= targetOldLine - skippedLines;
|
|
2697
|
+
break;
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
const diffResult = await generateDiff(
|
|
2702
|
+
filePath,
|
|
2703
|
+
existingContent,
|
|
2704
|
+
newContent,
|
|
2705
|
+
contextLines
|
|
2706
|
+
);
|
|
2707
|
+
state.diffLines = diffResult.lines;
|
|
2708
|
+
let newScrollOffset = 0;
|
|
2709
|
+
if (referenceLineFinder !== null) {
|
|
2710
|
+
const referenceIndex = diffResult.lines.findIndex(referenceLineFinder);
|
|
2711
|
+
if (referenceIndex !== -1) {
|
|
2712
|
+
newScrollOffset = Math.max(0, referenceIndex);
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
const maxTotalHeight = Math.floor(state.terminalHeight * 0.75);
|
|
2716
|
+
const maxContentHeight = maxTotalHeight - 3;
|
|
2717
|
+
const windowHeight = Math.min(diffResult.lines.length, maxContentHeight);
|
|
2718
|
+
state.maxScroll = Math.max(0, diffResult.lines.length - windowHeight);
|
|
2719
|
+
state.scrollOffset = Math.min(newScrollOffset, state.maxScroll);
|
|
2720
|
+
render(state, filePath, true);
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
// src/templates/extensions/types.ts
|
|
2724
|
+
var _ExtensionTemplateManager = class extends import_extensibility_tool_utils4._TemplateManager {
|
|
2725
|
+
/**
|
|
2726
|
+
* Get summary info for all registered extension templates
|
|
2727
|
+
*/
|
|
2728
|
+
getTemplateInfo() {
|
|
2729
|
+
return this.getTemplateEntries().map(([key, t]) => ({
|
|
2730
|
+
key,
|
|
2731
|
+
description: t.description,
|
|
2732
|
+
deprecated: t.deprecated ?? false,
|
|
2733
|
+
hidden: t.hidden ?? false,
|
|
2734
|
+
methods: t.methods
|
|
2735
|
+
}));
|
|
2736
|
+
}
|
|
2737
|
+
};
|
|
2738
|
+
|
|
2739
|
+
// src/templates/extensions/base.ts
|
|
2740
|
+
var import_extensibility_tool_utils9 = require("@stripe/extensibility-tool-utils");
|
|
2741
|
+
var SDK_PACKAGE_NAME = "@stripe/extensibility-sdk";
|
|
2742
|
+
var LANGUAGE_SERVER_PACKAGE_NAME = "@stripe/extensibility-language-server";
|
|
2743
|
+
var LANGUAGE_SERVER_PACKAGE_VERSION = "^0.2.0";
|
|
2744
|
+
function _createExtensionEslintConfigFile(params, context) {
|
|
2745
|
+
const { id, extensionInterfaceId } = params;
|
|
2746
|
+
const { fs: fs2 } = context;
|
|
2747
|
+
return {
|
|
2748
|
+
path: `extensions/${id}/eslint.config.mts`,
|
|
2749
|
+
content: fs2.mustache(
|
|
2750
|
+
{ extensionInterfaceId },
|
|
2751
|
+
"common",
|
|
2752
|
+
"eslint.config.mts.mustache"
|
|
2753
|
+
),
|
|
2754
|
+
precious: true
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2757
|
+
function _createBaseOutput(params, context) {
|
|
2758
|
+
const { id } = params;
|
|
2759
|
+
const { fs: fs2 } = context;
|
|
2760
|
+
return {
|
|
2761
|
+
files: [
|
|
2762
|
+
{
|
|
2763
|
+
path: `extensions/${id}/package.json`,
|
|
2764
|
+
content: fs2.mustache(params, "common", "package.json.mustache")
|
|
2765
|
+
},
|
|
2766
|
+
{
|
|
2767
|
+
..._createExtensionEslintConfigFile(params, context)
|
|
2768
|
+
},
|
|
2769
|
+
{
|
|
2770
|
+
path: `extensions/${id}/tsconfig.build.json`,
|
|
2771
|
+
content: fs2.mustache(params, "common", "tsconfig.build.json.mustache")
|
|
2772
|
+
},
|
|
2773
|
+
{
|
|
2774
|
+
path: `extensions/${id}/tsconfig.json`,
|
|
2775
|
+
content: fs2.mustache(params, "common", "tsconfig.json.mustache")
|
|
2776
|
+
},
|
|
2777
|
+
{
|
|
2778
|
+
path: `extensions/${id}/.prettierignore`,
|
|
2779
|
+
content: fs2.textFile("common", ".prettierignore")
|
|
2780
|
+
}
|
|
2781
|
+
],
|
|
2782
|
+
methods: {},
|
|
2783
|
+
dependencies: {
|
|
2784
|
+
// Exact pin (no caret) — the SDK is tightly coupled to dev-tools and
|
|
2785
|
+
// must match the version that generated the extension scaffolding.
|
|
2786
|
+
runtime: [_npmDep(SDK_PACKAGE_NAME, (0, import_extensibility_tool_utils9._workspaceVersion)(SDK_PACKAGE_NAME))],
|
|
2787
|
+
dev: [_devNpmDep(LANGUAGE_SERVER_PACKAGE_NAME, LANGUAGE_SERVER_PACKAGE_VERSION)]
|
|
2788
|
+
},
|
|
2789
|
+
postGenerationHooks: [
|
|
2790
|
+
{
|
|
2791
|
+
script: "build",
|
|
2792
|
+
description: "Build extension"
|
|
2793
|
+
},
|
|
2794
|
+
{
|
|
2795
|
+
exec: "prettier --write .",
|
|
2796
|
+
description: "Format generated files"
|
|
2797
|
+
}
|
|
2798
|
+
]
|
|
2799
|
+
};
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
// src/templates/extensions/core.workflows.custom_action.ts
|
|
2803
|
+
var EXTENSION_INTERFACE_ID = "core.workflows.custom_action";
|
|
2804
|
+
var customActionTemplate = {
|
|
2805
|
+
methods: {
|
|
2806
|
+
execute: { implementation_types: ["script", "remote-function"] },
|
|
2807
|
+
get_form_state: { implementation_types: ["script", "remote-function"] }
|
|
2808
|
+
},
|
|
2809
|
+
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.",
|
|
2810
|
+
generate: (params, context) => {
|
|
2811
|
+
const { id } = params;
|
|
2812
|
+
const { fs: fs2 } = context;
|
|
2813
|
+
const base = _createBaseOutput(
|
|
2814
|
+
{
|
|
2815
|
+
...params,
|
|
2816
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID,
|
|
2817
|
+
executionProfile: "egress"
|
|
2818
|
+
},
|
|
2819
|
+
context
|
|
2820
|
+
);
|
|
2821
|
+
return {
|
|
2822
|
+
...base,
|
|
2823
|
+
files: [
|
|
2824
|
+
{
|
|
2825
|
+
path: `extensions/${id}/src/index.ts`,
|
|
2826
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID, "index.ts"),
|
|
2827
|
+
precious: true
|
|
2828
|
+
},
|
|
2829
|
+
{
|
|
2830
|
+
path: `extensions/${id}/src/custom_input.schema.json`,
|
|
2831
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID, "custom_input.schema.json"),
|
|
2832
|
+
precious: true
|
|
2833
|
+
},
|
|
2834
|
+
{
|
|
2835
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
2836
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID, "index.test.ts"),
|
|
2837
|
+
precious: true
|
|
2838
|
+
},
|
|
2839
|
+
...base.files
|
|
2840
|
+
],
|
|
2841
|
+
methods: {
|
|
2842
|
+
execute: {
|
|
2843
|
+
implementation_type: "script",
|
|
2844
|
+
custom_input: {
|
|
2845
|
+
input_schema: {
|
|
2846
|
+
type: "json_schema",
|
|
2847
|
+
content: `extensions/${id}/src/custom_input.schema.json`
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
},
|
|
2851
|
+
get_form_state: {
|
|
2852
|
+
implementation_type: "script"
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
};
|
|
2858
|
+
var core_workflows_custom_action_default = {
|
|
2859
|
+
[EXTENSION_INTERFACE_ID]: customActionTemplate
|
|
2860
|
+
};
|
|
2861
|
+
|
|
2862
|
+
// src/templates/extensions/extend.objects.custom_objects.ts
|
|
2863
|
+
var EXTENSION_INTERFACE_ID2 = "extend.objects.custom_objects";
|
|
2864
|
+
var customObjectsTemplate = {
|
|
2865
|
+
methods: {
|
|
2866
|
+
execute_method: { implementation_types: ["script"] }
|
|
2867
|
+
},
|
|
2868
|
+
description: "Methods extension for custom object actions. Generated at build time \u2014 the transformed custom object class becomes the entry point.",
|
|
2869
|
+
hidden: true,
|
|
2870
|
+
generate: (params, context) => {
|
|
2871
|
+
const base = _createBaseOutput(
|
|
2872
|
+
{
|
|
2873
|
+
...params,
|
|
2874
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID2,
|
|
2875
|
+
executionProfile: "egress"
|
|
2876
|
+
},
|
|
2877
|
+
context
|
|
2878
|
+
);
|
|
2879
|
+
return {
|
|
2880
|
+
...base,
|
|
2881
|
+
methods: {
|
|
2882
|
+
execute_method: {
|
|
2883
|
+
implementation_type: "script"
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
};
|
|
2889
|
+
var extend_objects_custom_objects_default = {
|
|
2890
|
+
[EXTENSION_INTERFACE_ID2]: customObjectsTemplate
|
|
2891
|
+
};
|
|
2892
|
+
|
|
2893
|
+
// src/templates/extensions/extend.workflows.custom_action.ts
|
|
2894
|
+
var EXTENSION_INTERFACE_ID3 = "extend.workflows.custom_action";
|
|
2895
|
+
var extendCustomActionTemplate = {
|
|
2896
|
+
methods: {
|
|
2897
|
+
execute: { implementation_types: ["script", "remote-function"] },
|
|
2898
|
+
get_form_state: { implementation_types: ["script", "remote-function"] }
|
|
2899
|
+
},
|
|
2900
|
+
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.",
|
|
2901
|
+
generate: (params, context) => {
|
|
2902
|
+
const { id } = params;
|
|
2903
|
+
const { fs: fs2 } = context;
|
|
2904
|
+
const base = _createBaseOutput(
|
|
2905
|
+
{
|
|
2906
|
+
...params,
|
|
2907
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID3,
|
|
2908
|
+
executionProfile: "egress"
|
|
2909
|
+
},
|
|
2910
|
+
context
|
|
2911
|
+
);
|
|
2912
|
+
return {
|
|
2913
|
+
...base,
|
|
2914
|
+
files: [
|
|
2915
|
+
{
|
|
2916
|
+
path: `extensions/${id}/src/index.ts`,
|
|
2917
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID3, "index.ts"),
|
|
2918
|
+
precious: true
|
|
2919
|
+
},
|
|
2920
|
+
{
|
|
2921
|
+
path: `extensions/${id}/src/custom_input.schema.json`,
|
|
2922
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID3, "custom_input.schema.json"),
|
|
2923
|
+
precious: true
|
|
2924
|
+
},
|
|
2925
|
+
{
|
|
2926
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
2927
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID3, "index.test.ts"),
|
|
2928
|
+
precious: true
|
|
2929
|
+
},
|
|
2930
|
+
...base.files
|
|
2931
|
+
],
|
|
2932
|
+
methods: {
|
|
2933
|
+
execute: {
|
|
2934
|
+
implementation_type: "script",
|
|
2935
|
+
custom_input: {
|
|
2936
|
+
input_schema: {
|
|
2937
|
+
type: "json_schema",
|
|
2938
|
+
content: `extensions/${id}/src/custom_input.schema.json`
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
},
|
|
2942
|
+
get_form_state: {
|
|
2943
|
+
implementation_type: "script"
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
};
|
|
2947
|
+
}
|
|
2948
|
+
};
|
|
2949
|
+
var extend_workflows_custom_action_default = {
|
|
2950
|
+
[EXTENSION_INTERFACE_ID3]: extendCustomActionTemplate
|
|
2951
|
+
};
|
|
2952
|
+
|
|
2953
|
+
// src/templates/extensions/billing.customer_balance_application.ts
|
|
2954
|
+
var EXTENSION_INTERFACE_ID4 = "billing.customer_balance_application";
|
|
2955
|
+
var customerBalanceApplicationTemplate = {
|
|
2956
|
+
methods: {
|
|
2957
|
+
compute_applied_customer_balance: { implementation_types: ["script"] }
|
|
2958
|
+
},
|
|
2959
|
+
description: "Implement custom logic to control when and how much customer balance is applied to subscription invoices using scripts.",
|
|
2960
|
+
generate: (params, context) => {
|
|
2961
|
+
const { id } = params;
|
|
2962
|
+
const { fs: fs2 } = context;
|
|
2963
|
+
const base = _createBaseOutput(
|
|
2964
|
+
{
|
|
2965
|
+
...params,
|
|
2966
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID4,
|
|
2967
|
+
executionProfile: "restricted"
|
|
2968
|
+
},
|
|
2969
|
+
context
|
|
2970
|
+
);
|
|
2971
|
+
return {
|
|
2972
|
+
...base,
|
|
2973
|
+
files: [
|
|
2974
|
+
{
|
|
2975
|
+
path: `extensions/${id}/src/index.ts`,
|
|
2976
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID4, "index.ts"),
|
|
2977
|
+
precious: true
|
|
2978
|
+
},
|
|
2979
|
+
{
|
|
2980
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
2981
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID4, "index.test.ts"),
|
|
2982
|
+
precious: true
|
|
2983
|
+
},
|
|
2984
|
+
...base.files
|
|
2985
|
+
],
|
|
2986
|
+
methods: {
|
|
2987
|
+
compute_applied_customer_balance: {
|
|
2988
|
+
implementation_type: "script"
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
};
|
|
2994
|
+
var billing_customer_balance_application_default = {
|
|
2995
|
+
[EXTENSION_INTERFACE_ID4]: customerBalanceApplicationTemplate
|
|
2996
|
+
};
|
|
2997
|
+
|
|
2998
|
+
// src/templates/extensions/billing.bill.discount_calculation.ts
|
|
2999
|
+
var EXTENSION_INTERFACE_ID5 = "billing.bill.discount_calculation";
|
|
3000
|
+
var discountCalculationTemplate = {
|
|
3001
|
+
hidden: true,
|
|
3002
|
+
methods: {
|
|
3003
|
+
compute_discounts: { implementation_types: ["script"] }
|
|
3004
|
+
},
|
|
3005
|
+
description: "Create coupons with custom scripting logic for dynamic discounts based on subscription attributes, customer metadata, and complex business rules.",
|
|
3006
|
+
generate: (params, context) => {
|
|
3007
|
+
const { id } = params;
|
|
3008
|
+
const { fs: fs2 } = context;
|
|
3009
|
+
const base = _createBaseOutput(
|
|
3010
|
+
{
|
|
3011
|
+
...params,
|
|
3012
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID5,
|
|
3013
|
+
executionProfile: "restricted"
|
|
3014
|
+
},
|
|
3015
|
+
context
|
|
3016
|
+
);
|
|
3017
|
+
return {
|
|
3018
|
+
...base,
|
|
3019
|
+
files: [
|
|
3020
|
+
{
|
|
3021
|
+
path: `extensions/${id}/src/index.ts`,
|
|
3022
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID5, "index.ts"),
|
|
3023
|
+
precious: true
|
|
3024
|
+
},
|
|
3025
|
+
{
|
|
3026
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
3027
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID5, "index.test.ts"),
|
|
3028
|
+
precious: true
|
|
3029
|
+
},
|
|
3030
|
+
...base.files
|
|
3031
|
+
],
|
|
3032
|
+
methods: {
|
|
3033
|
+
compute_discounts: {
|
|
3034
|
+
implementation_type: "script"
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
};
|
|
3040
|
+
var billing_bill_discount_calculation_default = {
|
|
3041
|
+
[EXTENSION_INTERFACE_ID5]: discountCalculationTemplate
|
|
3042
|
+
};
|
|
3043
|
+
|
|
3044
|
+
// src/templates/extensions/billing.invoice_collection_setting.ts
|
|
3045
|
+
var EXTENSION_INTERFACE_ID6 = "billing.invoice_collection_setting";
|
|
3046
|
+
var invoiceCollectionSettingTemplate = {
|
|
3047
|
+
hidden: true,
|
|
3048
|
+
methods: {
|
|
3049
|
+
collection_override: { implementation_types: ["script"] }
|
|
3050
|
+
},
|
|
3051
|
+
description: "Use Stripe Scripts to create custom invoice collection logic that controls how your integration handles invoices generated from subscriptions.",
|
|
3052
|
+
generate: (params, context) => {
|
|
3053
|
+
const { id } = params;
|
|
3054
|
+
const { fs: fs2 } = context;
|
|
3055
|
+
const base = _createBaseOutput(
|
|
3056
|
+
{
|
|
3057
|
+
...params,
|
|
3058
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID6,
|
|
3059
|
+
executionProfile: "restricted"
|
|
3060
|
+
},
|
|
3061
|
+
context
|
|
3062
|
+
);
|
|
3063
|
+
return {
|
|
3064
|
+
...base,
|
|
3065
|
+
files: [
|
|
3066
|
+
{
|
|
3067
|
+
path: `extensions/${id}/src/index.ts`,
|
|
3068
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID6, "index.ts"),
|
|
3069
|
+
precious: true
|
|
3070
|
+
},
|
|
3071
|
+
{
|
|
3072
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
3073
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID6, "index.test.ts"),
|
|
3074
|
+
precious: true
|
|
3075
|
+
},
|
|
3076
|
+
...base.files
|
|
3077
|
+
],
|
|
3078
|
+
methods: {
|
|
3079
|
+
collection_override: {
|
|
3080
|
+
implementation_type: "script"
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
3085
|
+
};
|
|
3086
|
+
var billing_invoice_collection_setting_default = {
|
|
3087
|
+
[EXTENSION_INTERFACE_ID6]: invoiceCollectionSettingTemplate
|
|
3088
|
+
};
|
|
3089
|
+
|
|
3090
|
+
// src/templates/extensions/billing.prorations.ts
|
|
3091
|
+
var EXTENSION_INTERFACE_ID7 = "billing.prorations";
|
|
3092
|
+
var prorationsTemplate = {
|
|
3093
|
+
methods: {
|
|
3094
|
+
prorate_items: { implementation_types: ["script"] }
|
|
3095
|
+
},
|
|
3096
|
+
description: "Create custom proration logic for subscriptions using scripts to handle upgrades, downgrades, and mid-cycle changes.",
|
|
3097
|
+
generate: (params, context) => {
|
|
3098
|
+
const { id } = params;
|
|
3099
|
+
const { fs: fs2 } = context;
|
|
3100
|
+
const base = _createBaseOutput(
|
|
3101
|
+
{
|
|
3102
|
+
...params,
|
|
3103
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID7,
|
|
3104
|
+
executionProfile: "restricted"
|
|
3105
|
+
},
|
|
3106
|
+
context
|
|
3107
|
+
);
|
|
3108
|
+
return {
|
|
3109
|
+
...base,
|
|
3110
|
+
files: [
|
|
3111
|
+
{
|
|
3112
|
+
path: `extensions/${id}/src/index.ts`,
|
|
3113
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID7, "index.ts"),
|
|
3114
|
+
precious: true
|
|
3115
|
+
},
|
|
3116
|
+
{
|
|
3117
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
3118
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID7, "index.test.ts"),
|
|
3119
|
+
precious: true
|
|
3120
|
+
},
|
|
3121
|
+
...base.files
|
|
3122
|
+
],
|
|
3123
|
+
methods: {
|
|
3124
|
+
prorate_items: {
|
|
3125
|
+
implementation_type: "script"
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
};
|
|
3129
|
+
}
|
|
3130
|
+
};
|
|
3131
|
+
var billing_prorations_default = {
|
|
3132
|
+
[EXTENSION_INTERFACE_ID7]: prorationsTemplate
|
|
3133
|
+
};
|
|
3134
|
+
|
|
3135
|
+
// src/templates/extensions/billing.recurring_billing_item_handling.ts
|
|
3136
|
+
var EXTENSION_INTERFACE_ID8 = "billing.recurring_billing_item_handling";
|
|
3137
|
+
var template = {
|
|
3138
|
+
methods: {
|
|
3139
|
+
before_item_creation: { implementation_types: ["script"] },
|
|
3140
|
+
filter_items: { implementation_types: ["script"] },
|
|
3141
|
+
group_items: { implementation_types: ["script"] }
|
|
3142
|
+
},
|
|
3143
|
+
description: "Customize how recurring billing items are filtered, grouped, and created during subscription billing runs.",
|
|
3144
|
+
generate: (params, context) => {
|
|
3145
|
+
const { id } = params;
|
|
3146
|
+
const { fs: fs2 } = context;
|
|
3147
|
+
const base = _createBaseOutput(
|
|
3148
|
+
{
|
|
3149
|
+
...params,
|
|
3150
|
+
extensionInterfaceId: EXTENSION_INTERFACE_ID8,
|
|
3151
|
+
executionProfile: "restricted"
|
|
3152
|
+
},
|
|
3153
|
+
context
|
|
3154
|
+
);
|
|
3155
|
+
return {
|
|
3156
|
+
...base,
|
|
3157
|
+
files: [
|
|
3158
|
+
{
|
|
3159
|
+
path: `extensions/${id}/src/index.ts`,
|
|
3160
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID8, "index.ts"),
|
|
3161
|
+
precious: true
|
|
3162
|
+
},
|
|
3163
|
+
{
|
|
3164
|
+
path: `extensions/${id}/src/index.test.ts`,
|
|
3165
|
+
content: fs2.textFile(EXTENSION_INTERFACE_ID8, "index.test.ts"),
|
|
3166
|
+
precious: true
|
|
3167
|
+
},
|
|
3168
|
+
...base.files
|
|
3169
|
+
],
|
|
3170
|
+
methods: {
|
|
3171
|
+
before_item_creation: {
|
|
3172
|
+
implementation_type: "script"
|
|
3173
|
+
},
|
|
3174
|
+
filter_items: {
|
|
3175
|
+
implementation_type: "script"
|
|
3176
|
+
},
|
|
3177
|
+
group_items: {
|
|
3178
|
+
implementation_type: "script"
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
};
|
|
3184
|
+
var billing_recurring_billing_item_handling_default = {
|
|
3185
|
+
[EXTENSION_INTERFACE_ID8]: template
|
|
3186
|
+
};
|
|
3187
|
+
|
|
3188
|
+
// src/templates/extensions/registry.ts
|
|
3189
|
+
var DEFAULT_TEMPLATES = {
|
|
3190
|
+
...core_workflows_custom_action_default,
|
|
3191
|
+
...extend_objects_custom_objects_default,
|
|
3192
|
+
...extend_workflows_custom_action_default,
|
|
3193
|
+
...billing_customer_balance_application_default,
|
|
3194
|
+
...billing_bill_discount_calculation_default,
|
|
3195
|
+
...billing_invoice_collection_setting_default,
|
|
3196
|
+
...billing_prorations_default,
|
|
3197
|
+
...billing_recurring_billing_item_handling_default
|
|
3198
|
+
};
|
|
3199
|
+
var registry_default = DEFAULT_TEMPLATES;
|
|
3200
|
+
|
|
3201
|
+
// src/templates/extensions/index.ts
|
|
3202
|
+
var _templateManager = new _ExtensionTemplateManager(
|
|
3203
|
+
registry_default,
|
|
3204
|
+
_fs.scope("extensions")
|
|
3205
|
+
);
|
|
3206
|
+
|
|
3207
|
+
// src/templates/root/index.ts
|
|
3208
|
+
var import_extensibility_tool_utils10 = require("@stripe/extensibility-tool-utils");
|
|
3209
|
+
var EXTENSIBILITY_ESLINT_PLUGIN_PACKAGE_NAME = "@stripe/extensibility-eslint-plugin";
|
|
3210
|
+
var EXTENSIBILITY_ESLINT_PLUGIN_VERSION = `^${(0, import_extensibility_tool_utils10._workspaceVersion)(EXTENSIBILITY_ESLINT_PLUGIN_PACKAGE_NAME)}`;
|
|
3211
|
+
var _rootWorkspaceTemplate = {
|
|
3212
|
+
generate: (params, context) => {
|
|
3213
|
+
const { fs: fs2 } = context;
|
|
3214
|
+
return {
|
|
3215
|
+
files: [
|
|
3216
|
+
{
|
|
3217
|
+
path: "stripe-app.yaml",
|
|
3218
|
+
content: fs2.mustache(params, "stripe-app.yaml.mustache"),
|
|
3219
|
+
precious: true
|
|
3220
|
+
},
|
|
3221
|
+
{
|
|
3222
|
+
path: "package.json",
|
|
3223
|
+
content: fs2.mustache(params, "package.json.mustache")
|
|
3224
|
+
},
|
|
3225
|
+
{
|
|
3226
|
+
path: ".husky/pre-commit",
|
|
3227
|
+
content: fs2.textFile(".husky/pre-commit")
|
|
3228
|
+
},
|
|
3229
|
+
{
|
|
3230
|
+
path: "tsconfig.base.json",
|
|
3231
|
+
content: fs2.textFile("tsconfig.base.json")
|
|
3232
|
+
},
|
|
3233
|
+
{
|
|
3234
|
+
path: "tsconfig.json",
|
|
3235
|
+
content: fs2.textFile("tsconfig.json")
|
|
3236
|
+
},
|
|
3237
|
+
{
|
|
3238
|
+
path: "eslint.config.mts",
|
|
3239
|
+
content: fs2.textFile("eslint.config.mts")
|
|
3240
|
+
},
|
|
3241
|
+
{
|
|
3242
|
+
path: "vitest.config.mts",
|
|
3243
|
+
content: fs2.textFile("vitest.config.mts")
|
|
3244
|
+
},
|
|
3245
|
+
{
|
|
3246
|
+
path: ".gitignore",
|
|
3247
|
+
content: fs2.textFile("_gitignore")
|
|
3248
|
+
},
|
|
3249
|
+
{
|
|
3250
|
+
path: "pnpm-workspace.yaml",
|
|
3251
|
+
content: fs2.textFile("pnpm-workspace.yaml")
|
|
3252
|
+
},
|
|
3253
|
+
{
|
|
3254
|
+
path: ".prettierrc",
|
|
3255
|
+
content: fs2.textFile(".prettierrc")
|
|
3256
|
+
},
|
|
3257
|
+
{
|
|
3258
|
+
path: ".prettierignore",
|
|
3259
|
+
content: fs2.textFile(".prettierignore")
|
|
3260
|
+
},
|
|
3261
|
+
{
|
|
3262
|
+
path: "custom-objects/package.json",
|
|
3263
|
+
content: fs2.textFile("custom-objects/package.json"),
|
|
3264
|
+
precious: true
|
|
3265
|
+
},
|
|
3266
|
+
{
|
|
3267
|
+
path: "custom-objects/tsconfig.json",
|
|
3268
|
+
content: fs2.textFile("custom-objects/tsconfig.json"),
|
|
3269
|
+
precious: true
|
|
3270
|
+
},
|
|
3271
|
+
{
|
|
3272
|
+
path: "ui/package.json",
|
|
3273
|
+
content: fs2.textFile("ui/package.json"),
|
|
3274
|
+
precious: true
|
|
3275
|
+
},
|
|
3276
|
+
{
|
|
3277
|
+
path: "tools/test.mts",
|
|
3278
|
+
content: fs2.textFile("tools/test.mts")
|
|
3279
|
+
},
|
|
3280
|
+
{
|
|
3281
|
+
// temporarily add dummy JSON manifest until stripe apps plugin is fixed
|
|
3282
|
+
path: "stripe-app.json",
|
|
3283
|
+
content: "{}\n"
|
|
3284
|
+
}
|
|
3285
|
+
],
|
|
3286
|
+
dependencies: {
|
|
3287
|
+
dev: [
|
|
3288
|
+
_devNpmDep("@eslint/js", "^9.0.0"),
|
|
3289
|
+
_devNpmDep(
|
|
3290
|
+
EXTENSIBILITY_ESLINT_PLUGIN_PACKAGE_NAME,
|
|
3291
|
+
EXTENSIBILITY_ESLINT_PLUGIN_VERSION
|
|
3292
|
+
),
|
|
3293
|
+
_devNpmDep(LANGUAGE_SERVER_PACKAGE_NAME, LANGUAGE_SERVER_PACKAGE_VERSION),
|
|
3294
|
+
_devNpmDep("@types/node", "^20.19.0"),
|
|
3295
|
+
_devNpmDep(
|
|
3296
|
+
"@stripe/extensibility-dev-tools",
|
|
3297
|
+
`^${(0, import_extensibility_tool_utils10._workspaceVersion)("@stripe/extensibility-dev-tools")}`
|
|
3298
|
+
),
|
|
3299
|
+
_devNpmDep("concurrently", "^9.2.1"),
|
|
3300
|
+
_devNpmDep("eslint", "^9.0.0"),
|
|
3301
|
+
_devNpmDep("eslint-config-prettier", "^10.1.8"),
|
|
3302
|
+
_devNpmDep("eslint-plugin-workspaces", "^0.12.1"),
|
|
3303
|
+
_devNpmDep("globals", "^17.4.0"),
|
|
3304
|
+
_devNpmDep("husky", "^9.1.7"),
|
|
3305
|
+
_devNpmDep("jiti", "^2.6.1"),
|
|
3306
|
+
_devNpmDep("lint-staged", "^16.2.7"),
|
|
3307
|
+
_devNpmDep("chokidar-cli", "^3.0.0"),
|
|
3308
|
+
_devNpmDep("prettier", "^3.8.1"),
|
|
3309
|
+
_devNpmDep("ts-node", "^10.9.2"),
|
|
3310
|
+
_devNpmDep("tsx", "^4.21.0"),
|
|
3311
|
+
_devNpmDep("typescript", "^5.9.3"),
|
|
3312
|
+
_devNpmDep("typescript-eslint", "^8.54.0"),
|
|
3313
|
+
_devNpmDep("vitest", "^3.0.0")
|
|
3314
|
+
]
|
|
3315
|
+
},
|
|
3316
|
+
postGenerationHooks: [
|
|
3317
|
+
{
|
|
3318
|
+
exec: "prettier --write .",
|
|
3319
|
+
description: "Format generated files"
|
|
3320
|
+
}
|
|
3321
|
+
]
|
|
3322
|
+
};
|
|
3323
|
+
}
|
|
3324
|
+
};
|
|
3325
|
+
var _rootTemplateManager = new import_extensibility_tool_utils6._SingleTemplateManager(_rootWorkspaceTemplate, _fs.scope("root"));
|
|
3326
|
+
|
|
3327
|
+
// src/workspace/index.ts
|
|
3328
|
+
var _regenWorkspaceOptionsSchema = import_zod.z.object({
|
|
3329
|
+
/** Workspace template ID (e.g., 'core.workflows.custom_action', 'custom-objects'). Absence means root workspace mode. */
|
|
3330
|
+
templateId: import_zod.z.string().optional(),
|
|
3331
|
+
/** Path to project root. Resolved relative to cwd. Defaults to cwd. */
|
|
3332
|
+
root: import_zod.z.string().optional(),
|
|
3333
|
+
/** Path to directory containing package.json (resolved relative to cwd). Used for reading existing metadata. If undefined, package.json is not read. */
|
|
3334
|
+
packageDir: import_zod.z.string().optional(),
|
|
3335
|
+
/** Workspace ID. Validated against package.json if both are present. */
|
|
3336
|
+
workspaceId: import_zod.z.string().optional(),
|
|
3337
|
+
/** Workspace name. Overrides package.json description if provided. */
|
|
3338
|
+
workspaceName: import_zod.z.string().optional(),
|
|
3339
|
+
/** Skip dependency installation */
|
|
3340
|
+
skipDependencies: import_zod.z.boolean().optional(),
|
|
3341
|
+
/** Skip post-generation hooks */
|
|
3342
|
+
skipHooks: import_zod.z.boolean().optional(),
|
|
3343
|
+
/** Force overwrite all files without prompting */
|
|
3344
|
+
force: import_zod.z.boolean().optional(),
|
|
3345
|
+
/** Custom object name. Required when templateId is 'custom-object'. */
|
|
3346
|
+
name: import_zod.z.string().optional(),
|
|
3347
|
+
/** Implementation type. When 'remote-function', only .schema.json files are written and dependencies/hooks are skipped. */
|
|
3348
|
+
implementationType: import_zod.z.enum(["script", "remote-function"]).optional()
|
|
3349
|
+
});
|
|
3350
|
+
async function readPackageMetadata(dir) {
|
|
3351
|
+
const pkgPath = import_node_path3.default.join(dir, "package.json");
|
|
3352
|
+
try {
|
|
3353
|
+
const pkgContent = await (0, import_promises5.readFile)(pkgPath, "utf-8");
|
|
3354
|
+
const pkg = JSON.parse(pkgContent);
|
|
3355
|
+
return {
|
|
3356
|
+
...pkg.name !== void 0 && { id: pkg.name },
|
|
3357
|
+
...pkg.description !== void 0 && { name: pkg.description },
|
|
3358
|
+
...pkg.version !== void 0 && { version: pkg.version }
|
|
3359
|
+
};
|
|
3360
|
+
} catch {
|
|
3361
|
+
return null;
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
async function _resolvePackageMetadata(providedId, providedName, packageDir) {
|
|
3365
|
+
let id = providedId;
|
|
3366
|
+
let name = providedName;
|
|
3367
|
+
let version = "0.0.1";
|
|
3368
|
+
if (packageDir !== void 0) {
|
|
3369
|
+
const pkgMetadata = await readPackageMetadata(packageDir);
|
|
3370
|
+
if (pkgMetadata) {
|
|
3371
|
+
if (id && pkgMetadata.id && id !== pkgMetadata.id) {
|
|
3372
|
+
throw new Error(
|
|
3373
|
+
`Workspace ID mismatch: --workspace-id "${id}" does not match package.json name "${pkgMetadata.id}"`
|
|
3374
|
+
);
|
|
3375
|
+
}
|
|
3376
|
+
id ??= pkgMetadata.id;
|
|
3377
|
+
name ??= pkgMetadata.name;
|
|
3378
|
+
if (pkgMetadata.version) {
|
|
3379
|
+
version = pkgMetadata.version;
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
if (!id) {
|
|
3384
|
+
throw new Error(
|
|
3385
|
+
"ID required: provide --workspace-id or ensure package.json has a name field"
|
|
3386
|
+
);
|
|
3387
|
+
}
|
|
3388
|
+
name ??= id;
|
|
3389
|
+
return { id, name, version };
|
|
3390
|
+
}
|
|
3391
|
+
function runPostGenerationHooks(hooks, workingDir, disabled = false, context = (0, import_extensibility_tool_utils11._createCliContext)()) {
|
|
3392
|
+
if (!hooks || hooks.length === 0 || disabled) {
|
|
3393
|
+
return Promise.resolve();
|
|
3394
|
+
}
|
|
3395
|
+
context.ux.log("\nRunning post-generation hooks...");
|
|
3396
|
+
for (const hook of hooks) {
|
|
3397
|
+
const description = hook.description ? ` (${hook.description})` : "";
|
|
3398
|
+
let command;
|
|
3399
|
+
let hookName;
|
|
3400
|
+
if ("script" in hook) {
|
|
3401
|
+
command = `pnpm run ${hook.script}`;
|
|
3402
|
+
hookName = hook.script;
|
|
3403
|
+
} else {
|
|
3404
|
+
command = `pnpm exec ${hook.exec}`;
|
|
3405
|
+
hookName = hook.exec;
|
|
3406
|
+
}
|
|
3407
|
+
context.ux.log(`
|
|
3408
|
+
Running: ${command}${description}`);
|
|
3409
|
+
try {
|
|
3410
|
+
(0, import_node_child_process2.execSync)(command, {
|
|
3411
|
+
cwd: workingDir,
|
|
3412
|
+
stdio: "inherit"
|
|
3413
|
+
});
|
|
3414
|
+
context.ux.log(`\u2713 Hook completed: ${hookName}`);
|
|
3415
|
+
} catch (error) {
|
|
3416
|
+
context.ux.error(`! Hook failed: ${hookName}`);
|
|
3417
|
+
if (error instanceof Error) {
|
|
3418
|
+
context.ux.error(` ${error.message}`);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
return Promise.resolve();
|
|
3423
|
+
}
|
|
3424
|
+
async function installDependencies(services, cwd, dependencies, context) {
|
|
3425
|
+
if (dependencies.length === 0) {
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3428
|
+
const depManager = services.createDepManager({ cwd, context });
|
|
3429
|
+
const pendingChanges = await depManager.ensureDependencies({
|
|
3430
|
+
required: dependencies
|
|
3431
|
+
});
|
|
3432
|
+
for (const pendingChange of pendingChanges) {
|
|
3433
|
+
if (pendingChange.action === "conflict") {
|
|
3434
|
+
depManager.resolveConflict(pendingChange.dependency);
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
await depManager.commit();
|
|
3438
|
+
}
|
|
3439
|
+
async function backfillMissingExtensionEslintConfigs(services, projectRoot) {
|
|
3440
|
+
const manifestPath = import_node_path3.default.join(projectRoot, "stripe-app.yaml");
|
|
3441
|
+
if (!(0, import_node_fs2.existsSync)(manifestPath)) {
|
|
3442
|
+
return [];
|
|
3443
|
+
}
|
|
3444
|
+
const manifest = await services.loadManifest(manifestPath);
|
|
3445
|
+
const extensions = manifest.getExtensions();
|
|
3446
|
+
return extensions.flatMap((extension) => {
|
|
3447
|
+
const eslintConfigPath = `extensions/${extension.id}/eslint.config.mts`;
|
|
3448
|
+
if ((0, import_node_fs2.existsSync)(import_node_path3.default.join(projectRoot, eslintConfigPath))) {
|
|
3449
|
+
return [];
|
|
3450
|
+
}
|
|
3451
|
+
return _createExtensionEslintConfigFile(
|
|
3452
|
+
{
|
|
3453
|
+
id: extension.id,
|
|
3454
|
+
extensionInterfaceId: extension.interface_id
|
|
3455
|
+
},
|
|
3456
|
+
{ fs: _fs.scope("extensions") }
|
|
3457
|
+
);
|
|
3458
|
+
});
|
|
3459
|
+
}
|
|
3460
|
+
function resolveMode(options, projectRoot, packageDir) {
|
|
3461
|
+
if (!options.templateId) {
|
|
3462
|
+
return {
|
|
3463
|
+
label: "root",
|
|
3464
|
+
workspaceDir: () => packageDir ?? projectRoot,
|
|
3465
|
+
generate: (svc, meta) => svc.generateRoot({
|
|
3466
|
+
appId: meta.id,
|
|
3467
|
+
appName: meta.name,
|
|
3468
|
+
version: meta.version
|
|
3469
|
+
})
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
if (options.templateId === "custom-objects") {
|
|
3473
|
+
return {
|
|
3474
|
+
label: "custom-objects",
|
|
3475
|
+
workspaceDir: () => packageDir ?? import_node_path3.default.join(projectRoot, "custom-objects"),
|
|
3476
|
+
generate: async (svc) => {
|
|
3477
|
+
const workspaceResult = await svc.generateCustomObjectsWorkspace({});
|
|
3478
|
+
return {
|
|
3479
|
+
...workspaceResult,
|
|
3480
|
+
files: workspaceResult.files.map((f) => ({ ...f, precious: true }))
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
};
|
|
3484
|
+
}
|
|
3485
|
+
if (options.templateId === "custom-object") {
|
|
3486
|
+
const objectName = options.name;
|
|
3487
|
+
if (!objectName) {
|
|
3488
|
+
throw new Error("--name is required when --template-id is 'custom-object'");
|
|
3489
|
+
}
|
|
3490
|
+
return {
|
|
3491
|
+
label: `custom-object:${objectName}`,
|
|
3492
|
+
workspaceDir: () => packageDir ?? import_node_path3.default.join(projectRoot, "custom-objects"),
|
|
3493
|
+
generate: async (svc) => {
|
|
3494
|
+
const customObjectsWorkspaceDir = packageDir ?? import_node_path3.default.join(projectRoot, "custom-objects");
|
|
3495
|
+
const workspaceExists = (0, import_node_fs2.existsSync)(import_node_path3.default.join(customObjectsWorkspaceDir, "package.json")) && (0, import_node_fs2.existsSync)(import_node_path3.default.join(customObjectsWorkspaceDir, "tsconfig.json"));
|
|
3496
|
+
const objectResult = await svc.generateCustomObject({ name: objectName });
|
|
3497
|
+
if (workspaceExists) {
|
|
3498
|
+
return objectResult;
|
|
3499
|
+
}
|
|
3500
|
+
const workspaceResult = await svc.generateCustomObjectsWorkspace({});
|
|
3501
|
+
return {
|
|
3502
|
+
...objectResult,
|
|
3503
|
+
files: [
|
|
3504
|
+
...workspaceResult.files.map((f) => ({ ...f, precious: true })),
|
|
3505
|
+
...objectResult.files
|
|
3506
|
+
]
|
|
3507
|
+
};
|
|
3508
|
+
},
|
|
3509
|
+
finalize: async (_svc, _meta, root, _output, context) => {
|
|
3510
|
+
context.ux.log("\nUpdating stripe-app.yaml...");
|
|
3511
|
+
const manifestPath = import_node_path3.default.join(root, "stripe-app.yaml");
|
|
3512
|
+
const manifest = await (0, import_extensibility_tool_utils11._openStripeAppManifest)(manifestPath);
|
|
3513
|
+
const apiName = (0, import_extensibility_tool_utils11._toSnakeCase)(objectName);
|
|
3514
|
+
if (!manifest.hasCustomObject(apiName)) {
|
|
3515
|
+
manifest.addCustomObject(apiName, `custom-objects/src/${apiName}.object.ts`);
|
|
3516
|
+
const tx = (0, import_extensibility_tool_utils11._beginTransaction)();
|
|
3517
|
+
tx.adopt(manifest);
|
|
3518
|
+
const report = await tx.commit();
|
|
3519
|
+
if (report.written.length > 0) {
|
|
3520
|
+
context.ux.log("\u2713 Updated stripe-app.yaml");
|
|
3521
|
+
}
|
|
3522
|
+
} else {
|
|
3523
|
+
context.ux.log("- Custom object already registered in stripe-app.yaml");
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
};
|
|
3527
|
+
}
|
|
3528
|
+
const interfaceId = options.templateId;
|
|
3529
|
+
function buildRemoteFunctionMethodsAndEndpoints(extensionId, outputMethods) {
|
|
3530
|
+
const methods = {};
|
|
3531
|
+
const endpoints = Object.entries(outputMethods).map(([methodName, methodConfig]) => {
|
|
3532
|
+
const endpointId = `${extensionId}.${methodName}`;
|
|
3533
|
+
const customInput = typeof methodConfig === "object" && methodConfig !== null && "custom_input" in methodConfig ? Reflect.get(methodConfig, "custom_input") : void 0;
|
|
3534
|
+
methods[methodName] = {
|
|
3535
|
+
implementation_type: "remote_function",
|
|
3536
|
+
...customInput !== void 0 && { custom_input: customInput },
|
|
3537
|
+
endpoint_id: endpointId
|
|
3538
|
+
};
|
|
3539
|
+
return {
|
|
3540
|
+
id: endpointId,
|
|
3541
|
+
type: "remote_function",
|
|
3542
|
+
live: { url: "https://example.com/" }
|
|
3543
|
+
};
|
|
3544
|
+
});
|
|
3545
|
+
return { methods, endpoints };
|
|
3546
|
+
}
|
|
3547
|
+
return {
|
|
3548
|
+
label: interfaceId,
|
|
3549
|
+
workspaceDir: (meta) => packageDir ?? import_node_path3.default.join(projectRoot, "extensions", meta.id),
|
|
3550
|
+
generate: (svc, meta) => svc.generateExtension(interfaceId, {
|
|
3551
|
+
id: meta.id,
|
|
3552
|
+
name: meta.name,
|
|
3553
|
+
version: meta.version
|
|
3554
|
+
}),
|
|
3555
|
+
finalize: async (svc, meta, root, output, context) => {
|
|
3556
|
+
if (!output.methods) return;
|
|
3557
|
+
context.ux.log("\nUpdating stripe-app.yaml...");
|
|
3558
|
+
const manifest = await svc.loadManifest(import_node_path3.default.join(root, "stripe-app.yaml"));
|
|
3559
|
+
const permissions = [];
|
|
3560
|
+
const baseConfig = {
|
|
3561
|
+
id: meta.id,
|
|
3562
|
+
name: meta.name,
|
|
3563
|
+
interface_id: interfaceId,
|
|
3564
|
+
version: meta.version,
|
|
3565
|
+
permissions
|
|
3566
|
+
};
|
|
3567
|
+
if (options.implementationType === "remote-function") {
|
|
3568
|
+
const { methods, endpoints } = buildRemoteFunctionMethodsAndEndpoints(
|
|
3569
|
+
meta.id,
|
|
3570
|
+
output.methods
|
|
3571
|
+
);
|
|
3572
|
+
manifest.addOrUpdateExtension({ ...baseConfig, methods, endpoints });
|
|
3573
|
+
} else {
|
|
3574
|
+
manifest.addOrUpdateExtension({
|
|
3575
|
+
...baseConfig,
|
|
3576
|
+
script: {
|
|
3577
|
+
type: "typescript",
|
|
3578
|
+
content: `extensions/${meta.id}/src/index.ts`
|
|
3579
|
+
},
|
|
3580
|
+
methods: output.methods,
|
|
3581
|
+
configuration: {
|
|
3582
|
+
input_schema: {
|
|
3583
|
+
type: "json_schema",
|
|
3584
|
+
content: `extensions/${meta.id}/generated/config.schema.json`
|
|
3585
|
+
},
|
|
3586
|
+
ui_schema: {
|
|
3587
|
+
type: "jsonforms",
|
|
3588
|
+
content: `extensions/${meta.id}/generated/config.ui.json`
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
});
|
|
3592
|
+
}
|
|
3593
|
+
await manifest.save();
|
|
3594
|
+
}
|
|
3595
|
+
};
|
|
3596
|
+
}
|
|
3597
|
+
var _defaultServices = {
|
|
3598
|
+
generateRoot: (vars) => _rootTemplateManager.generate(vars),
|
|
3599
|
+
generateExtension: (key, vars) => _templateManager.generate(key, vars),
|
|
3600
|
+
generateCustomObject: async (vars) => {
|
|
3601
|
+
const plan = await import_internal._customObjectGenerators.plan(vars, {
|
|
3602
|
+
scope: "project",
|
|
3603
|
+
projectRoot: process.cwd()
|
|
3604
|
+
});
|
|
3605
|
+
return {
|
|
3606
|
+
files: plan.files.map((f) => ({ path: f.path, content: f.content }))
|
|
3607
|
+
};
|
|
3608
|
+
},
|
|
3609
|
+
generateCustomObjectsWorkspace: async (vars) => {
|
|
3610
|
+
const runner = new import_extensibility_tool_utils11._GeneratorRunner(import_internal._customObjectsWorkspaceGenerator);
|
|
3611
|
+
const plan = await runner.plan(vars, {
|
|
3612
|
+
scope: "project",
|
|
3613
|
+
projectRoot: process.cwd()
|
|
3614
|
+
});
|
|
3615
|
+
return {
|
|
3616
|
+
files: plan.files.map((f) => ({ path: f.path, content: f.content }))
|
|
3617
|
+
};
|
|
3618
|
+
},
|
|
3619
|
+
writeFiles: _writeGeneratedFiles,
|
|
3620
|
+
createDepManager: ({ cwd, context }) => new _DependencyManager({ cwd, ...context !== void 0 && { context } }),
|
|
3621
|
+
loadManifest: (manifestPath) => _StripeAppManifest.load(manifestPath),
|
|
3622
|
+
runHooks: runPostGenerationHooks
|
|
3623
|
+
};
|
|
3624
|
+
async function _regenAppProjectWorkspace(options, services = _defaultServices, context = (0, import_extensibility_tool_utils11._createCliContext)()) {
|
|
3625
|
+
const isCustomObjectMode = options.templateId === "custom-object";
|
|
3626
|
+
if (!isCustomObjectMode && !options.packageDir && !options.workspaceId) {
|
|
3627
|
+
throw new Error("At least one of packageDir or workspaceId is required");
|
|
3628
|
+
}
|
|
3629
|
+
const projectRoot = import_node_path3.default.resolve(options.root ?? ".");
|
|
3630
|
+
const packageDir = options.packageDir ? import_node_path3.default.resolve(options.packageDir) : void 0;
|
|
3631
|
+
const mode = resolveMode(options, projectRoot, packageDir);
|
|
3632
|
+
const metadata = isCustomObjectMode ? { id: "", name: "", version: "" } : await _resolvePackageMetadata(
|
|
3633
|
+
options.workspaceId,
|
|
3634
|
+
options.workspaceName,
|
|
3635
|
+
packageDir
|
|
3636
|
+
);
|
|
3637
|
+
const workspaceDir = mode.workspaceDir(metadata);
|
|
3638
|
+
const isRootRegen = options.templateId === void 0;
|
|
3639
|
+
context.ux.log(`Generating files from template: ${mode.label}...`);
|
|
3640
|
+
const templateOutput = await mode.generate(services, metadata);
|
|
3641
|
+
const allFiles = isRootRegen ? [
|
|
3642
|
+
...templateOutput.files,
|
|
3643
|
+
...await backfillMissingExtensionEslintConfigs(services, projectRoot)
|
|
3644
|
+
] : templateOutput.files;
|
|
3645
|
+
const filesToWrite = options.implementationType === "remote-function" ? allFiles.filter((f) => f.path.endsWith(".schema.json")) : allFiles;
|
|
3646
|
+
if (isRootRegen) {
|
|
3647
|
+
const existingPkgPath = import_node_path3.default.join(projectRoot, "package.json");
|
|
3648
|
+
let existingOverrides;
|
|
3649
|
+
try {
|
|
3650
|
+
const existingPkg = JSON.parse(await (0, import_promises5.readFile)(existingPkgPath, "utf-8"));
|
|
3651
|
+
existingOverrides = existingPkg.pnpm?.overrides;
|
|
3652
|
+
} catch {
|
|
3653
|
+
}
|
|
3654
|
+
if (existingOverrides && Object.keys(existingOverrides).length > 0) {
|
|
3655
|
+
const pkgFile = filesToWrite.find((f) => f.path === "package.json");
|
|
3656
|
+
if (pkgFile) {
|
|
3657
|
+
let newPkg;
|
|
3658
|
+
try {
|
|
3659
|
+
newPkg = JSON.parse(pkgFile.content);
|
|
3660
|
+
} catch (err) {
|
|
3661
|
+
throw new Error(
|
|
3662
|
+
`Failed to parse generated package.json while preserving pnpm.overrides: ${err instanceof Error ? err.message : String(err)}`
|
|
3663
|
+
);
|
|
3664
|
+
}
|
|
3665
|
+
newPkg.pnpm = {
|
|
3666
|
+
...newPkg.pnpm,
|
|
3667
|
+
overrides: { ...newPkg.pnpm?.overrides, ...existingOverrides }
|
|
3668
|
+
};
|
|
3669
|
+
pkgFile.content = JSON.stringify(newPkg, null, 2) + "\n";
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
if (isRootRegen) {
|
|
3674
|
+
const existingPkgPath = import_node_path3.default.join(projectRoot, "package.json");
|
|
3675
|
+
try {
|
|
3676
|
+
const existingPkg = JSON.parse(await (0, import_promises5.readFile)(existingPkgPath, "utf-8"));
|
|
3677
|
+
const existingOverrides = existingPkg.pnpm?.overrides;
|
|
3678
|
+
if (existingOverrides && Object.keys(existingOverrides).length > 0) {
|
|
3679
|
+
const pkgFile = filesToWrite.find((f) => f.path === "package.json");
|
|
3680
|
+
if (pkgFile) {
|
|
3681
|
+
const newPkg = JSON.parse(pkgFile.content);
|
|
3682
|
+
newPkg.pnpm = {
|
|
3683
|
+
...newPkg.pnpm,
|
|
3684
|
+
overrides: { ...newPkg.pnpm?.overrides, ...existingOverrides }
|
|
3685
|
+
};
|
|
3686
|
+
pkgFile.content = JSON.stringify(newPkg, null, 2) + "\n";
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
} catch {
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
context.ux.log("Writing files...");
|
|
3693
|
+
const writeResult = await services.writeFiles(filesToWrite, {
|
|
3694
|
+
cwd: projectRoot,
|
|
3695
|
+
write: options.force ? "overwrite-all" : (path5, cwd, newContent) => _advancedDiffPrompt(path5, cwd, newContent),
|
|
3696
|
+
onFileWritten: (file, wasOverwrite) => {
|
|
3697
|
+
const verb = wasOverwrite ? "Updated" : "Wrote";
|
|
3698
|
+
context.ux.log(`\u2713 ${verb} ${file.path}`);
|
|
3699
|
+
},
|
|
3700
|
+
onFileSkipped: (file) => {
|
|
3701
|
+
context.ux.log(`- Skipped ${file.path}`);
|
|
3702
|
+
},
|
|
3703
|
+
onFileIdentical: (file) => {
|
|
3704
|
+
context.ux.log(`= Identical ${file.path}`);
|
|
3705
|
+
}
|
|
3706
|
+
});
|
|
3707
|
+
if (writeResult.aborted) {
|
|
3708
|
+
context.ux.log("\nAborted by user.");
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3711
|
+
if (options.implementationType !== "remote-function" && !options.skipDependencies && templateOutput.dependencies) {
|
|
3712
|
+
context.ux.log("\nInstalling dependencies...");
|
|
3713
|
+
const allDeps = [
|
|
3714
|
+
...templateOutput.dependencies.runtime ?? [],
|
|
3715
|
+
...templateOutput.dependencies.dev ?? []
|
|
3716
|
+
];
|
|
3717
|
+
await installDependencies(services, workspaceDir, allDeps, context);
|
|
3718
|
+
}
|
|
3719
|
+
if (options.implementationType !== "remote-function" && !options.skipDependencies && !isRootRegen && !isCustomObjectMode) {
|
|
3720
|
+
await installDependencies(
|
|
3721
|
+
services,
|
|
3722
|
+
projectRoot,
|
|
3723
|
+
[
|
|
3724
|
+
_devNpmDep(
|
|
3725
|
+
EXTENSIBILITY_ESLINT_PLUGIN_PACKAGE_NAME,
|
|
3726
|
+
EXTENSIBILITY_ESLINT_PLUGIN_VERSION
|
|
3727
|
+
)
|
|
3728
|
+
],
|
|
3729
|
+
context
|
|
3730
|
+
);
|
|
3731
|
+
}
|
|
3732
|
+
await mode.finalize?.(services, metadata, projectRoot, templateOutput, context);
|
|
3733
|
+
if (options.implementationType !== "remote-function" && !options.skipHooks) {
|
|
3734
|
+
await services.runHooks(
|
|
3735
|
+
templateOutput.postGenerationHooks,
|
|
3736
|
+
workspaceDir,
|
|
3737
|
+
false,
|
|
3738
|
+
context
|
|
3739
|
+
);
|
|
3740
|
+
}
|
|
3741
|
+
context.ux.log("\n\u2713 Regeneration complete");
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
// src/bin/gen-workspace.ts
|
|
3745
|
+
var logger3 = (0, import_extensibility_tool_utils12._createLogger)({ name: "gen-workspace" });
|
|
3746
|
+
async function main() {
|
|
3747
|
+
await (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).usage("Regenerate extension or root workspace files from templates").help().version(false).command(
|
|
3748
|
+
"$0",
|
|
3749
|
+
"Regenerate files from templates",
|
|
3750
|
+
(y) => y.option("root", {
|
|
3751
|
+
type: "string",
|
|
3752
|
+
description: "Path to project root (defaults to cwd)"
|
|
3753
|
+
}).option("template-id", {
|
|
3754
|
+
type: "string",
|
|
3755
|
+
description: "Workspace template ID (e.g. extend.workflows.custom_action, custom-objects); omit for root mode"
|
|
3756
|
+
}).option("extension-interface-id", {
|
|
3757
|
+
type: "string",
|
|
3758
|
+
description: "Deprecated alias for --template-id",
|
|
3759
|
+
hidden: true
|
|
3760
|
+
}).option("package-dir", {
|
|
3761
|
+
type: "string",
|
|
3762
|
+
description: "Path to directory containing package.json (relative to cwd)"
|
|
3763
|
+
}).option("workspace-id", {
|
|
3764
|
+
type: "string",
|
|
3765
|
+
description: "Workspace ID; validated against package.json if both present"
|
|
3766
|
+
}).option("workspace-name", {
|
|
3767
|
+
type: "string",
|
|
3768
|
+
description: "Workspace name (overrides package.json description)"
|
|
3769
|
+
}).option("skip-dependencies", {
|
|
3770
|
+
type: "boolean",
|
|
3771
|
+
description: "Skip dependency installation",
|
|
3772
|
+
default: false
|
|
3773
|
+
}).option("skip-hooks", {
|
|
3774
|
+
type: "boolean",
|
|
3775
|
+
description: "Skip post-generation hooks",
|
|
3776
|
+
default: false
|
|
3777
|
+
}).option("force", {
|
|
3778
|
+
type: "boolean",
|
|
3779
|
+
description: "Overwrite all files without prompting",
|
|
3780
|
+
default: false
|
|
3781
|
+
}).option("implementation-type", {
|
|
3782
|
+
type: "string",
|
|
3783
|
+
choices: ["script", "remote-function"],
|
|
3784
|
+
description: 'Implementation type. Use "remote-function" to write only .schema.json files and skip dependencies and hooks.'
|
|
3785
|
+
}).option("name", {
|
|
3786
|
+
type: "string",
|
|
3787
|
+
description: "Custom object name (required with --template-id custom-object)"
|
|
3788
|
+
}).option("output-path", {
|
|
3789
|
+
type: "string",
|
|
3790
|
+
description: "Write JSON result to this file instead of stdout"
|
|
3791
|
+
}).example(
|
|
3792
|
+
"gen-workspace --package-dir .",
|
|
3793
|
+
"Regenerate root workspace files (run from project root)"
|
|
3794
|
+
).example(
|
|
3795
|
+
"gen-workspace --template-id extend.workflows.custom_action --workspace-id my-ext",
|
|
3796
|
+
"Create a new extension (run from project root)"
|
|
3797
|
+
).example(
|
|
3798
|
+
"gen-workspace --root ../.. --package-dir . --template-id extend.workflows.custom_action",
|
|
3799
|
+
"Regenerate extension from its directory"
|
|
3800
|
+
).example(
|
|
3801
|
+
"gen-workspace --package-dir extensions/my-ext --template-id extend.workflows.custom_action",
|
|
3802
|
+
"Regenerate extension from project root"
|
|
3803
|
+
).example(
|
|
3804
|
+
"gen-workspace --template-id custom-object --name Device --root .",
|
|
3805
|
+
"Generate a new custom object definition and test"
|
|
3806
|
+
),
|
|
3807
|
+
async (args) => {
|
|
3808
|
+
const ctx = (0, import_extensibility_tool_utils12._createCliContext)();
|
|
3809
|
+
const {
|
|
3810
|
+
root,
|
|
3811
|
+
templateId,
|
|
3812
|
+
extensionInterfaceId,
|
|
3813
|
+
packageDir,
|
|
3814
|
+
workspaceId,
|
|
3815
|
+
workspaceName,
|
|
3816
|
+
skipDependencies,
|
|
3817
|
+
skipHooks,
|
|
3818
|
+
force,
|
|
3819
|
+
implementationType,
|
|
3820
|
+
name,
|
|
3821
|
+
outputPath
|
|
3822
|
+
} = args;
|
|
3823
|
+
const resolvedTemplateId = templateId ?? extensionInterfaceId;
|
|
3824
|
+
await _regenAppProjectWorkspace(
|
|
3825
|
+
{
|
|
3826
|
+
...resolvedTemplateId !== void 0 && { templateId: resolvedTemplateId },
|
|
3827
|
+
...root !== void 0 && { root },
|
|
3828
|
+
...packageDir !== void 0 && { packageDir },
|
|
3829
|
+
...workspaceId !== void 0 && { workspaceId },
|
|
3830
|
+
...workspaceName !== void 0 && { workspaceName },
|
|
3831
|
+
...name !== void 0 && { name },
|
|
3832
|
+
skipDependencies,
|
|
3833
|
+
skipHooks,
|
|
3834
|
+
force,
|
|
3835
|
+
...implementationType !== void 0 && { implementationType }
|
|
3836
|
+
},
|
|
3837
|
+
void 0,
|
|
3838
|
+
ctx
|
|
3839
|
+
);
|
|
3840
|
+
(0, import_extensibility_tool_utils12._writeJsonOutput)(outputPath, { success: true });
|
|
3841
|
+
}
|
|
3842
|
+
).parse();
|
|
3843
|
+
}
|
|
3844
|
+
main().catch((err) => {
|
|
3845
|
+
logger3.error({ err }, "Unexpected error");
|
|
3846
|
+
process.exit(1);
|
|
3847
|
+
});
|