@servicenow/sdk-build-plugins 4.2.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acl-plugin.js +11 -0
- package/dist/acl-plugin.js.map +1 -1
- package/dist/applicability-plugin.d.ts +2 -0
- package/dist/applicability-plugin.js +72 -0
- package/dist/applicability-plugin.js.map +1 -0
- package/dist/atf/test-plugin.js +5 -2
- package/dist/atf/test-plugin.js.map +1 -1
- package/dist/basic-syntax-plugin.js +7 -1
- package/dist/basic-syntax-plugin.js.map +1 -1
- package/dist/business-rule-plugin.js +1 -0
- package/dist/business-rule-plugin.js.map +1 -1
- package/dist/call-expression-plugin.js +1 -107
- package/dist/call-expression-plugin.js.map +1 -1
- package/dist/column/column-to-record.d.ts +10 -3
- package/dist/column/column-to-record.js +44 -7
- package/dist/column/column-to-record.js.map +1 -1
- package/dist/column-plugin.d.ts +3 -1
- package/dist/column-plugin.js +12 -12
- package/dist/column-plugin.js.map +1 -1
- package/dist/dashboard/dashboard-component-property-defaults.d.ts +152 -0
- package/dist/dashboard/dashboard-component-property-defaults.js +264 -0
- package/dist/dashboard/dashboard-component-property-defaults.js.map +1 -0
- package/dist/dashboard/dashboard-component-resolver.d.ts +13 -0
- package/dist/dashboard/dashboard-component-resolver.js +69 -0
- package/dist/dashboard/dashboard-component-resolver.js.map +1 -0
- package/dist/dashboard/dashboard-plugin.d.ts +12 -0
- package/dist/dashboard/dashboard-plugin.js +397 -0
- package/dist/dashboard/dashboard-plugin.js.map +1 -0
- package/dist/data-plugin.d.ts +3 -0
- package/dist/data-plugin.js +61 -113
- package/dist/data-plugin.js.map +1 -1
- package/dist/email-notification-plugin.d.ts +2 -0
- package/dist/email-notification-plugin.js +541 -0
- package/dist/email-notification-plugin.js.map +1 -0
- package/dist/flow/constants/flow-plugin-constants.d.ts +58 -0
- package/dist/flow/constants/flow-plugin-constants.js +70 -0
- package/dist/flow/constants/flow-plugin-constants.js.map +1 -0
- package/dist/flow/flow-logic/flow-logic-constants.d.ts +38 -0
- package/dist/flow/flow-logic/flow-logic-constants.js +118 -0
- package/dist/flow/flow-logic/flow-logic-constants.js.map +1 -0
- package/dist/flow/flow-logic/flow-logic-diagnostics.d.ts +19 -0
- package/dist/flow/flow-logic/flow-logic-diagnostics.js +503 -0
- package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -0
- package/dist/flow/flow-logic/flow-logic-plugin-helpers.d.ts +62 -0
- package/dist/flow/flow-logic/flow-logic-plugin-helpers.js +2092 -0
- package/dist/flow/flow-logic/flow-logic-plugin-helpers.js.map +1 -0
- package/dist/flow/flow-logic/flow-logic-plugin.d.ts +52 -0
- package/dist/flow/flow-logic/flow-logic-plugin.js +283 -0
- package/dist/flow/flow-logic/flow-logic-plugin.js.map +1 -0
- package/dist/flow/flow-logic/flow-logic-shapes.d.ts +104 -0
- package/dist/flow/flow-logic/flow-logic-shapes.js +201 -0
- package/dist/flow/flow-logic/flow-logic-shapes.js.map +1 -0
- package/dist/flow/plugins/approval-rules-plugin.d.ts +2 -0
- package/dist/flow/plugins/approval-rules-plugin.js +49 -0
- package/dist/flow/plugins/approval-rules-plugin.js.map +1 -0
- package/dist/flow/plugins/flow-action-definition-plugin.d.ts +2 -0
- package/dist/flow/plugins/flow-action-definition-plugin.js +286 -0
- package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -0
- package/dist/flow/plugins/flow-data-pill-plugin.d.ts +9 -0
- package/dist/flow/plugins/flow-data-pill-plugin.js +212 -0
- package/dist/flow/plugins/flow-data-pill-plugin.js.map +1 -0
- package/dist/flow/plugins/flow-definition-plugin.d.ts +2 -0
- package/dist/flow/plugins/flow-definition-plugin.js +1668 -0
- package/dist/flow/plugins/flow-definition-plugin.js.map +1 -0
- package/dist/flow/plugins/flow-diagnostics-plugin.d.ts +26 -0
- package/dist/flow/plugins/flow-diagnostics-plugin.js +217 -0
- package/dist/flow/plugins/flow-diagnostics-plugin.js.map +1 -0
- package/dist/flow/plugins/flow-instance-plugin.d.ts +12 -0
- package/dist/flow/plugins/flow-instance-plugin.js +1205 -0
- package/dist/flow/plugins/flow-instance-plugin.js.map +1 -0
- package/dist/flow/plugins/flow-trigger-instance-plugin.d.ts +2 -0
- package/dist/flow/plugins/flow-trigger-instance-plugin.js +338 -0
- package/dist/flow/plugins/flow-trigger-instance-plugin.js.map +1 -0
- package/dist/flow/plugins/inline-script-plugin.d.ts +39 -0
- package/dist/flow/plugins/inline-script-plugin.js +80 -0
- package/dist/flow/plugins/inline-script-plugin.js.map +1 -0
- package/dist/flow/plugins/step-definition-plugin.d.ts +5 -0
- package/dist/flow/plugins/step-definition-plugin.js +71 -0
- package/dist/flow/plugins/step-definition-plugin.js.map +1 -0
- package/dist/flow/plugins/step-instance-plugin.d.ts +31 -0
- package/dist/flow/plugins/step-instance-plugin.js +339 -0
- package/dist/flow/plugins/step-instance-plugin.js.map +1 -0
- package/dist/flow/plugins/trigger-plugin.d.ts +2 -0
- package/dist/flow/plugins/trigger-plugin.js +96 -0
- package/dist/flow/plugins/trigger-plugin.js.map +1 -0
- package/dist/flow/plugins/wfa-datapill-plugin.d.ts +15 -0
- package/dist/flow/plugins/wfa-datapill-plugin.js +178 -0
- package/dist/flow/plugins/wfa-datapill-plugin.js.map +1 -0
- package/dist/flow/utils/approval-rules-processor.d.ts +13 -0
- package/dist/flow/utils/approval-rules-processor.js +267 -0
- package/dist/flow/utils/approval-rules-processor.js.map +1 -0
- package/dist/flow/utils/built-in-complex-objects.d.ts +19 -0
- package/dist/flow/utils/built-in-complex-objects.js +62 -0
- package/dist/flow/utils/built-in-complex-objects.js.map +1 -0
- package/dist/flow/utils/complex-object-resolver.d.ts +8 -0
- package/dist/flow/utils/complex-object-resolver.js +614 -0
- package/dist/flow/utils/complex-object-resolver.js.map +1 -0
- package/dist/flow/utils/complex-objects.d.ts +36 -0
- package/dist/flow/utils/complex-objects.js +481 -0
- package/dist/flow/utils/complex-objects.js.map +1 -0
- package/dist/flow/utils/data-pill-shapes.d.ts +58 -0
- package/dist/flow/utils/data-pill-shapes.js +135 -0
- package/dist/flow/utils/data-pill-shapes.js.map +1 -0
- package/dist/flow/utils/datapill-transformer.d.ts +110 -0
- package/dist/flow/utils/datapill-transformer.js +503 -0
- package/dist/flow/utils/datapill-transformer.js.map +1 -0
- package/dist/flow/utils/flow-constants.d.ts +72 -0
- package/dist/flow/utils/flow-constants.js +230 -0
- package/dist/flow/utils/flow-constants.js.map +1 -0
- package/dist/flow/utils/flow-io-to-record.d.ts +44 -0
- package/dist/flow/utils/flow-io-to-record.js +409 -0
- package/dist/flow/utils/flow-io-to-record.js.map +1 -0
- package/dist/flow/utils/flow-shapes.d.ts +161 -0
- package/dist/flow/utils/flow-shapes.js +255 -0
- package/dist/flow/utils/flow-shapes.js.map +1 -0
- package/dist/flow/utils/flow-to-xml.d.ts +16 -0
- package/dist/flow/utils/flow-to-xml.js +237 -0
- package/dist/flow/utils/flow-to-xml.js.map +1 -0
- package/dist/flow/utils/flow-variable-processor.d.ts +51 -0
- package/dist/flow/utils/flow-variable-processor.js +69 -0
- package/dist/flow/utils/flow-variable-processor.js.map +1 -0
- package/dist/flow/utils/label-cache-parser.d.ts +7 -0
- package/dist/flow/utils/label-cache-parser.js +24 -0
- package/dist/flow/utils/label-cache-parser.js.map +1 -0
- package/dist/flow/utils/label-cache-processor.d.ts +119 -0
- package/dist/flow/utils/label-cache-processor.js +719 -0
- package/dist/flow/utils/label-cache-processor.js.map +1 -0
- package/dist/flow/utils/pill-string-parser.d.ts +88 -0
- package/dist/flow/utils/pill-string-parser.js +306 -0
- package/dist/flow/utils/pill-string-parser.js.map +1 -0
- package/dist/flow/utils/schema-to-flow-object.d.ts +22 -0
- package/dist/flow/utils/schema-to-flow-object.js +318 -0
- package/dist/flow/utils/schema-to-flow-object.js.map +1 -0
- package/dist/flow/utils/service-catalog.d.ts +47 -0
- package/dist/flow/utils/service-catalog.js +137 -0
- package/dist/flow/utils/service-catalog.js.map +1 -0
- package/dist/flow/utils/utils.d.ts +117 -0
- package/dist/flow/utils/utils.js +345 -0
- package/dist/flow/utils/utils.js.map +1 -0
- package/dist/index.d.ts +20 -1
- package/dist/index.js +21 -1
- package/dist/index.js.map +1 -1
- package/dist/list-plugin.js +1 -1
- package/dist/list-plugin.js.map +1 -1
- package/dist/now-attach-plugin.d.ts +1 -0
- package/dist/now-attach-plugin.js +10 -10
- package/dist/now-attach-plugin.js.map +1 -1
- package/dist/now-ref-plugin.js +1 -1
- package/dist/now-ref-plugin.js.map +1 -1
- package/dist/record-plugin.d.ts +29 -0
- package/dist/record-plugin.js +66 -7
- package/dist/record-plugin.js.map +1 -1
- package/dist/repack/index.d.ts +2 -0
- package/dist/repack/index.js +8 -0
- package/dist/repack/index.js.map +1 -1
- package/dist/rest-api-plugin.js +54 -44
- package/dist/rest-api-plugin.js.map +1 -1
- package/dist/server-module-plugin/index.d.ts +10 -0
- package/dist/server-module-plugin/index.js +83 -59
- package/dist/server-module-plugin/index.js.map +1 -1
- package/dist/service-catalog/catalog-clientscript-plugin.d.ts +2 -0
- package/dist/service-catalog/catalog-clientscript-plugin.js +117 -0
- package/dist/service-catalog/catalog-clientscript-plugin.js.map +1 -0
- package/dist/service-catalog/catalog-item-plugin.d.ts +2 -0
- package/dist/service-catalog/catalog-item-plugin.js +115 -0
- package/dist/service-catalog/catalog-item-plugin.js.map +1 -0
- package/dist/service-catalog/catalog-ui-policy-plugin.d.ts +2 -0
- package/dist/service-catalog/catalog-ui-policy-plugin.js +266 -0
- package/dist/service-catalog/catalog-ui-policy-plugin.js.map +1 -0
- package/dist/service-catalog/index.d.ts +5 -0
- package/dist/service-catalog/index.js +22 -0
- package/dist/service-catalog/index.js.map +1 -0
- package/dist/service-catalog/record-to-shape.d.ts +6 -0
- package/dist/service-catalog/record-to-shape.js +93 -0
- package/dist/service-catalog/record-to-shape.js.map +1 -0
- package/dist/service-catalog/sc-record-producer-plugin.d.ts +2 -0
- package/dist/service-catalog/sc-record-producer-plugin.js +140 -0
- package/dist/service-catalog/sc-record-producer-plugin.js.map +1 -0
- package/dist/service-catalog/service-catalog-base.d.ts +311 -0
- package/dist/service-catalog/service-catalog-base.js +542 -0
- package/dist/service-catalog/service-catalog-base.js.map +1 -0
- package/dist/service-catalog/service-catalog-diagnostics.d.ts +45 -0
- package/dist/service-catalog/service-catalog-diagnostics.js +172 -0
- package/dist/service-catalog/service-catalog-diagnostics.js.map +1 -0
- package/dist/service-catalog/shape-to-record.d.ts +8 -0
- package/dist/service-catalog/shape-to-record.js +235 -0
- package/dist/service-catalog/shape-to-record.js.map +1 -0
- package/dist/service-catalog/utils.d.ts +323 -0
- package/dist/service-catalog/utils.js +1216 -0
- package/dist/service-catalog/utils.js.map +1 -0
- package/dist/service-catalog/variable-helper.d.ts +43 -0
- package/dist/service-catalog/variable-helper.js +92 -0
- package/dist/service-catalog/variable-helper.js.map +1 -0
- package/dist/service-catalog/variable-set-plugin.d.ts +2 -0
- package/dist/service-catalog/variable-set-plugin.js +175 -0
- package/dist/service-catalog/variable-set-plugin.js.map +1 -0
- package/dist/service-catalog/variables-transform.d.ts +139 -0
- package/dist/service-catalog/variables-transform.js +403 -0
- package/dist/service-catalog/variables-transform.js.map +1 -0
- package/dist/sla/sla-validators.d.ts +61 -0
- package/dist/sla/sla-validators.js +224 -0
- package/dist/sla/sla-validators.js.map +1 -0
- package/dist/sla-plugin.d.ts +5 -0
- package/dist/sla-plugin.js +280 -0
- package/dist/sla-plugin.js.map +1 -0
- package/dist/static-content-plugin.js +25 -2
- package/dist/static-content-plugin.js.map +1 -1
- package/dist/table-plugin.js +32 -15
- package/dist/table-plugin.js.map +1 -1
- package/dist/ui-page-plugin.js +832 -19
- package/dist/ui-page-plugin.js.map +1 -1
- package/dist/ui-policy-plugin.js +5 -7
- package/dist/ui-policy-plugin.js.map +1 -1
- package/dist/utils.d.ts +10 -1
- package/dist/utils.js +16 -0
- package/dist/utils.js.map +1 -1
- package/dist/ux-list-menu-config-plugin.d.ts +2 -0
- package/dist/ux-list-menu-config-plugin.js +292 -0
- package/dist/ux-list-menu-config-plugin.js.map +1 -0
- package/dist/workspace-plugin/chrome-tab.d.ts +2 -0
- package/dist/workspace-plugin/chrome-tab.js +46 -0
- package/dist/workspace-plugin/chrome-tab.js.map +1 -0
- package/dist/workspace-plugin/constants.d.ts +52 -0
- package/dist/workspace-plugin/constants.js +56 -0
- package/dist/workspace-plugin/constants.js.map +1 -0
- package/dist/workspace-plugin/fluent-utils.d.ts +9 -0
- package/dist/workspace-plugin/fluent-utils.js +60 -0
- package/dist/workspace-plugin/fluent-utils.js.map +1 -0
- package/dist/workspace-plugin/page.d.ts +8 -0
- package/dist/workspace-plugin/page.js +108 -0
- package/dist/workspace-plugin/page.js.map +1 -0
- package/dist/workspace-plugin/screen.d.ts +1 -0
- package/dist/workspace-plugin/screen.js +38 -0
- package/dist/workspace-plugin/screen.js.map +1 -0
- package/dist/workspace-plugin/templates/index.d.ts +10 -0
- package/dist/workspace-plugin/templates/index.js +20 -0
- package/dist/workspace-plugin/templates/index.js.map +1 -0
- package/dist/workspace-plugin/templates/record-page-composition.d.ts +1 -0
- package/dist/workspace-plugin/templates/record-page-composition.js +4043 -0
- package/dist/workspace-plugin/templates/record-page-composition.js.map +1 -0
- package/dist/workspace-plugin/templates/record-page-data.d.ts +1 -0
- package/dist/workspace-plugin/templates/record-page-data.js +527 -0
- package/dist/workspace-plugin/templates/record-page-data.js.map +1 -0
- package/dist/workspace-plugin/templates/record-page-interalEventMappings.d.ts +1 -0
- package/dist/workspace-plugin/templates/record-page-interalEventMappings.js +39 -0
- package/dist/workspace-plugin/templates/record-page-interalEventMappings.js.map +1 -0
- package/dist/workspace-plugin/templates/record-page-layoutModel.d.ts +1 -0
- package/dist/workspace-plugin/templates/record-page-layoutModel.js +55 -0
- package/dist/workspace-plugin/templates/record-page-layoutModel.js.map +1 -0
- package/dist/workspace-plugin/templates/record-page-properties.d.ts +1 -0
- package/dist/workspace-plugin/templates/record-page-properties.js +135 -0
- package/dist/workspace-plugin/templates/record-page-properties.js.map +1 -0
- package/dist/workspace-plugin/templates/record-page.d.ts +3 -0
- package/dist/workspace-plugin/templates/record-page.js +8 -0
- package/dist/workspace-plugin/templates/record-page.js.map +1 -0
- package/dist/workspace-plugin.d.ts +2 -0
- package/dist/workspace-plugin.js +453 -0
- package/dist/workspace-plugin.js.map +1 -0
- package/package.json +10 -12
- package/src/acl-plugin.ts +16 -1
- package/src/applicability-plugin.ts +82 -0
- package/src/atf/test-plugin.ts +6 -3
- package/src/basic-syntax-plugin.ts +10 -1
- package/src/business-rule-plugin.ts +2 -1
- package/src/call-expression-plugin.ts +2 -130
- package/src/column/column-to-record.ts +54 -8
- package/src/column-plugin.ts +29 -13
- package/src/dashboard/dashboard-component-property-defaults.ts +277 -0
- package/src/dashboard/dashboard-component-resolver.ts +69 -0
- package/src/dashboard/dashboard-plugin.ts +450 -0
- package/src/data-plugin.ts +67 -139
- package/src/email-notification-plugin.ts +850 -0
- package/src/flow/constants/flow-plugin-constants.ts +79 -0
- package/src/flow/flow-logic/flow-logic-constants.ts +120 -0
- package/src/flow/flow-logic/flow-logic-diagnostics.ts +591 -0
- package/src/flow/flow-logic/flow-logic-plugin-helpers.ts +2550 -0
- package/src/flow/flow-logic/flow-logic-plugin.ts +337 -0
- package/src/flow/flow-logic/flow-logic-shapes.ts +215 -0
- package/src/flow/plugins/approval-rules-plugin.ts +48 -0
- package/src/flow/plugins/flow-action-definition-plugin.ts +295 -0
- package/src/flow/plugins/flow-data-pill-plugin.ts +258 -0
- package/src/flow/plugins/flow-definition-plugin.ts +2173 -0
- package/src/flow/plugins/flow-diagnostics-plugin.ts +280 -0
- package/src/flow/plugins/flow-instance-plugin.ts +1499 -0
- package/src/flow/plugins/flow-trigger-instance-plugin.ts +444 -0
- package/src/flow/plugins/inline-script-plugin.ts +83 -0
- package/src/flow/plugins/step-definition-plugin.ts +67 -0
- package/src/flow/plugins/step-instance-plugin.ts +431 -0
- package/src/flow/plugins/trigger-plugin.ts +95 -0
- package/src/flow/plugins/wfa-datapill-plugin.ts +213 -0
- package/src/flow/utils/approval-rules-processor.ts +298 -0
- package/src/flow/utils/built-in-complex-objects.ts +81 -0
- package/src/flow/utils/complex-object-resolver.ts +875 -0
- package/src/flow/utils/complex-objects.ts +656 -0
- package/src/flow/utils/data-pill-shapes.ts +165 -0
- package/src/flow/utils/datapill-transformer.ts +632 -0
- package/src/flow/utils/flow-constants.ts +285 -0
- package/src/flow/utils/flow-io-to-record.ts +533 -0
- package/src/flow/utils/flow-shapes.ts +296 -0
- package/src/flow/utils/flow-to-xml.ts +318 -0
- package/src/flow/utils/flow-variable-processor.ts +100 -0
- package/src/flow/utils/label-cache-parser.ts +37 -0
- package/src/flow/utils/label-cache-processor.ts +870 -0
- package/src/flow/utils/pill-string-parser.ts +375 -0
- package/src/flow/utils/schema-to-flow-object.ts +385 -0
- package/src/flow/utils/service-catalog.ts +174 -0
- package/src/flow/utils/utils.ts +395 -0
- package/src/index.ts +20 -1
- package/src/list-plugin.ts +1 -1
- package/src/now-attach-plugin.ts +14 -11
- package/src/now-ref-plugin.ts +1 -1
- package/src/record-plugin.ts +76 -11
- package/src/repack/index.ts +14 -0
- package/src/rest-api-plugin.ts +62 -50
- package/src/server-module-plugin/index.ts +112 -86
- package/src/service-catalog/catalog-clientscript-plugin.ts +140 -0
- package/src/service-catalog/catalog-item-plugin.ts +162 -0
- package/src/service-catalog/catalog-ui-policy-plugin.ts +324 -0
- package/src/service-catalog/index.ts +5 -0
- package/src/service-catalog/record-to-shape.ts +109 -0
- package/src/service-catalog/sc-record-producer-plugin.ts +201 -0
- package/src/service-catalog/service-catalog-base.ts +600 -0
- package/src/service-catalog/service-catalog-diagnostics.ts +254 -0
- package/src/service-catalog/shape-to-record.ts +279 -0
- package/src/service-catalog/utils.ts +1455 -0
- package/src/service-catalog/variable-helper.ts +135 -0
- package/src/service-catalog/variable-set-plugin.ts +197 -0
- package/src/service-catalog/variables-transform.ts +438 -0
- package/src/sla/sla-validators.ts +331 -0
- package/src/sla-plugin.ts +358 -0
- package/src/static-content-plugin.ts +25 -2
- package/src/table-plugin.ts +49 -16
- package/src/ui-page-plugin.ts +1063 -20
- package/src/ui-policy-plugin.ts +5 -9
- package/src/utils.ts +24 -1
- package/src/ux-list-menu-config-plugin.ts +312 -0
- package/src/workspace-plugin/chrome-tab.ts +44 -0
- package/src/workspace-plugin/constants.ts +53 -0
- package/src/workspace-plugin/fluent-utils.ts +60 -0
- package/src/workspace-plugin/page.ts +139 -0
- package/src/workspace-plugin/screen.ts +34 -0
- package/src/workspace-plugin/templates/index.ts +17 -0
- package/src/workspace-plugin/templates/record-page-composition.ts +4051 -0
- package/src/workspace-plugin/templates/record-page-data.ts +523 -0
- package/src/workspace-plugin/templates/record-page-interalEventMappings.ts +35 -0
- package/src/workspace-plugin/templates/record-page-layoutModel.ts +51 -0
- package/src/workspace-plugin/templates/record-page-properties.ts +131 -0
- package/src/workspace-plugin/templates/record-page.ts +6 -0
- package/src/workspace-plugin.ts +574 -0
package/dist/ui-page-plugin.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.UiPagePlugin = void 0;
|
|
4
4
|
const sdk_build_core_1 = require("@servicenow/sdk-build-core");
|
|
5
|
-
const sdk_build_core_2 = require("@servicenow/sdk-build-core");
|
|
6
5
|
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
6
|
+
const xmlbuilder2_1 = require("xmlbuilder2");
|
|
7
7
|
const now_id_plugin_1 = require("./now-id-plugin");
|
|
8
|
+
const static_content_plugin_1 = require("./static-content-plugin");
|
|
9
|
+
const now_attach_plugin_1 = require("./now-attach-plugin");
|
|
8
10
|
const parserOptions = {
|
|
9
11
|
ignoreAttributes: false,
|
|
10
12
|
alwaysCreateTextNode: true,
|
|
@@ -31,6 +33,49 @@ const builderOptions = {
|
|
|
31
33
|
],
|
|
32
34
|
};
|
|
33
35
|
const POLARIS_APPSHELL_THEME_ID = 'c86a62e2c7022010099a308dc7c26022';
|
|
36
|
+
const BYOUI_ARTIFACT_NAME_SUFFIX = 'BYOUI Files';
|
|
37
|
+
// Matches the prefix HtmlImportPlugin prepends when it resolves an `import x from '*.html'`
|
|
38
|
+
// identifier. Its presence on the html value means the developer used the import variable,
|
|
39
|
+
// not an inline string — the only case where a source artifact should be created.
|
|
40
|
+
const HTML_IMPORT_PREFIX = '<!-- @fluent-import-html';
|
|
41
|
+
// sys_update_xml.payload has a max length of 4,096,000 characters. The artifact content goes
|
|
42
|
+
// through two rounds of base64 encoding with gzip compression in between:
|
|
43
|
+
// raw → base64 → gzip → base64 (stored in sys_attachment_doc.data)
|
|
44
|
+
// This means payload_chars ≈ raw_bytes × (4/3) × C × (4/3) = raw_bytes × 16C/9, where C is the
|
|
45
|
+
// gzip compression ratio. For typical TS/JS source files (C ≈ 0.25), 4 MB of raw source produces
|
|
46
|
+
// ~1.8 MB of payload data — safely within the 4,096,000-character limit.
|
|
47
|
+
const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2 MB — per-file guard (half of total budget)
|
|
48
|
+
const MAX_TOTAL_SIZE = 4 * 1024 * 1024; // 4 MB — derived from sys_update_xml.payload limit
|
|
49
|
+
/**
|
|
50
|
+
* Relationship configuration for source artifact tables.
|
|
51
|
+
*
|
|
52
|
+
* Defines the hierarchy: M2M → Artifact → Attachment → Attachment Docs
|
|
53
|
+
*/
|
|
54
|
+
const SOURCE_ARTIFACT_RELATIONSHIPS = {
|
|
55
|
+
sn_glider_source_artifact_m2m: {
|
|
56
|
+
via: 'application_file',
|
|
57
|
+
descendant: true,
|
|
58
|
+
relationships: {
|
|
59
|
+
sn_glider_source_artifact: {
|
|
60
|
+
via: 'source_artifact',
|
|
61
|
+
inverse: true,
|
|
62
|
+
descendant: true,
|
|
63
|
+
relationships: {
|
|
64
|
+
sys_attachment: {
|
|
65
|
+
via: 'table_sys_id',
|
|
66
|
+
descendant: true,
|
|
67
|
+
relationships: {
|
|
68
|
+
sys_attachment_doc: {
|
|
69
|
+
via: 'sys_attachment',
|
|
70
|
+
descendant: true,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
34
79
|
const parser = new fast_xml_parser_1.XMLParser(parserOptions);
|
|
35
80
|
// TODO: Remove this shim tag once we've shipped Glide support for this feature.
|
|
36
81
|
const nowUxGlobals = (themeId = POLARIS_APPSHELL_THEME_ID) => {
|
|
@@ -72,6 +117,9 @@ const nodeTransformer = (nodes) => {
|
|
|
72
117
|
for (let i = 0; i < nodes.length; i++) {
|
|
73
118
|
const node = nodes[i];
|
|
74
119
|
const tag = Object.keys(node)[0];
|
|
120
|
+
if (!tag) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
75
123
|
if (tag === 'sdk:now-ux-globals') {
|
|
76
124
|
const themeId = node[':@']?.['@_theme-id'];
|
|
77
125
|
nodes.splice(i, 1, ...nowUxGlobals(themeId));
|
|
@@ -88,7 +136,23 @@ exports.UiPagePlugin = sdk_build_core_1.Plugin.create({
|
|
|
88
136
|
name: 'UiPagePlugin',
|
|
89
137
|
records: {
|
|
90
138
|
sys_ui_page: {
|
|
91
|
-
|
|
139
|
+
composite: true,
|
|
140
|
+
coalesce: ['endpoint'],
|
|
141
|
+
relationships: SOURCE_ARTIFACT_RELATIONSHIPS,
|
|
142
|
+
async toShape(record, { descendants, fs, project, config, logger, diagnostics }) {
|
|
143
|
+
const shapeWithSourceArtifacts = await getShapeWithSourceArtifacts(record, descendants, {
|
|
144
|
+
fs,
|
|
145
|
+
project,
|
|
146
|
+
config,
|
|
147
|
+
logger,
|
|
148
|
+
diagnostics,
|
|
149
|
+
});
|
|
150
|
+
if (shapeWithSourceArtifacts) {
|
|
151
|
+
return {
|
|
152
|
+
success: true,
|
|
153
|
+
value: shapeWithSourceArtifacts,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
92
156
|
return {
|
|
93
157
|
success: true,
|
|
94
158
|
value: new sdk_build_core_1.CallExpressionShape({
|
|
@@ -109,25 +173,132 @@ exports.UiPagePlugin = sdk_build_core_1.Plugin.create({
|
|
|
109
173
|
}),
|
|
110
174
|
};
|
|
111
175
|
},
|
|
176
|
+
async toFile(uiPage, { config, descendants, transform }) {
|
|
177
|
+
if (!uiPage.has('endpoint')) {
|
|
178
|
+
return { success: false };
|
|
179
|
+
}
|
|
180
|
+
// Only use custom serialization if source artifacts are present
|
|
181
|
+
const sourceArtifacts = descendants.query('sn_glider_source_artifact_m2m');
|
|
182
|
+
if (sourceArtifacts.length === 0) {
|
|
183
|
+
// No source artifacts, use default serialization
|
|
184
|
+
return { success: false };
|
|
185
|
+
}
|
|
186
|
+
const uiPagePropsToSerialize = [
|
|
187
|
+
'name',
|
|
188
|
+
'endpoint',
|
|
189
|
+
'description',
|
|
190
|
+
'direct',
|
|
191
|
+
'category',
|
|
192
|
+
'html',
|
|
193
|
+
'client_script',
|
|
194
|
+
'processing_script',
|
|
195
|
+
];
|
|
196
|
+
const result = await serializeWithArtifact(uiPage, uiPagePropsToSerialize, {
|
|
197
|
+
config,
|
|
198
|
+
descendants,
|
|
199
|
+
transform,
|
|
200
|
+
});
|
|
201
|
+
return {
|
|
202
|
+
success: true,
|
|
203
|
+
value: result,
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
// These records are embedded as descendants of sys_ui_page XML but are also
|
|
208
|
+
// downloaded as standalone records during `init + transform`. Defining coalesce
|
|
209
|
+
// here ensures their sys_ids are registered in keys.ts during transform, so
|
|
210
|
+
// subsequent builds by any user reuse the same IDs rather than generating fresh UUIDs.
|
|
211
|
+
sn_glider_source_artifact: {
|
|
212
|
+
coalesce: ['name'],
|
|
213
|
+
relationships: SOURCE_ARTIFACT_RELATIONSHIPS.sn_glider_source_artifact_m2m.relationships.sn_glider_source_artifact
|
|
214
|
+
.relationships,
|
|
215
|
+
async toShape(record) {
|
|
216
|
+
return { success: true, value: sdk_build_core_1.Shape.noOp(record) };
|
|
217
|
+
},
|
|
218
|
+
// Custom diff: attachments use hash-based immutable IDs. New content → new attachment ID.
|
|
219
|
+
// Stale cleanup is handled via delete_multiple in serializeWithArtifact, so we must NOT
|
|
220
|
+
// emit DELETE records here — the default diff would conflict with that mechanism.
|
|
221
|
+
async diff(existing, incoming) {
|
|
222
|
+
if (incoming.query().length === 0 || existing.query().length === 0) {
|
|
223
|
+
return {
|
|
224
|
+
success: true,
|
|
225
|
+
value: incoming.query().length === 0 ? new sdk_build_core_1.Database() : new sdk_build_core_1.Database(incoming.query()),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const changedRecords = [];
|
|
229
|
+
const existingArtifacts = existing.query('sn_glider_source_artifact');
|
|
230
|
+
const incomingArtifacts = incoming.query('sn_glider_source_artifact');
|
|
231
|
+
const incomingAttachments = incoming.query('sys_attachment');
|
|
232
|
+
const incomingAttachmentDocs = incoming.query('sys_attachment_doc');
|
|
233
|
+
for (const incomingArtifact of incomingArtifacts) {
|
|
234
|
+
const existingArtifact = existingArtifacts.find((a) => a.getId().getValue() === incomingArtifact.getId().getValue());
|
|
235
|
+
changedRecords.push(existingArtifact ? existingArtifact.merge(incomingArtifact) : incomingArtifact);
|
|
236
|
+
// Always add new attachments — unique hash-based IDs per content version.
|
|
237
|
+
const artifactAttachments = incomingAttachments.filter((att) => att.get('table_sys_id').toString().getValue() === incomingArtifact.getId().getValue());
|
|
238
|
+
for (const attachment of artifactAttachments) {
|
|
239
|
+
changedRecords.push(attachment);
|
|
240
|
+
changedRecords.push(...incomingAttachmentDocs.filter((doc) => doc.get('sys_attachment').toString().getValue() === attachment.getId().getValue()));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return { success: true, value: new sdk_build_core_1.Database(changedRecords) };
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
sn_glider_source_artifact_m2m: {
|
|
247
|
+
coalesce: ['application_file', 'source_artifact'],
|
|
248
|
+
relationships: SOURCE_ARTIFACT_RELATIONSHIPS.sn_glider_source_artifact_m2m.relationships,
|
|
249
|
+
async toShape(record) {
|
|
250
|
+
return { success: true, value: sdk_build_core_1.Shape.noOp(record) };
|
|
251
|
+
},
|
|
252
|
+
async toFile() {
|
|
253
|
+
// Page M2Ms are already in handledGuids as descendants of sys_ui_page, so
|
|
254
|
+
// this handler only runs for asset M2Ms (application_file = sys_ux_lib_asset
|
|
255
|
+
// sys_id). Those are embedded in each sys_ux_lib_asset XML by
|
|
256
|
+
// static-content-plugin via sourceArtifactRelationships. Return success with
|
|
257
|
+
// no output to mark them as handled and prevent RecordPlugin's catch-all from
|
|
258
|
+
// serializing them as standalone XMLs.
|
|
259
|
+
return { success: true, value: [] };
|
|
260
|
+
},
|
|
112
261
|
},
|
|
113
262
|
},
|
|
114
263
|
shapes: [
|
|
115
264
|
{
|
|
116
265
|
shape: sdk_build_core_1.CallExpressionShape,
|
|
117
266
|
fileTypes: ['fluent'],
|
|
118
|
-
async toRecord(callExpression, { config, factory, diagnostics }) {
|
|
267
|
+
async toRecord(callExpression, { config, factory, diagnostics, fs, project, logger }) {
|
|
119
268
|
if (callExpression.getCallee() !== 'UiPage') {
|
|
120
269
|
return { success: false };
|
|
121
270
|
}
|
|
122
271
|
const arg = callExpression.getArgument(0).asObject();
|
|
123
272
|
const endpoint = arg.get('endpoint').asString();
|
|
124
273
|
const scope = config.scope;
|
|
125
|
-
if (scope !== 'global' && !(0,
|
|
274
|
+
if (scope !== 'global' && !(0, sdk_build_core_1.isSNScope)(scope) && !endpoint.getValue().startsWith(`${scope}_`)) {
|
|
126
275
|
diagnostics.error(endpoint.getOriginalNode(), `endpoint must begin with '${scope}_'`);
|
|
127
276
|
return { success: false };
|
|
128
277
|
}
|
|
129
278
|
const name = endpoint.asString().getValue().replace(`${scope}_`, '').replace(/\.do$/, '');
|
|
130
279
|
let html = arg.get('html').toString().getValue();
|
|
280
|
+
let sourceFilePaths = [];
|
|
281
|
+
let assetNames = [];
|
|
282
|
+
// HtmlImportPlugin (which runs before toRecord) resolves `html: _html` identifiers
|
|
283
|
+
// to the file content and prepends an HTML_IMPORT_PREFIX warning comment.
|
|
284
|
+
// Source artifacts are only created when that prefix is present, meaning the html
|
|
285
|
+
// argument actually referenced an imported .html file.
|
|
286
|
+
// An inline string (e.g. `html: '<h1>...</h1>'`) never gets the prefix, so an
|
|
287
|
+
// unrelated `.html` import in the same file does NOT trigger artifact creation.
|
|
288
|
+
if (html.trimStart().startsWith(HTML_IMPORT_PREFIX)) {
|
|
289
|
+
const originalNode = callExpression.getOriginalNode();
|
|
290
|
+
const sourceFile = originalNode.getSourceFile();
|
|
291
|
+
// biome-ignore lint/suspicious/noExplicitAny: ts-morph ImportDeclaration type not exported
|
|
292
|
+
const htmlImport = sourceFile.getImportDeclarations().find((imp) => imp.getModuleSpecifierValue().endsWith('.html'));
|
|
293
|
+
if (htmlImport) {
|
|
294
|
+
const relativeHtmlPath = htmlImport.getModuleSpecifierValue();
|
|
295
|
+
const sourceFileDir = sdk_build_core_1.path.dirname(sourceFile.getFilePath());
|
|
296
|
+
const absoluteHtmlPath = sdk_build_core_1.path.resolve(sourceFileDir, relativeHtmlPath);
|
|
297
|
+
const manifest = getUIPageSourceFilePaths(absoluteHtmlPath, fs, logger, config, project.getRootDir());
|
|
298
|
+
sourceFilePaths = manifest.files;
|
|
299
|
+
assetNames = manifest.assetNames;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
131
302
|
if (html) {
|
|
132
303
|
try {
|
|
133
304
|
const nodes = parser.parse(html);
|
|
@@ -144,26 +315,668 @@ exports.UiPagePlugin = sdk_build_core_1.Plugin.create({
|
|
|
144
315
|
return { success: false };
|
|
145
316
|
}
|
|
146
317
|
}
|
|
318
|
+
const record = await factory.createRecord({
|
|
319
|
+
source: callExpression,
|
|
320
|
+
table: 'sys_ui_page',
|
|
321
|
+
explicitId: arg.get('$id'),
|
|
322
|
+
properties: arg.transform(({ $ }) => ({
|
|
323
|
+
name: $.val(name),
|
|
324
|
+
endpoint: $.val(endpoint),
|
|
325
|
+
description: $,
|
|
326
|
+
direct: $.def(false),
|
|
327
|
+
category: $,
|
|
328
|
+
html: $.val(html).toCdata(),
|
|
329
|
+
client_script: $.from('clientScript').toCdata(),
|
|
330
|
+
processing_script: $.from('processingScript').toCdata(),
|
|
331
|
+
})),
|
|
332
|
+
});
|
|
333
|
+
// Build source artifact if source files are present in the manifest
|
|
334
|
+
if (sourceFilePaths.length > 0) {
|
|
335
|
+
logger.debug(`Found ${sourceFilePaths.length} source files in manifest`);
|
|
336
|
+
// Manifest files already contain paths relative to project root
|
|
337
|
+
const files = sourceFilePaths.map((file) => file.replace(/\\/g, '/'));
|
|
338
|
+
const prebuildPath = sdk_build_core_1.path.join(project.getRootDir(), 'now.prebuild.mjs');
|
|
339
|
+
if (sdk_build_core_1.FileSystem.existsSync(fs, prebuildPath)) {
|
|
340
|
+
files.push('now.prebuild.mjs');
|
|
341
|
+
}
|
|
342
|
+
const artifactName = `${endpoint.getValue()} - ${BYOUI_ARTIFACT_NAME_SUFFIX}`;
|
|
343
|
+
const sourceArtifactRecords = await buildArtifact(artifactName, files, record, {
|
|
344
|
+
fs,
|
|
345
|
+
project,
|
|
346
|
+
factory,
|
|
347
|
+
config,
|
|
348
|
+
logger,
|
|
349
|
+
diagnostics,
|
|
350
|
+
}, assetNames);
|
|
351
|
+
if (sourceArtifactRecords.length > 0) {
|
|
352
|
+
record.with(...sourceArtifactRecords);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
diagnostics.warn(record, 'No source artifact records were created despite source files being present');
|
|
356
|
+
}
|
|
357
|
+
}
|
|
147
358
|
return {
|
|
148
359
|
success: true,
|
|
149
|
-
value:
|
|
150
|
-
source: callExpression,
|
|
151
|
-
table: 'sys_ui_page',
|
|
152
|
-
explicitId: arg.get('$id'),
|
|
153
|
-
properties: arg.transform(({ $ }) => ({
|
|
154
|
-
name: $.val(name),
|
|
155
|
-
endpoint: $.val(endpoint),
|
|
156
|
-
description: $,
|
|
157
|
-
direct: $.def(false),
|
|
158
|
-
category: $,
|
|
159
|
-
html: $.val(html).toCdata(),
|
|
160
|
-
client_script: $.from('clientScript').toCdata(),
|
|
161
|
-
processing_script: $.from('processingScript').toCdata(),
|
|
162
|
-
})),
|
|
163
|
-
}),
|
|
360
|
+
value: record,
|
|
164
361
|
};
|
|
165
362
|
},
|
|
166
363
|
},
|
|
167
364
|
],
|
|
168
365
|
});
|
|
366
|
+
/**
|
|
367
|
+
* Reads source file paths from the UI source manifest file.
|
|
368
|
+
*
|
|
369
|
+
* The manifest is generated by isomorphic-rollup's sourceManifest plugin during build.
|
|
370
|
+
* It's a JSON file with the structure: { html, entry, files: string[] }
|
|
371
|
+
*
|
|
372
|
+
* @param htmlFilePath - Path to the HTML file
|
|
373
|
+
* @param fs - File system interface
|
|
374
|
+
* @param logger - Logger for diagnostics
|
|
375
|
+
* @param config - NowConfig with staticContentDir
|
|
376
|
+
* @param rootDir - Project root directory
|
|
377
|
+
* @returns Array of source file paths (empty array if manifest not found)
|
|
378
|
+
*/
|
|
379
|
+
const getUIPageSourceFilePaths = (htmlFilePath, fs, logger, config, rootDir) => {
|
|
380
|
+
const empty = { files: [], assetNames: [] };
|
|
381
|
+
try {
|
|
382
|
+
// Derive manifest path from HTML path
|
|
383
|
+
// The manifest is in the build output directory (staticContentDir), not the source directory
|
|
384
|
+
// e.g., src/client/index.html -> dist/static/index.ui-source-manifest.json
|
|
385
|
+
const htmlBasename = sdk_build_core_1.path.basename(htmlFilePath, '.html');
|
|
386
|
+
const staticContentAbsDir = sdk_build_core_1.path.join(rootDir, config.staticContentDir);
|
|
387
|
+
const manifestPath = sdk_build_core_1.path.join(staticContentAbsDir, `${htmlBasename}.ui-source-manifest.json`);
|
|
388
|
+
// Check if manifest file exists
|
|
389
|
+
try {
|
|
390
|
+
fs.accessSync(manifestPath);
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
logger.debug(`No source manifest found at ${manifestPath}`);
|
|
394
|
+
return empty;
|
|
395
|
+
}
|
|
396
|
+
const manifestContent = fs.readFileSync(manifestPath, { encoding: 'utf-8' });
|
|
397
|
+
const manifest = JSON.parse(manifestContent);
|
|
398
|
+
if (!manifest.files || !Array.isArray(manifest.files)) {
|
|
399
|
+
logger.warn(`Invalid manifest format at ${manifestPath}`);
|
|
400
|
+
return empty;
|
|
401
|
+
}
|
|
402
|
+
// Derive the JS asset name from the manifest's entry field, matching
|
|
403
|
+
// static-content-plugin's formula: path.join(scope, relativePath_without_ext).
|
|
404
|
+
// The manifest file is named after the HTML entry (e.g. index.ui-source-manifest.json)
|
|
405
|
+
// but the JS bundle is named after the JS entry (e.g. main.tsx -> main.jsdbx).
|
|
406
|
+
// Using manifest.entry's basename ensures the names align.
|
|
407
|
+
if (!manifest.entry || typeof manifest.entry !== 'string') {
|
|
408
|
+
logger.warn(`No entry field in manifest at ${manifestPath}`);
|
|
409
|
+
return empty;
|
|
410
|
+
}
|
|
411
|
+
const entryBasename = sdk_build_core_1.path.basename(manifest.entry, sdk_build_core_1.path.extname(manifest.entry));
|
|
412
|
+
const entryAssetName = sdk_build_core_1.path.join(config.scope, entryBasename).replace(/\\/g, '/');
|
|
413
|
+
// Check if a source map bundle also exists in staticContentDir.
|
|
414
|
+
// static-content-plugin names source map assets as: path.join(scope, relativePath.replace('dbx', ''))
|
|
415
|
+
// e.g. main.jsdbx.map -> main.js.map -> scope/main.js.map
|
|
416
|
+
const assetNames = [entryAssetName];
|
|
417
|
+
const sourceMapFilePath = sdk_build_core_1.path.join(staticContentAbsDir, `${entryBasename}.jsdbx.map`);
|
|
418
|
+
try {
|
|
419
|
+
fs.accessSync(sourceMapFilePath);
|
|
420
|
+
const sourceMapAssetName = sdk_build_core_1.path.join(config.scope, `${entryBasename}.js.map`).replace(/\\/g, '/');
|
|
421
|
+
assetNames.push(sourceMapAssetName);
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
// no source map in this build output — skip
|
|
425
|
+
}
|
|
426
|
+
return { files: manifest.files, assetNames };
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
logger.warn(`Failed to read source manifest: ${error}`);
|
|
430
|
+
return empty;
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
/**
|
|
434
|
+
* Extracts source files from artifacts and generates a SourceFileShape for the UI page.
|
|
435
|
+
*
|
|
436
|
+
* This function is called during transform to:
|
|
437
|
+
* 1. Find the source artifact associated with the UI page
|
|
438
|
+
* 2. Extract source files to the project directory
|
|
439
|
+
* 3. Generate a .now.ts file that imports the extracted HTML
|
|
440
|
+
*
|
|
441
|
+
* @param uiPageRecord - The UI page record being transformed
|
|
442
|
+
* @param descendants - Database containing descendant records (artifacts, attachments)
|
|
443
|
+
* @param context - Transform context with config, file system, project, diagnostics, and logger
|
|
444
|
+
* @returns SourceFileShape for the UI page, or undefined if no source artifact found
|
|
445
|
+
*/
|
|
446
|
+
async function getShapeWithSourceArtifacts(uiPageRecord, descendants, context) {
|
|
447
|
+
const sourceArtifact = getSourceArtifact(descendants, { name: new RegExp(`${BYOUI_ARTIFACT_NAME_SUFFIX}$`) });
|
|
448
|
+
if (!sourceArtifact) {
|
|
449
|
+
return undefined;
|
|
450
|
+
}
|
|
451
|
+
const artifactName = sourceArtifact.get('name').toString().getValue();
|
|
452
|
+
context.logger.debug(`Found source artifact ${artifactName}`);
|
|
453
|
+
const unpackedFiles = await extractArtifact(sourceArtifact, descendants, '', {
|
|
454
|
+
fs: context.fs,
|
|
455
|
+
project: context.project,
|
|
456
|
+
logger: context.logger,
|
|
457
|
+
diagnostics: context.diagnostics,
|
|
458
|
+
});
|
|
459
|
+
context.logger.debug(`Unpacked ${unpackedFiles.length} files from artifact ${artifactName}`);
|
|
460
|
+
const entryHtmlFilePath = unpackedFiles.find((file) => file.endsWith('.html'));
|
|
461
|
+
if (!entryHtmlFilePath) {
|
|
462
|
+
context.logger.debug(`Failed to find entry HTML file in artifact ${artifactName}`);
|
|
463
|
+
return undefined;
|
|
464
|
+
}
|
|
465
|
+
const { generatedDir, taxonomy } = context.config;
|
|
466
|
+
const tableName = uiPageRecord.getTable();
|
|
467
|
+
const fluentFileDirByTaxonomy = tableName && taxonomy.mapping[tableName] ? sdk_build_core_1.path.join(generatedDir, taxonomy.mapping[tableName]) : generatedDir;
|
|
468
|
+
const originalFilePath = uiPageRecord.getOriginalFilePath();
|
|
469
|
+
let dirWhereTheFluentFileWillExistEventually = sdk_build_core_1.path.dirname(originalFilePath);
|
|
470
|
+
if (context.project.isInMetadataDir(originalFilePath) || !context.project.isInRootDir(originalFilePath)) {
|
|
471
|
+
// context.project.isInMetadataDir(originalFilePath) => init --from downloads XML to metadata dir
|
|
472
|
+
// !context.project.isInRootDir(originalFilePath) => simple transform downloads XML is in a temp metadata dir
|
|
473
|
+
dirWhereTheFluentFileWillExistEventually = fluentFileDirByTaxonomy;
|
|
474
|
+
}
|
|
475
|
+
// need an import statement for the html file; normalize to forward slashes for cross-platform imports
|
|
476
|
+
const htmlFileRelativePath = sdk_build_core_1.path
|
|
477
|
+
.relative(dirWhereTheFluentFileWillExistEventually, entryHtmlFilePath)
|
|
478
|
+
.replace(/\\/g, '/');
|
|
479
|
+
if (htmlFileRelativePath && !context.project.isInFluentDir(originalFilePath)) {
|
|
480
|
+
const endpoint = uiPageRecord.get('endpoint').toString().getValue();
|
|
481
|
+
const description = uiPageRecord.get('description').toString().getValue();
|
|
482
|
+
const category = uiPageRecord.get('category').toString().getValue();
|
|
483
|
+
const direct = uiPageRecord.get('direct').toString().getValue();
|
|
484
|
+
const clientScript = uiPageRecord.get('client_script').toString().getValue();
|
|
485
|
+
const processingScript = uiPageRecord.get('processing_script').toString().getValue();
|
|
486
|
+
const fileContent = `
|
|
487
|
+
import '@servicenow/sdk/global'
|
|
488
|
+
import { UiPage } from '@servicenow/sdk/core'
|
|
489
|
+
import htmlFile from '${htmlFileRelativePath}'
|
|
490
|
+
|
|
491
|
+
UiPage({
|
|
492
|
+
$id: Now.ID['${uiPageRecord.getId().getValue()}'],
|
|
493
|
+
endpoint: ${JSON.stringify(endpoint)},
|
|
494
|
+
description: ${JSON.stringify(description)},
|
|
495
|
+
category: ${JSON.stringify(category)},
|
|
496
|
+
direct: ${direct},
|
|
497
|
+
html: htmlFile,
|
|
498
|
+
clientScript: ${JSON.stringify(clientScript)},
|
|
499
|
+
processingScript: ${JSON.stringify(processingScript)},
|
|
500
|
+
})
|
|
501
|
+
`;
|
|
502
|
+
const newFileName = endpoint.replace('.do', '.now.ts');
|
|
503
|
+
const sourceFileShape = new sdk_build_core_1.SourceFileShape({
|
|
504
|
+
source: uiPageRecord,
|
|
505
|
+
path: `${sdk_build_core_1.path.join(fluentFileDirByTaxonomy, newFileName)}`,
|
|
506
|
+
content: fileContent,
|
|
507
|
+
});
|
|
508
|
+
return sourceFileShape;
|
|
509
|
+
}
|
|
510
|
+
return undefined;
|
|
511
|
+
}
|
|
512
|
+
// ─── Source Artifact: Build ───────────────────────────────────────────────────
|
|
513
|
+
//
|
|
514
|
+
// Asset M2M coordination between UiPagePlugin and StaticContentPlugin:
|
|
515
|
+
//
|
|
516
|
+
// UiPagePlugin creates two kinds of M2M records:
|
|
517
|
+
// - Page M2M (application_file = sys_ui_page sys_id): embedded in sys_ui_page XML.
|
|
518
|
+
// - Asset M2Ms (application_file = sys_ux_lib_asset sys_id): embedded in
|
|
519
|
+
// sys_ux_lib_asset XML by StaticContentPlugin (transform direction), or claimed
|
|
520
|
+
// with no output by sn_glider_source_artifact_m2m.toFile (build direction) to
|
|
521
|
+
// prevent RecordPlugin from serializing them as standalone XMLs.
|
|
522
|
+
//
|
|
523
|
+
// A dummy sys_ux_lib_asset is created (but not added to the build database) solely
|
|
524
|
+
// to derive the same stable sys_id that StaticContentPlugin would use, ensuring both
|
|
525
|
+
// plugins always agree on the asset's sys_id.
|
|
526
|
+
/**
|
|
527
|
+
* Builds a source artifact record from the given files and associates it with the provided metadata record.
|
|
528
|
+
*
|
|
529
|
+
* Reads files from disk, encodes them as base64, compresses with gzip, creates
|
|
530
|
+
* artifact/attachment/attachment_doc records with hash-based IDs, and links to metadata via M2M.
|
|
531
|
+
*
|
|
532
|
+
* @param artifactName - Descriptive name for the artifact (e.g., "home_page.do - BYOUI Files")
|
|
533
|
+
* @param files - Array of file paths relative to project root
|
|
534
|
+
* @param record - Parent metadata record to link to (the artifact will be associated via M2M)
|
|
535
|
+
* @param context - Build context (fs, project, factory, config, logger, diagnostics)
|
|
536
|
+
* @returns Array of records: artifact, attachment, attachment docs, M2M link record, and
|
|
537
|
+
* per-asset M2M records (one per assetName). Returns empty array if no files
|
|
538
|
+
* provided or all files were skipped.
|
|
539
|
+
*/
|
|
540
|
+
async function buildArtifact(artifactName, files, record, context, assetNames) {
|
|
541
|
+
if (files.length === 0) {
|
|
542
|
+
return [];
|
|
543
|
+
}
|
|
544
|
+
const { files: attachmentFiles, skippedFiles, totalSize, } = await getAttachmentContent(context.fs, context.project.getRootDir(), files);
|
|
545
|
+
// Report skipped files as warnings before the empty-content check so the
|
|
546
|
+
// caller knows *why* no records were created (e.g. all files exceeded size limits).
|
|
547
|
+
for (const warning of skippedFiles) {
|
|
548
|
+
context.diagnostics.warn(record, warning);
|
|
549
|
+
}
|
|
550
|
+
if (!attachmentFiles.size) {
|
|
551
|
+
context.logger.warn(`No source artifact records created for "${artifactName}": all ${files.length} source file(s) were skipped`);
|
|
552
|
+
return [];
|
|
553
|
+
}
|
|
554
|
+
const artifactRecord = await createArtifactRecord(artifactName, record, context);
|
|
555
|
+
const attachmentRecords = await createAttachmentRecords(context.factory, artifactRecord, attachmentFiles, totalSize);
|
|
556
|
+
const m2mRecord = await createArtifactM2mRecord(artifactRecord, record, context);
|
|
557
|
+
const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(2);
|
|
558
|
+
context.logger.info(`Built source artifact "${artifactName}" (${totalSizeMB} MB, ${files.length} files)`);
|
|
559
|
+
const assetM2mRecords = await Promise.all((assetNames ?? []).map((assetName) => createAssetArtifactM2mRecord(artifactRecord, assetName, record, context)));
|
|
560
|
+
return [artifactRecord, ...attachmentRecords, m2mRecord, ...assetM2mRecords];
|
|
561
|
+
}
|
|
562
|
+
const createArtifactRecord = async (artifactName, metadataRecord, context) => {
|
|
563
|
+
return context.factory.createRecord({
|
|
564
|
+
source: metadataRecord.getSource(),
|
|
565
|
+
table: 'sn_glider_source_artifact',
|
|
566
|
+
explicitId: artifactName,
|
|
567
|
+
properties: {
|
|
568
|
+
name: artifactName,
|
|
569
|
+
},
|
|
570
|
+
});
|
|
571
|
+
};
|
|
572
|
+
const createArtifactM2mRecord = async (artifactRecord, metadataRecord, context) => {
|
|
573
|
+
return context.factory.createRecord({
|
|
574
|
+
source: metadataRecord.getSource(),
|
|
575
|
+
table: 'sn_glider_source_artifact_m2m',
|
|
576
|
+
explicitId: `${metadataRecord.getId().getValue()}-${artifactRecord.getId().getValue()}`,
|
|
577
|
+
properties: {
|
|
578
|
+
application_file: metadataRecord.getId().getValue(),
|
|
579
|
+
source_artifact: artifactRecord.getId().getValue(),
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
};
|
|
583
|
+
/**
|
|
584
|
+
* Creates a sn_glider_source_artifact_m2m record linking the given asset to the source artifact.
|
|
585
|
+
*
|
|
586
|
+
* A dummy sys_ux_lib_asset record is created with the same explicitId as static-content-plugin's
|
|
587
|
+
* real asset so both plugins resolve to the same sys_id. Only the M2M record is returned and
|
|
588
|
+
* added to the page's .with() tree so it enters the build database. static-content-plugin picks
|
|
589
|
+
* up the M2M via sourceArtifactRelationships (application_file = assetSysId) and embeds it in
|
|
590
|
+
* the sys_ux_lib_asset XML. UiPagePlugin's sn_glider_source_artifact_m2m.toFile marks the M2M as
|
|
591
|
+
* handled so RecordPlugin does not serialize it as a standalone XML in fluentFile.getOutput().
|
|
592
|
+
*/
|
|
593
|
+
const createAssetArtifactM2mRecord = async (artifactRecord, entryAssetName, metadataRecord, context) => {
|
|
594
|
+
const dummyAsset = await context.factory.createRecord({
|
|
595
|
+
source: metadataRecord.getSource(),
|
|
596
|
+
table: 'sys_ux_lib_asset',
|
|
597
|
+
explicitId: entryAssetName,
|
|
598
|
+
properties: {
|
|
599
|
+
name: entryAssetName,
|
|
600
|
+
},
|
|
601
|
+
});
|
|
602
|
+
return context.factory.createRecord({
|
|
603
|
+
source: metadataRecord.getSource(),
|
|
604
|
+
table: 'sn_glider_source_artifact_m2m',
|
|
605
|
+
explicitId: `${dummyAsset.getId().getValue()}-${artifactRecord.getId().getValue()}`,
|
|
606
|
+
properties: {
|
|
607
|
+
application_file: dummyAsset.getId().getValue(),
|
|
608
|
+
source_artifact: artifactRecord.getId().getValue(),
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
};
|
|
612
|
+
/**
|
|
613
|
+
* Creates attachment and attachment_doc records for the artifact.
|
|
614
|
+
*
|
|
615
|
+
* Attachments are compressed using zip and chunked for storage. The attachment ID includes
|
|
616
|
+
* a hash prefix to ensure immutability (each content version gets a unique ID).
|
|
617
|
+
*/
|
|
618
|
+
const createAttachmentRecords = async (factory, artifactRecord, files, totalSize) => {
|
|
619
|
+
// Build zip entries with a fixed mtime for deterministic output.
|
|
620
|
+
// ZIP format only supports dates 1980-2099; we use the minimum valid date.
|
|
621
|
+
// new Date(1982, 0, 1) creates midnight January 1, 1982 in local time,
|
|
622
|
+
// which fflate's local-time date encoding renders identically in any timezone.
|
|
623
|
+
const FIXED_MTIME = new Date(1982, 0, 1);
|
|
624
|
+
const zipEntries = {};
|
|
625
|
+
for (const [filePath, content] of files) {
|
|
626
|
+
zipEntries[filePath] = [content, { mtime: FIXED_MTIME }];
|
|
627
|
+
}
|
|
628
|
+
const zipped = (0, sdk_build_core_1.zipSync)(zipEntries);
|
|
629
|
+
const compressedData = Buffer.from(zipped);
|
|
630
|
+
const hash = await (0, now_attach_plugin_1.sha256)(compressedData);
|
|
631
|
+
// Uniform chunking — no special header split
|
|
632
|
+
const allChunks = (0, static_content_plugin_1.chunkData)(compressedData.toString('base64'));
|
|
633
|
+
// Deterministic sys_id derived from artifact + content hash — no keys registry entry needed.
|
|
634
|
+
// Same content always produces the same attachment ID without inflating keys.json.
|
|
635
|
+
const attachmentSysId = (0, static_content_plugin_1.generateId)(artifactRecord.getId().getValue(), 'sys_attachment', hash);
|
|
636
|
+
const attachment = await factory.createRecord({
|
|
637
|
+
source: artifactRecord.getSource(),
|
|
638
|
+
table: 'sys_attachment',
|
|
639
|
+
properties: {
|
|
640
|
+
sys_id: attachmentSysId,
|
|
641
|
+
average_image_color: '',
|
|
642
|
+
chunk_size_bytes: static_content_plugin_1.CHUNK_SIZE,
|
|
643
|
+
compressed: true,
|
|
644
|
+
content_type: 'application/zip',
|
|
645
|
+
hash,
|
|
646
|
+
image_height: '',
|
|
647
|
+
image_width: '',
|
|
648
|
+
size_bytes: totalSize,
|
|
649
|
+
size_compressed: compressedData.length,
|
|
650
|
+
file_name: `${artifactRecord.get('name').getValue()}.zip`,
|
|
651
|
+
table_name: artifactRecord.getTable(),
|
|
652
|
+
table_sys_id: artifactRecord.getId().getValue(),
|
|
653
|
+
},
|
|
654
|
+
});
|
|
655
|
+
const attachmentDocs = [];
|
|
656
|
+
for (let i = 0; i < allChunks.length; i++) {
|
|
657
|
+
const doc = await factory.createRecord({
|
|
658
|
+
source: artifactRecord.getSource(),
|
|
659
|
+
table: 'sys_attachment_doc',
|
|
660
|
+
properties: {
|
|
661
|
+
sys_id: (0, static_content_plugin_1.generateId)(attachmentSysId, 'sys_attachment_doc', i),
|
|
662
|
+
data: allChunks[i],
|
|
663
|
+
position: i,
|
|
664
|
+
sys_attachment: attachment.getId().getValue(),
|
|
665
|
+
},
|
|
666
|
+
});
|
|
667
|
+
attachmentDocs.push(doc);
|
|
668
|
+
}
|
|
669
|
+
return [attachment, ...attachmentDocs];
|
|
670
|
+
};
|
|
671
|
+
/**
|
|
672
|
+
* Reads files from disk and collects them for attachment storage.
|
|
673
|
+
*
|
|
674
|
+
* Error handling uses two distinct strategies:
|
|
675
|
+
* - **Recoverable** (per-file): files that cannot be read or exceed the per-file size limit are
|
|
676
|
+
* skipped and their paths are collected in the returned `skippedFiles` array.
|
|
677
|
+
* - **Fatal** (aggregate): if adding a file would push the total over `MAX_TOTAL_SIZE`, an error
|
|
678
|
+
* is thrown immediately.
|
|
679
|
+
*
|
|
680
|
+
* @returns Map of file paths to raw buffers, total size in bytes, and array of warning messages for skipped files
|
|
681
|
+
* @throws {Error} if the cumulative size of valid files would exceed `MAX_TOTAL_SIZE`
|
|
682
|
+
*/
|
|
683
|
+
const getAttachmentContent = async (fs, rootDir, files) => {
|
|
684
|
+
const fileMap = new Map();
|
|
685
|
+
let totalSize = 0;
|
|
686
|
+
const skippedFiles = [];
|
|
687
|
+
for (const filePath of files) {
|
|
688
|
+
let content;
|
|
689
|
+
try {
|
|
690
|
+
const absolutePath = sdk_build_core_1.path.join(rootDir, filePath.replace(/^[/\\]/, ''));
|
|
691
|
+
content = fs.readFileSync(absolutePath);
|
|
692
|
+
}
|
|
693
|
+
catch (error) {
|
|
694
|
+
skippedFiles.push(`Failed to read file ${filePath}: ${error}`);
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
const fileSize = content.length;
|
|
698
|
+
if (fileSize > MAX_FILE_SIZE) {
|
|
699
|
+
const sizeMB = (fileSize / (1024 * 1024)).toFixed(2);
|
|
700
|
+
skippedFiles.push(`Skipping file ${filePath}: exceeds max file size (${sizeMB} MB)`);
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
if (totalSize + fileSize > MAX_TOTAL_SIZE) {
|
|
704
|
+
const currentMB = (totalSize / (1024 * 1024)).toFixed(2);
|
|
705
|
+
const limitMB = (MAX_TOTAL_SIZE / (1024 * 1024)).toFixed(0);
|
|
706
|
+
throw new Error(`Total artifact size would exceed limit (${currentMB}/${limitMB} MB)`);
|
|
707
|
+
}
|
|
708
|
+
fileMap.set(filePath, content);
|
|
709
|
+
totalSize += fileSize;
|
|
710
|
+
}
|
|
711
|
+
return { files: fileMap, totalSize, skippedFiles };
|
|
712
|
+
};
|
|
713
|
+
// ─── Source Artifact: Extract ─────────────────────────────────────────────────
|
|
714
|
+
/**
|
|
715
|
+
* Extracts and unpacks files from a source artifact record.
|
|
716
|
+
*
|
|
717
|
+
* Finds attachments for the artifact, reconstructs compressed data from chunks,
|
|
718
|
+
* decompresses with unzipSync, and writes files to disk.
|
|
719
|
+
*
|
|
720
|
+
* @param artifactRecord - The source artifact record (type: sn_glider_source_artifact)
|
|
721
|
+
* @param allDescendants - Query interface to access attachment and attachment_doc records
|
|
722
|
+
* @param targetDir - Target directory path relative to project root (use '' for project root)
|
|
723
|
+
* @param context - Context with fs, project, logger, diagnostics
|
|
724
|
+
* @returns Array of file paths that were unpacked (relative to project root)
|
|
725
|
+
*/
|
|
726
|
+
async function extractArtifact(artifactRecord, allDescendants, targetDir = '', context) {
|
|
727
|
+
const artifactSysId = artifactRecord.getId().getValue();
|
|
728
|
+
// Find all attachments for this artifact and select the most recent one
|
|
729
|
+
const allAttachments = allDescendants.query('sys_attachment');
|
|
730
|
+
const matchingAttachments = allAttachments
|
|
731
|
+
.filter((att) => att.get('table_sys_id').toString().getValue() === artifactSysId)
|
|
732
|
+
.sort((a, b) => {
|
|
733
|
+
const aTime = a.get('sys_created_on').toString().getValue();
|
|
734
|
+
const bTime = b.get('sys_created_on').toString().getValue();
|
|
735
|
+
return aTime.localeCompare(bTime);
|
|
736
|
+
});
|
|
737
|
+
if (matchingAttachments.length === 0) {
|
|
738
|
+
context.logger.debug(`No attachments found for artifact ${artifactSysId}`);
|
|
739
|
+
return [];
|
|
740
|
+
}
|
|
741
|
+
// Use the most recent attachment (last after sorting by creation time)
|
|
742
|
+
const latestAttachment = matchingAttachments.at(-1);
|
|
743
|
+
if (!latestAttachment) {
|
|
744
|
+
return [];
|
|
745
|
+
}
|
|
746
|
+
// Get attachment docs (chunks) for the latest attachment
|
|
747
|
+
const allAttachmentDocs = allDescendants.query('sys_attachment_doc');
|
|
748
|
+
const attachmentDocs = allAttachmentDocs.filter((doc) => doc.get('sys_attachment').toString().getValue() === latestAttachment.getId().getValue());
|
|
749
|
+
if (attachmentDocs.length === 0) {
|
|
750
|
+
context.logger.debug(`No attachment docs found for attachment ${latestAttachment.getId().getValue()}`);
|
|
751
|
+
return [];
|
|
752
|
+
}
|
|
753
|
+
// Sort docs by position to ensure correct byte order
|
|
754
|
+
const sortedDocs = attachmentDocs.sort((a, b) => Number(a.get('position').toString().getValue()) - Number(b.get('position').toString().getValue()));
|
|
755
|
+
// Reconstruct zip bytes from all chunks uniformly
|
|
756
|
+
const chunks = sortedDocs.map((doc) => Buffer.from(doc.get('data').toString().getValue(), 'base64'));
|
|
757
|
+
const compressedData = Buffer.concat(chunks);
|
|
758
|
+
// Decompress — returns { [filePath]: Uint8Array }
|
|
759
|
+
const entries = (0, sdk_build_core_1.unzipSync)(compressedData);
|
|
760
|
+
const unpackedFiles = [];
|
|
761
|
+
for (const [filePath, data] of Object.entries(entries)) {
|
|
762
|
+
try {
|
|
763
|
+
const fileContent = Buffer.from(data);
|
|
764
|
+
const absolutePath = sdk_build_core_1.path.join(context.project.getRootDir(), targetDir, filePath);
|
|
765
|
+
const dir = sdk_build_core_1.path.dirname(absolutePath);
|
|
766
|
+
context.fs.mkdirSync(dir, { recursive: true });
|
|
767
|
+
context.fs.writeFileSync(absolutePath, fileContent);
|
|
768
|
+
context.project.addFile({ path: absolutePath, content: fileContent.toString('utf-8') });
|
|
769
|
+
unpackedFiles.push(filePath);
|
|
770
|
+
context.logger.debug(`Extracted file: ${filePath}`);
|
|
771
|
+
}
|
|
772
|
+
catch (error) {
|
|
773
|
+
context.diagnostics.error(artifactRecord, `Failed to write file ${filePath}: ${error}`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (unpackedFiles.length > 0) {
|
|
777
|
+
context.logger.info(`Extracted ${unpackedFiles.length} files from artifact "${artifactRecord.get('name').toString().getValue()}"`);
|
|
778
|
+
}
|
|
779
|
+
return unpackedFiles;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Finds a source artifact record by ID or name pattern in descendants.
|
|
783
|
+
*
|
|
784
|
+
* @param descendants - Query interface to access source artifact records
|
|
785
|
+
* @param query - Search criteria: use either id (exact sys_id) OR name (regex against name)
|
|
786
|
+
* @returns The matching source artifact record, or undefined if not found
|
|
787
|
+
*/
|
|
788
|
+
function getSourceArtifact(descendants, query) {
|
|
789
|
+
return descendants.query('sn_glider_source_artifact').find((record) => {
|
|
790
|
+
if (query.id) {
|
|
791
|
+
return record.getId().getValue() === query.id;
|
|
792
|
+
}
|
|
793
|
+
if (query.name) {
|
|
794
|
+
const nameMatch = record.get('name').toString().getValue().match(query.name);
|
|
795
|
+
return nameMatch && nameMatch.length > 0;
|
|
796
|
+
}
|
|
797
|
+
return false;
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
// ─── Source Artifact: Serialize ───────────────────────────────────────────────
|
|
801
|
+
/**
|
|
802
|
+
* Serializes a metadata record along with its embedded source artifacts to XML.
|
|
803
|
+
*
|
|
804
|
+
* Creates a custom XML format where source artifacts, attachments, M2M records,
|
|
805
|
+
* and the parent metadata record are all embedded within a single XML file.
|
|
806
|
+
*
|
|
807
|
+
* The generated XML contains (in order):
|
|
808
|
+
* 1. M2M records (sn_glider_source_artifact_m2m)
|
|
809
|
+
* 2. Artifact records (sn_glider_source_artifact)
|
|
810
|
+
* 3. Attachment records (sys_attachment)
|
|
811
|
+
* 4. Attachment doc records (sys_attachment_doc)
|
|
812
|
+
* 5. Parent metadata record
|
|
813
|
+
*
|
|
814
|
+
* Also emits delete_multiple elements to clean up stale attachments per artifact,
|
|
815
|
+
* since attachments use hash-based IDs that change with each new content version.
|
|
816
|
+
*
|
|
817
|
+
* @param metadataRecord - The main metadata record (e.g., sys_ui_page)
|
|
818
|
+
* @param metadataProps - List of property names to serialize from the metadata record
|
|
819
|
+
* @param context - Serialization context (descendants, config, transform)
|
|
820
|
+
*/
|
|
821
|
+
async function serializeWithArtifact(metadataRecord, metadataProps, context) {
|
|
822
|
+
const recordUpdate = (0, xmlbuilder2_1.create)().ele('record_update', { table: metadataRecord.getTable() });
|
|
823
|
+
// Embed the metadata record itself at the root, before descendant records
|
|
824
|
+
const metadataElement = recordUpdate.ele(metadataRecord.getTable(), { action: metadataRecord.getAction() });
|
|
825
|
+
metadataElement.ele('sys_id').txt(metadataRecord.getId().getValue());
|
|
826
|
+
metadataElement.ele('sys_scope', { display_value: context.config.scope }).txt(context.config.scopeId);
|
|
827
|
+
const updateName = await context.transform.getUpdateName(metadataRecord);
|
|
828
|
+
metadataElement.ele('sys_update_name').txt(updateName);
|
|
829
|
+
for (const prop of metadataProps) {
|
|
830
|
+
const value = metadataRecord.get(prop);
|
|
831
|
+
if (value.isDefined()) {
|
|
832
|
+
const stringValue = value.toString().getValue();
|
|
833
|
+
const contentType = value.toString().getContentType();
|
|
834
|
+
if (stringValue) {
|
|
835
|
+
if (contentType === 'plain') {
|
|
836
|
+
metadataElement.ele(prop).txt(stringValue);
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
// Handle CDATA content
|
|
840
|
+
// The ]]> sequence terminates CDATA, so we escape it as: ]]]]><![CDATA[>
|
|
841
|
+
// This creates: <![CDATA[content before]]]]><![CDATA[>content after]]>
|
|
842
|
+
if (stringValue.includes(']]>')) {
|
|
843
|
+
// Split by ]]> and create multiple adjacent CDATA sections
|
|
844
|
+
const parts = stringValue.split(']]>');
|
|
845
|
+
const propElement = metadataElement.ele(prop);
|
|
846
|
+
// Create adjacent CDATA sections for each part
|
|
847
|
+
// First part
|
|
848
|
+
propElement.dat(`${parts[0]}]]`);
|
|
849
|
+
// Remaining parts (each starts with a new CDATA section containing >)
|
|
850
|
+
for (let i = 1; i < parts.length; i++) {
|
|
851
|
+
propElement.dat(`>${parts[i]}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
else {
|
|
855
|
+
// No ]]> sequences, safe to use .dat() directly
|
|
856
|
+
metadataElement.ele(prop).dat(stringValue);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
metadataElement.ele(prop);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
// Embed the M2M record linking this metadata record to its source artifact.
|
|
866
|
+
// Asset M2Ms (application_file = sys_ux_lib_asset sys_id) are embedded in
|
|
867
|
+
// each sys_ux_lib_asset XML by static-content-plugin.
|
|
868
|
+
context.descendants.query('sn_glider_source_artifact_m2m').forEach((m2m) => {
|
|
869
|
+
const m2mElement = recordUpdate.ele('sn_glider_source_artifact_m2m', {
|
|
870
|
+
action: m2m.getAction(),
|
|
871
|
+
});
|
|
872
|
+
m2mElement.ele('sys_id').txt(m2m.getId().getValue());
|
|
873
|
+
const applicationFile = m2m.get('application_file');
|
|
874
|
+
if (applicationFile.isDefined()) {
|
|
875
|
+
const appFileId = applicationFile.isRecord()
|
|
876
|
+
? applicationFile.asRecord().getId().getValue()
|
|
877
|
+
: applicationFile.toString().getValue();
|
|
878
|
+
m2mElement.ele('application_file').txt(appFileId);
|
|
879
|
+
}
|
|
880
|
+
const artifactId = m2m.get('source_artifact');
|
|
881
|
+
if (artifactId.isDefined()) {
|
|
882
|
+
const artifactSysId = artifactId.isRecord()
|
|
883
|
+
? artifactId.asRecord().getId().getValue()
|
|
884
|
+
: artifactId.toString().getValue();
|
|
885
|
+
m2mElement.ele('source_artifact').txt(artifactSysId);
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
// Embed artifact records
|
|
889
|
+
context.descendants.query('sn_glider_source_artifact').forEach((artifact) => {
|
|
890
|
+
const artifactElement = recordUpdate.ele('sn_glider_source_artifact', {
|
|
891
|
+
action: artifact.getAction(),
|
|
892
|
+
});
|
|
893
|
+
artifactElement.ele('sys_id').txt(artifact.getId().getValue());
|
|
894
|
+
const artifactName = artifact.get('name');
|
|
895
|
+
if (artifactName.isDefined()) {
|
|
896
|
+
artifactElement.ele('name').txt(artifactName.toString().getValue());
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
// Embed attachment records
|
|
900
|
+
context.descendants.query('sys_attachment').forEach((attachment) => {
|
|
901
|
+
const attachmentElement = recordUpdate.ele('sys_attachment', { action: attachment.getAction() });
|
|
902
|
+
attachmentElement.ele('sys_id').txt(attachment.getId().getValue());
|
|
903
|
+
attachmentElement.ele('sys_scope', { display_value: context.config.scope }).txt(context.config.scopeId);
|
|
904
|
+
const tableId = attachment.get('table_sys_id');
|
|
905
|
+
if (tableId.isDefined()) {
|
|
906
|
+
attachmentElement.ele('table_sys_id').txt(tableId.toString().getValue());
|
|
907
|
+
}
|
|
908
|
+
const tableName = attachment.get('table_name');
|
|
909
|
+
if (tableName.isDefined()) {
|
|
910
|
+
attachmentElement.ele('table_name').txt(tableName.toString().getValue());
|
|
911
|
+
}
|
|
912
|
+
const attachmentProps = [
|
|
913
|
+
'file_name',
|
|
914
|
+
'content_type',
|
|
915
|
+
'size_bytes',
|
|
916
|
+
'size_compressed',
|
|
917
|
+
'compressed',
|
|
918
|
+
'chunk_size_bytes',
|
|
919
|
+
'hash',
|
|
920
|
+
'average_image_color',
|
|
921
|
+
'image_width',
|
|
922
|
+
'image_height',
|
|
923
|
+
];
|
|
924
|
+
for (const prop of attachmentProps) {
|
|
925
|
+
const value = attachment.get(prop);
|
|
926
|
+
if (value.isDefined()) {
|
|
927
|
+
attachmentElement.ele(prop).txt(value.toString().getValue());
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
// Embed attachment doc records (chunks)
|
|
932
|
+
context.descendants.query('sys_attachment_doc').forEach((doc) => {
|
|
933
|
+
const docElement = recordUpdate.ele('sys_attachment_doc', { action: doc.getAction() });
|
|
934
|
+
docElement.ele('sys_id').txt(doc.getId().getValue());
|
|
935
|
+
// Link to the attachment
|
|
936
|
+
const attachmentId = doc.get('sys_attachment');
|
|
937
|
+
if (attachmentId.isDefined()) {
|
|
938
|
+
const sysId = attachmentId.isRecord()
|
|
939
|
+
? attachmentId.asRecord().getId().getValue()
|
|
940
|
+
: attachmentId.toString().getValue();
|
|
941
|
+
docElement.ele('sys_attachment').txt(sysId);
|
|
942
|
+
}
|
|
943
|
+
const position = doc.get('position');
|
|
944
|
+
if (position.isDefined()) {
|
|
945
|
+
docElement.ele('position').txt(position.toString().getValue());
|
|
946
|
+
}
|
|
947
|
+
const data = doc.get('data');
|
|
948
|
+
if (data.isDefined()) {
|
|
949
|
+
docElement.ele('data').txt(data.toString().getValue());
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
// Emit delete_multiple elements to clean up stale attachments and docs per artifact.
|
|
953
|
+
// Since attachments use hash-based IDs, each build produces a new attachment ID.
|
|
954
|
+
// These queries tell the platform to remove any attachment/doc on this artifact
|
|
955
|
+
// that is not part of the current build.
|
|
956
|
+
context.descendants.query('sn_glider_source_artifact').forEach((artifact) => {
|
|
957
|
+
const artifactId = artifact.getId().getValue();
|
|
958
|
+
const currentAttachments = context.descendants
|
|
959
|
+
.query('sys_attachment')
|
|
960
|
+
.filter((att) => att.get('table_sys_id').toString().getValue() === artifactId);
|
|
961
|
+
const currentAttachmentIds = currentAttachments.map((att) => att.getId().getValue());
|
|
962
|
+
const currentDocIds = context.descendants
|
|
963
|
+
.query('sys_attachment_doc')
|
|
964
|
+
.filter((doc) => currentAttachmentIds.includes(doc.get('sys_attachment').toString().getValue()))
|
|
965
|
+
.map((doc) => doc.getId().getValue());
|
|
966
|
+
const attachmentQuery = currentAttachmentIds.length > 0
|
|
967
|
+
? `table_sys_id=${artifactId}^sys_idNOT IN${currentAttachmentIds.join(',')}`
|
|
968
|
+
: `table_sys_id=${artifactId}`;
|
|
969
|
+
recordUpdate.ele('sys_attachment', { action: 'delete_multiple', query: attachmentQuery });
|
|
970
|
+
const docQuery = currentDocIds.length > 0
|
|
971
|
+
? `sys_attachment.table_sys_id=${artifactId}^sys_idNOT IN${currentDocIds.join(',')}`
|
|
972
|
+
: `sys_attachment.table_sys_id=${artifactId}`;
|
|
973
|
+
recordUpdate.ele('sys_attachment_doc', { action: 'delete_multiple', query: docQuery });
|
|
974
|
+
});
|
|
975
|
+
return {
|
|
976
|
+
source: metadataRecord,
|
|
977
|
+
name: `${updateName}.xml`,
|
|
978
|
+
category: metadataRecord.getInstallCategory(),
|
|
979
|
+
content: recordUpdate.end({ prettyPrint: true }),
|
|
980
|
+
};
|
|
981
|
+
}
|
|
169
982
|
//# sourceMappingURL=ui-page-plugin.js.map
|