@sap/cds-dk 10.0.7 → 10.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +106 -0
- package/bin/add.js +7 -23
- package/bin/bind.js +14 -0
- package/bin/build.js +21 -34
- package/bin/cds.js +1 -1
- package/bin/compile/index.js +16 -41
- package/bin/completion/completionFs.js +11 -8
- package/bin/completion/index.js +603 -80
- package/bin/completion/scripts/cds.fish +620 -0
- package/bin/completion/scripts/cds.ps1 +403 -0
- package/bin/completion/scripts/cds.sh +327 -0
- package/bin/completion/scripts/cds.zsh +296 -0
- package/bin/completion/templates/cds-tmpl.fish +65 -0
- package/bin/completion/templates/cds-tmpl.ps1 +217 -0
- package/bin/completion/templates/cds-tmpl.sh +93 -0
- package/bin/completion/templates/cds-tmpl.zsh +74 -0
- package/bin/deploy/index.js +11 -1
- package/bin/deploy/options.js +25 -0
- package/{lib → bin}/deploy/to-hana/hana.js +8 -8
- package/{lib → bin}/deploy/to-hana/hdiDeployUtil.js +1 -1
- package/bin/deploy/{to-hana.js → to-hana/index.js} +1 -1
- package/{lib → bin}/deploy/to-hana/mtaUtil.js +1 -1
- package/bin/deploy/to-sql.js +0 -2
- package/bin/env.js +4 -1
- package/bin/export.js +44 -6
- package/bin/help/export.md +3 -0
- package/bin/help.js +1 -5
- package/bin/import.js +9 -0
- package/bin/init.js +6 -24
- package/bin/inspect.js +8 -0
- package/bin/lint.js +5 -3
- package/bin/login.js +26 -20
- package/bin/minify.js +6 -1
- package/bin/pull.js +19 -6
- package/bin/push.js +19 -6
- package/bin/repl.js +11 -2
- package/bin/run.js +12 -1
- package/bin/serve.js +13 -2
- package/bin/up.js +17 -11
- package/{lib → bin}/upgrade/constants.js +1 -1
- package/bin/{upgrade.js → upgrade/index.js} +2 -2
- package/{lib → bin}/upgrade/preflight/consistency.js +1 -1
- package/{lib → bin}/upgrade/preflight/index.js +1 -1
- package/{lib → bin}/upgrade/steps/analyze/index.js +1 -1
- package/{lib → bin}/upgrade/steps/install.js +1 -1
- package/{lib → bin}/upgrade/steps/openrewrite.js +1 -1
- package/{lib → bin}/upgrade/steps/removals.js +1 -1
- package/{lib → bin}/upgrade/steps/rewrite.js +1 -1
- package/{lib → bin}/upgrade/steps/verify.js +1 -1
- package/{lib/upgrade/index.js → bin/upgrade/upgrade.js} +2 -2
- package/{lib → bin}/upgrade/util/pom.js +2 -2
- package/{lib → bin}/upgrade/util/prompt.js +1 -1
- package/bin/util/containerize.js +10 -4
- package/bin/version.js +10 -1
- package/{lib → bin}/watch/hot-reload.js +1 -1
- package/bin/{watch.js → watch/index.js} +24 -9
- package/{lib → bin}/watch/livereload-connect.js +1 -1
- package/{lib → bin}/watch/vite-watch.js +1 -1
- package/{lib → bin}/watch/watched.js +2 -2
- package/lib/build/constants.js +1 -1
- package/lib/build/plugins/fiori/index.js +31 -2
- package/lib/build/plugins/internal.js +4 -2
- package/lib/build/plugins/mtx-sidecar/index.js +7 -8
- package/lib/build/plugins/nodejs/index.js +64 -7
- package/lib/build/util.js +2 -4
- package/lib/compile/to_asyncapi.js +1 -1
- package/lib/env/schema.js +16 -0
- package/lib/env/schemas/cds-rc.js +1 -76
- package/lib/import/importapi.js +2 -3
- package/lib/import/message.js +0 -2
- package/lib/init/constants.js +2 -1
- package/lib/init/index.js +2 -2
- package/lib/init/merge.js +123 -280
- package/lib/init/plugin.js +3 -1
- package/lib/init/projectReader.js +22 -6
- package/lib/init/registries/xs-app.js +3 -5
- package/lib/init/template/ai/files/mta.yaml.hbs +1 -1
- package/lib/init/template/ai/index.js +14 -2
- package/lib/init/template/ams/index.js +2 -2
- package/lib/init/template/app-frontend/files/containerize.yaml.hbs +2 -2
- package/lib/init/template/app-frontend/index.js +4 -4
- package/lib/init/template/application-logging/index.js +2 -2
- package/lib/init/template/approuter/files/package.json +1 -1
- package/lib/init/template/approuter/index.js +8 -3
- package/lib/init/template/attachments/files/package.json.hbs +1 -1
- package/lib/init/template/attachments/index.js +2 -2
- package/lib/init/template/audit-logging/index.js +2 -2
- package/lib/init/template/cloud-logging/files/package.json.hbs +4 -4
- package/lib/init/template/cloud-logging/index.js +2 -2
- package/lib/init/template/completion/completionSetup.js +36 -39
- package/lib/init/template/connectivity/index.js +2 -2
- package/lib/init/template/data/as-csv.js +15 -5
- package/lib/init/template/data/as-json.js +117 -42
- package/lib/init/template/data/index.js +16 -2
- package/lib/init/template/destination/index.js +2 -2
- package/lib/init/template/dynatrace/files/package.json.hbs +3 -3
- package/lib/init/template/dynatrace/index.js +2 -2
- package/lib/init/template/enterprise-messaging/index.js +2 -2
- package/lib/init/template/enterprise-messaging-shared/index.js +2 -2
- package/lib/init/template/extensibility/files/mta.yaml.hbs +11 -0
- package/lib/init/template/extensibility/files/xs-security.json.hbs +1 -1
- package/lib/init/template/extensibility/index.js +3 -3
- package/lib/init/template/github-actions/index.js +2 -2
- package/lib/init/template/hana/index.js +3 -2
- package/lib/init/template/hana-serverless/files/db/package.json +14 -0
- package/lib/init/template/hana-serverless/files/identity.values.yaml.hbs +10 -0
- package/lib/init/template/hana-serverless/files/mta.yaml.hbs +64 -0
- package/lib/init/template/hana-serverless/files/package.json.hbs +35 -0
- package/lib/init/template/hana-serverless/files/src/.hdiconfig +139 -0
- package/lib/init/template/hana-serverless/files/undeploy.json +7 -0
- package/lib/init/template/hana-serverless/files/values.yaml.hbs +18 -0
- package/lib/init/template/hana-serverless/index.js +184 -0
- package/lib/init/template/html5-repo/files/containerize.yaml.hbs +2 -2
- package/lib/init/template/html5-repo/files/mta.yaml.hbs +5 -0
- package/lib/init/template/html5-repo/index.js +18 -14
- package/lib/init/template/http/index.js +123 -26
- package/lib/init/template/ias/index.js +2 -2
- package/lib/init/template/kafka/index.js +2 -2
- package/lib/init/template/kyma/index.js +7 -5
- package/lib/init/template/malware-scanner/index.js +2 -2
- package/lib/init/template/mta/files/mta.yaml.hbs +1 -1
- package/lib/init/template/multitenancy/index.js +18 -3
- package/lib/init/template/notifications/index.js +2 -2
- package/lib/init/template/portal/files/mta.yaml.hbs +4 -0
- package/lib/init/template/portal/index.js +13 -8
- package/lib/init/template/postgres/index.js +2 -2
- package/lib/init/template/redis/index.js +2 -2
- package/lib/init/template/sample/files/db/currencies.cds +2 -0
- package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Authors.csv +1 -1
- package/lib/init/template/tiny-sample/files/nodejs/db/data/CatalogService.Books.csv +2 -2
- package/lib/init/template/workzone-standard/index.js +5 -5
- package/lib/init/template/xsuaa/index.js +20 -2
- package/lib/lint/eslintHelp.json +1 -1
- package/lib/mtx/auth_manager.js +103 -37
- package/lib/mtx/params.js +3 -2
- package/lib/mtx/settings_manager.js +45 -28
- package/lib/mtx/tenant_action.js +0 -1
- package/lib/mtx/util/credentials.js +62 -0
- package/node_modules/@cap-js/db-service/CHANGELOG.md +17 -0
- package/node_modules/@cap-js/db-service/lib/common/generic-pool.js +4 -7
- package/node_modules/@cap-js/db-service/lib/cqn2pql.js +1 -1
- package/node_modules/@cap-js/db-service/lib/cqn2sql.js +15 -11
- package/node_modules/@cap-js/db-service/lib/cqn4sql.js +119 -19
- package/node_modules/@cap-js/db-service/lib/infer/index.js +27 -16
- package/node_modules/@cap-js/db-service/lib/recursion-guard.js +40 -0
- package/node_modules/@cap-js/db-service/lib/search.js +1 -1
- package/node_modules/@cap-js/db-service/package.json +1 -1
- package/node_modules/@cap-js/sqlite/CHANGELOG.md +19 -0
- package/node_modules/@cap-js/sqlite/lib/SQLiteService.js +110 -14
- package/node_modules/@cap-js/sqlite/package.json +3 -3
- package/node_modules/@sap/cds/CHANGELOG.md +57 -0
- package/node_modules/@sap/cds/app/index.js +5 -4
- package/node_modules/@sap/cds/eslint.config.mjs +1 -0
- package/node_modules/@sap/cds/lib/compile/minify.js +14 -10
- package/node_modules/@sap/cds/lib/env/cds-env.js +1 -1
- package/node_modules/@sap/cds/lib/env/cds-requires.js +22 -11
- package/node_modules/@sap/cds/lib/env/defaults.js +4 -1
- package/node_modules/@sap/cds/lib/env/schemas/cds-rc.js +5 -2
- package/node_modules/@sap/cds/lib/plugins.js +9 -5
- package/node_modules/@sap/cds/lib/req/spawn.js +1 -1
- package/node_modules/@sap/cds/lib/srv/srv-dispatch.js +1 -1
- package/node_modules/@sap/cds/lib/srv/srv-models.js +1 -1
- package/node_modules/@sap/cds/lib/utils/cds-utils.js +2 -1
- package/node_modules/@sap/cds/libx/_runtime/common/generic/assert.js +2 -3
- package/node_modules/@sap/cds/libx/_runtime/common/generic/auth/restrict.js +8 -1
- package/node_modules/@sap/cds/libx/_runtime/common/generic/crud.js +18 -8
- package/node_modules/@sap/cds/libx/_runtime/fiori/lean-draft.js +25 -17
- package/node_modules/@sap/cds/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +9 -3
- package/node_modules/@sap/cds/libx/_runtime/remote/Service.js +4 -19
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/LruCache.js +42 -0
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/client.js +14 -4
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/cloudSdkProvider.js +2 -9
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/destinationClient.js +150 -0
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/error.js +13 -0
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/fetchClient.js +26 -18
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/query.js +34 -23
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/token.js +82 -0
- package/node_modules/@sap/cds/libx/_runtime/ucl/Service.js +36 -189
- package/node_modules/@sap/cds/libx/common/utils/streaming.js +16 -16
- package/node_modules/@sap/cds/libx/http/location.js +3 -3
- package/node_modules/@sap/cds/libx/odata/middleware/batch.js +16 -10
- package/node_modules/@sap/cds/libx/odata/middleware/metadata.js +9 -6
- package/node_modules/@sap/cds/libx/odata/middleware/service-document.js +1 -1
- package/node_modules/@sap/cds/libx/odata/parse/afterburner.js +19 -3
- package/node_modules/@sap/cds/libx/odata/parse/cqn2odata.js +83 -86
- package/node_modules/@sap/cds/libx/queue/SchedulingService.js +17 -2
- package/node_modules/@sap/cds/libx/queue/processing.js +1 -3
- package/node_modules/@sap/cds/libx/rest/RestAdapter.js +3 -7
- package/node_modules/@sap/cds/libx/rest/middleware/delete.js +3 -3
- package/node_modules/@sap/cds/libx/rest/middleware/parse.js +13 -8
- package/node_modules/@sap/cds/libx/rest/middleware/read.js +3 -3
- package/node_modules/@sap/cds/libx/rest/middleware/upsert.js +2 -2
- package/node_modules/@sap/cds/package.json +1 -1
- package/node_modules/@sap/cds-compiler/CHANGELOG.md +75 -8
- package/node_modules/@sap/cds-compiler/bin/cdsc.js +2 -2
- package/node_modules/@sap/cds-compiler/doc/IncompatibleChanges_v7.md +46 -19
- package/node_modules/@sap/cds-compiler/lib/api/main.js +10 -7
- package/node_modules/@sap/cds-compiler/lib/api/options.js +8 -2
- package/node_modules/@sap/cds-compiler/lib/base/csnRefs.js +5 -2
- package/node_modules/@sap/cds-compiler/lib/base/message-registry.js +4 -0
- package/node_modules/@sap/cds-compiler/lib/base/optionProcessor.js +12 -0
- package/node_modules/@sap/cds-compiler/lib/checks/enricher.js +1 -0
- package/node_modules/@sap/cds-compiler/lib/compiler/assert-consistency.js +8 -7
- package/node_modules/@sap/cds-compiler/lib/compiler/checks.js +3 -3
- package/node_modules/@sap/cds-compiler/lib/compiler/define.js +301 -75
- package/node_modules/@sap/cds-compiler/lib/compiler/dictionaries.js +2 -0
- package/node_modules/@sap/cds-compiler/lib/compiler/extend.js +40 -26
- package/node_modules/@sap/cds-compiler/lib/compiler/populate.js +45 -60
- package/node_modules/@sap/cds-compiler/lib/compiler/propagator.js +16 -7
- package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +48 -18
- package/node_modules/@sap/cds-compiler/lib/compiler/shared.js +7 -6
- package/node_modules/@sap/cds-compiler/lib/compiler/tweak-assocs.js +62 -50
- package/node_modules/@sap/cds-compiler/lib/compiler/utils.js +29 -14
- package/node_modules/@sap/cds-compiler/lib/edm/edmAnnoPreprocessor.js +6 -5
- package/node_modules/@sap/cds-compiler/lib/gen/CdlGrammar.checksum +1 -1
- package/node_modules/@sap/cds-compiler/lib/gen/CdlParser.js +1119 -1109
- package/node_modules/@sap/cds-compiler/lib/gen/Dictionary.json +19 -3
- package/node_modules/@sap/cds-compiler/lib/json/from-csn.js +10 -16
- package/node_modules/@sap/cds-compiler/lib/json/to-csn.js +58 -39
- package/node_modules/@sap/cds-compiler/lib/main.d.ts +8 -0
- package/node_modules/@sap/cds-compiler/lib/main.js +1 -0
- package/node_modules/@sap/cds-compiler/lib/model/csnUtils.js +4 -0
- package/node_modules/@sap/cds-compiler/lib/parsers/AstBuildingParser.js +2 -1
- package/node_modules/@sap/cds-compiler/lib/parsers/XprTree.js +8 -0
- package/node_modules/@sap/cds-compiler/lib/render/toCdl.js +22 -1
- package/node_modules/@sap/cds-compiler/lib/render/toSql.js +17 -2
- package/node_modules/@sap/cds-compiler/lib/render/utils/common.js +6 -1
- package/node_modules/@sap/cds-compiler/lib/render/utils/operators.js +27 -0
- package/node_modules/@sap/cds-compiler/lib/render/utils/standardDatabaseFunctions.js +125 -3
- package/node_modules/@sap/cds-compiler/lib/tool-lib/revealInternalProperties.js +2 -1
- package/node_modules/@sap/cds-compiler/lib/transform/db/applyTransformations.js +15 -0
- package/node_modules/@sap/cds-compiler/lib/transform/db/associations.js +10 -2
- package/node_modules/@sap/cds-compiler/lib/transform/db/backlinks.js +22 -6
- package/node_modules/@sap/cds-compiler/lib/transform/db/constraints.js +62 -1
- package/node_modules/@sap/cds-compiler/lib/transform/db/flattening.js +4 -3
- package/node_modules/@sap/cds-compiler/lib/transform/effective/annotations.js +40 -0
- package/node_modules/@sap/cds-compiler/lib/transform/effective/flattening.js +17 -1
- package/node_modules/@sap/cds-compiler/lib/transform/effective/main.js +20 -0
- package/node_modules/@sap/cds-compiler/lib/transform/fioriTreeViews.js +12 -1
- package/node_modules/@sap/cds-compiler/lib/transform/forRelationalDB.js +7 -2
- package/node_modules/@sap/cds-compiler/lib/transform/odata/adaptAnnotationRefs.js +11 -5
- package/node_modules/@sap/cds-compiler/lib/transform/translateAssocsToJoins.js +48 -10
- package/node_modules/@sap/cds-compiler/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/CHANGELOG.md +49 -0
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx-migrate.js +26 -5
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx.js +37 -16
- package/node_modules/@sap/cds-mtxs/lib/utils.js +23 -7
- package/node_modules/@sap/cds-mtxs/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/srv/cf/abstract-provisioning-service.js +63 -8
- package/node_modules/@sap/cds-mtxs/srv/cf/saas-provisioning-service.js +1 -1
- package/node_modules/@sap/cds-mtxs/srv/cf/sms-provisioning-service.js +23 -3
- package/node_modules/@sap/cds-mtxs/srv/extensibility/compat/update.js +15 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/annotations.js +2 -2
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/config.js +2 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/index.js +2 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/protected-namespace.js +45 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +30 -16
- package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +49 -10
- package/node_modules/@sap/cds-mtxs/srv/model-provider.js +28 -4
- package/node_modules/@sap/cds-mtxs/srv/plugins/common.js +19 -12
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/ctnr-mgr-base.js +1 -1
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/hdi-ctnr-mgr.js +6 -3
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/serverless.js +271 -0
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +7 -10
- package/node_modules/@sap/cds-mtxs/srv/worker/job-worker-pool.js +212 -0
- package/node_modules/@sap/cds-mtxs/srv/worker/job-worker.js +61 -0
- package/node_modules/@sap/xsenv/CHANGELOG.md +7 -0
- package/node_modules/@sap/xsenv/package.json +10 -7
- package/node_modules/body-parser/node_modules/content-type/README.md +2 -0
- package/node_modules/body-parser/node_modules/content-type/dist/index.d.ts +20 -0
- package/node_modules/body-parser/node_modules/content-type/dist/index.js +20 -14
- package/node_modules/body-parser/node_modules/content-type/dist/index.js.map +1 -1
- package/node_modules/body-parser/node_modules/content-type/package.json +1 -1
- package/node_modules/hdb/cfg/VERSION +1 -1
- package/node_modules/hdb/lib/protocol/ClientInfo.js +16 -3
- package/node_modules/hdb/lib/protocol/Connection.js +1 -0
- package/node_modules/hdb/package.json +3 -2
- package/node_modules/negotiator/lib/accept.js +54 -0
- package/node_modules/negotiator/lib/charset.js +10 -34
- package/node_modules/negotiator/lib/encoding.js +9 -30
- package/node_modules/negotiator/lib/language.js +15 -36
- package/node_modules/negotiator/lib/mediaType.js +16 -134
- package/node_modules/negotiator/node_modules/content-type/LICENSE +22 -0
- package/node_modules/negotiator/node_modules/content-type/README.md +71 -0
- package/node_modules/negotiator/node_modules/content-type/dist/index.d.ts +46 -0
- package/node_modules/negotiator/node_modules/content-type/dist/index.js +176 -0
- package/node_modules/negotiator/node_modules/content-type/dist/index.js.map +1 -0
- package/node_modules/negotiator/node_modules/content-type/package.json +52 -0
- package/node_modules/negotiator/package.json +16 -10
- package/node_modules/qs/.github/THREAT_MODEL.md +3 -3
- package/node_modules/qs/CHANGELOG.md +15 -0
- package/node_modules/qs/README.md +19 -1
- package/node_modules/qs/dist/qs.js +21 -21
- package/node_modules/qs/eslint.config.mjs +1 -1
- package/node_modules/qs/lib/parse.js +3 -4
- package/node_modules/qs/lib/stringify.js +23 -8
- package/node_modules/qs/lib/utils.js +10 -3
- package/node_modules/qs/package.json +4 -4
- package/node_modules/qs/test/parse.js +192 -3
- package/node_modules/qs/test/stringify.js +317 -0
- package/node_modules/qs/test/utils.js +12 -0
- package/node_modules/type-is/node_modules/content-type/README.md +2 -0
- package/node_modules/type-is/node_modules/content-type/dist/index.d.ts +20 -0
- package/node_modules/type-is/node_modules/content-type/dist/index.js +20 -14
- package/node_modules/type-is/node_modules/content-type/dist/index.js.map +1 -1
- package/node_modules/type-is/node_modules/content-type/package.json +1 -1
- package/node_modules/ws/lib/permessage-deflate.js +3 -1
- package/node_modules/ws/package.json +1 -1
- package/package.json +2 -2
- package/bin/completion/scripts/cds-bash.sh +0 -20
- package/bin/completion/scripts/cds-fish.fish +0 -13
- package/bin/completion/scripts/cds-ps.ps1 +0 -19
- package/bin/completion/scripts/cds-zsh.sh +0 -13
- package/lib/deploy/index.js +0 -25
- package/lib/import/asyncapi/importAsyncAPI.js +0 -33
- package/lib/import/asyncapi/index.js +0 -18
- package/lib/import/asyncapi/parserContext.js +0 -454
- package/lib/import/asyncapi/utils.js +0 -49
- package/node_modules/@sap/cds/libx/_runtime/ucl/queries.js +0 -61
- package/node_modules/negotiator/HISTORY.md +0 -114
- /package/{lib → bin}/upgrade/context.js +0 -0
- /package/{lib → bin}/upgrade/migrations/cds10/migrations.json +0 -0
- /package/{lib → bin}/upgrade/preflight/cds-version.js +0 -0
- /package/{lib → bin}/upgrade/preflight/project-type.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/classification.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/detector.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/evaluator.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/reporter.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/setup.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/utils/detection.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/utils/files.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/utils/grep.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/utils/yaml.js +0 -0
- /package/{lib → bin}/upgrade/util/workspaces.js +0 -0
- /package/{lib → bin}/watch/esm-resolve-registration.js +0 -0
- /package/{lib → bin}/watch/livereload-server.js +0 -0
- /package/{lib → bin}/watch/node-watch.js +0 -0
- /package/{lib → bin}/watch/open.js +0 -0
- /package/{lib → bin}/watch/watcher.js +0 -0
- /package/node_modules/@sap/cds-mtxs/srv/worker/{pool.js → model-worker-pool.js} +0 -0
- /package/node_modules/@sap/cds-mtxs/srv/worker/{load.js → model-worker.js} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { transformAnnotationExpression, implicitAs } = require('../../model/csnUtils');
|
|
3
|
+
const { transformAnnotationExpression, implicitAs, isSimpleAnnoExprRef } = require('../../model/csnUtils');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Used during annotating of foreign keys.
|
|
@@ -13,10 +13,10 @@ const { transformAnnotationExpression, implicitAs } = require('../../model/csnUt
|
|
|
13
13
|
* @param {object} messageFunctions
|
|
14
14
|
* @param {CSN.Path} elementPath
|
|
15
15
|
*/
|
|
16
|
-
function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, elementPath) {
|
|
16
|
+
function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, elementPath, options) {
|
|
17
17
|
if (Array.isArray(generatedForeignKeys?.[0])) {
|
|
18
18
|
// ensure we are always called with an array of objects. TODO: Cleanup fk creation in for.effective to create array of objects
|
|
19
|
-
adaptAnnotationsRefs(remapToArrayOfObjects(generatedForeignKeys), csnUtils, { error }, elementPath);
|
|
19
|
+
adaptAnnotationsRefs(remapToArrayOfObjects(generatedForeignKeys), csnUtils, { error }, elementPath, options);
|
|
20
20
|
}
|
|
21
21
|
else {
|
|
22
22
|
const reportedErrorsForAnnoPath = {};
|
|
@@ -49,8 +49,14 @@ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, element
|
|
|
49
49
|
if (gfkForRef.length === 1) {
|
|
50
50
|
ref[0] = gfkForRef[0].prefix;
|
|
51
51
|
|
|
52
|
-
if (ctx?.annoExpr?.['='])
|
|
53
|
-
|
|
52
|
+
if (ctx?.annoExpr?.['=']) {
|
|
53
|
+
// TODO: Apply the same fix for OData once the OData behavior is aligned;
|
|
54
|
+
// when done, the `options` parameter can be removed from this function.
|
|
55
|
+
if (options?.transformation === 'effective')
|
|
56
|
+
ctx.annoExpr['='] = isSimpleAnnoExprRef(ctx.annoExpr) ? ctx.annoExpr.ref.join('.') : true;
|
|
57
|
+
else
|
|
58
|
+
ctx.annoExpr['='] = true;
|
|
59
|
+
}
|
|
54
60
|
}
|
|
55
61
|
else {
|
|
56
62
|
// check if the annotation reference points to a structure that has been expanded,
|
|
@@ -24,6 +24,9 @@ function translateAssocsToJoinsCSN(csn, options) {
|
|
|
24
24
|
const compileOptions = { ...options, $skipNameCheck: true };
|
|
25
25
|
delete compileOptions.csnFlavor;
|
|
26
26
|
|
|
27
|
+
// DO NOT DELETE the `require('fs').writeFileSync(...)` lines in this function!
|
|
28
|
+
// Uncomment them to see the CSN input and output for A2J
|
|
29
|
+
// require('fs').writeFileSync('./a2j_in.csn', JSON.stringify(csn,null,2))
|
|
27
30
|
const model = recompileX(csn, compileOptions);
|
|
28
31
|
timetrace.stop('A2J: Recompiling model');
|
|
29
32
|
timetrace.start('A2J: Translating associations to joins');
|
|
@@ -49,7 +52,9 @@ function translateAssocsToJoinsCSN(csn, options) {
|
|
|
49
52
|
deduplicateMessages( options.messages );
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
const newCsn = compactModel(model, compileOptions);
|
|
56
|
+
// require('fs').writeFileSync('./a2j_out.csn', JSON.stringify(newCsn,null,2))
|
|
57
|
+
return newCsn;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
function translateAssocsToJoins(model, inputOptions = {}) {
|
|
@@ -607,11 +612,10 @@ function translateAssocsToJoins(model, inputOptions = {}) {
|
|
|
607
612
|
args.push({
|
|
608
613
|
op: { val: '=' },
|
|
609
614
|
args: [ constructPathNode( [ srcAlias, prefixFK(assoc.$elementPrefix, assoc.$flatSrcFKs[i]) ] ),
|
|
610
|
-
constructPathNode( [ tgtAlias, assoc.$flatTgtFKs[i] ] ) ],
|
|
615
|
+
constructPathNode( [ tgtAlias, resolveTgtFKToAlias(assoc.$flatTgtFKs[i], tgtAlias) ] ) ],
|
|
611
616
|
});
|
|
612
617
|
}
|
|
613
|
-
|
|
614
|
-
return parenthesise((args.length > 1 ? { op: { val: 'and' }, args: [ ...args.map(parenthesise) ] } : args[0] ));
|
|
618
|
+
return parenthesise((args.length > 1 ? { op: { val: 'and' }, args } : args[0] ));
|
|
615
619
|
}
|
|
616
620
|
else if (assoc.on) {
|
|
617
621
|
if (env.assocStack === undefined) {
|
|
@@ -707,7 +711,7 @@ function translateAssocsToJoins(model, inputOptions = {}) {
|
|
|
707
711
|
|
|
708
712
|
function cloneOnCondExprStream(expr) {
|
|
709
713
|
const { args } = expr;
|
|
710
|
-
const result = { op: { val: expr.op.val }, args: [ ] };
|
|
714
|
+
const result = { op: { val: expr.op.val }, args: [ ], $parens: expr.$parens };
|
|
711
715
|
for (let i = 0; i < args.length; i++) {
|
|
712
716
|
const op = args[i].op?.val;
|
|
713
717
|
if (op === 'xpr' || op === 'ixpr' || op === 'nary') {
|
|
@@ -727,7 +731,7 @@ function translateAssocsToJoins(model, inputOptions = {}) {
|
|
|
727
731
|
args[i + 2].$check = false;
|
|
728
732
|
}
|
|
729
733
|
else {
|
|
730
|
-
result.args.push(createOnCondition(fwdAssoc, ...swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias)));
|
|
734
|
+
result.args.push(createOnCondition(backlinkOf(assoc, fwdAssoc), ...swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias)));
|
|
731
735
|
}
|
|
732
736
|
i += 2; // skip next two tokens and continue with loop
|
|
733
737
|
continue;
|
|
@@ -764,7 +768,7 @@ function translateAssocsToJoins(model, inputOptions = {}) {
|
|
|
764
768
|
return expr;
|
|
765
769
|
}
|
|
766
770
|
|
|
767
|
-
return createOnCondition(fwdAssoc, ...swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias));
|
|
771
|
+
return createOnCondition(backlinkOf(assoc, fwdAssoc), ...swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias));
|
|
768
772
|
}
|
|
769
773
|
|
|
770
774
|
// If this is an ordinary expression, clone it and mangle its arguments
|
|
@@ -804,7 +808,19 @@ function translateAssocsToJoins(model, inputOptions = {}) {
|
|
|
804
808
|
const assocSource = assoc._main;
|
|
805
809
|
if (assoc.kind === 'mixin' && (i > 0 || fwdTarget !== assocSource) && fwdOrigin.kind !== 'mixin') {
|
|
806
810
|
const tas = Object.values(env.lead.$tableAliases);
|
|
807
|
-
|
|
811
|
+
// The backlink association's target may be reached through a view
|
|
812
|
+
// over it, whose alias is not the target artifact itself; fall back
|
|
813
|
+
// to the alias whose `_origin` chain reaches the target.
|
|
814
|
+
let j = tas.findIndex(ta => ta._artifact === fwdOrigin.target._artifact);
|
|
815
|
+
if (j < 0) {
|
|
816
|
+
j = tas.findIndex((ta) => {
|
|
817
|
+
for (let a = ta._artifact; a; a = a._origin) {
|
|
818
|
+
if (a === fwdOrigin.target._artifact)
|
|
819
|
+
return true;
|
|
820
|
+
}
|
|
821
|
+
return false;
|
|
822
|
+
});
|
|
823
|
+
}
|
|
808
824
|
if (j >= 0 && tas[j].$QA) {
|
|
809
825
|
newTgtAlias.id = tas[j].$QA.name.id;
|
|
810
826
|
newTgtAlias._artifact = tas[j]._effectiveType;
|
|
@@ -1050,6 +1066,28 @@ function translateAssocsToJoins(model, inputOptions = {}) {
|
|
|
1050
1066
|
}
|
|
1051
1067
|
return undefined;
|
|
1052
1068
|
}
|
|
1069
|
+
|
|
1070
|
+
// Build the backlink ON from the renaming-aware `$backlink` rather than the
|
|
1071
|
+
// forward assoc's base-entity keys/on, if it is available.
|
|
1072
|
+
function backlinkOf( backlinkAssoc, fwdAssoc ) {
|
|
1073
|
+
const backlink = backlinkAssoc.$backlink;
|
|
1074
|
+
return backlink && (backlink.foreignKeys || backlink.on) ? backlink : fwdAssoc;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// Resolve the target foreign key to the alias of the target table.
|
|
1078
|
+
// If the target foreign key is not found in the scope of the target alias,
|
|
1079
|
+
// try to find it in the origin chain of the foreign key artifact.
|
|
1080
|
+
function resolveTgtFKToAlias( fk, tgtAlias ) {
|
|
1081
|
+
const env = tgtAlias && (tgtAlias._effectiveType || tgtAlias._artifact);
|
|
1082
|
+
const scope = env && env.elements;
|
|
1083
|
+
if (!scope || !fk || scope[fk.id] || !fk._artifact || !fk._artifact._origin)
|
|
1084
|
+
return fk;
|
|
1085
|
+
for (let o = fk._artifact._origin; o; o = o._origin) {
|
|
1086
|
+
if (o.name && scope[o.name.id])
|
|
1087
|
+
return { id: o.name.id, _artifact: fk._artifact, acc: fk.acc };
|
|
1088
|
+
}
|
|
1089
|
+
return fk;
|
|
1090
|
+
}
|
|
1053
1091
|
} // createOnCondition
|
|
1054
1092
|
} // createJoinQA
|
|
1055
1093
|
|
|
@@ -1706,9 +1744,9 @@ function translateAssocsToJoins(model, inputOptions = {}) {
|
|
|
1706
1744
|
return newObj;
|
|
1707
1745
|
}
|
|
1708
1746
|
|
|
1747
|
+
// to-csn now auto-wraps AND args that are OR-terms (see json/to-csn.js `case 'and'`),
|
|
1748
|
+
// so A2J no longer needs to pre-wrap sub-expressions to preserve SQL precedence.
|
|
1709
1749
|
function parenthesise(expr) {
|
|
1710
|
-
if (typeof expr === 'object' && expr.op && expr.args)
|
|
1711
|
-
setProp(expr, '$parens', [ expr.$location ]);
|
|
1712
1750
|
return expr;
|
|
1713
1751
|
}
|
|
1714
1752
|
}
|
|
@@ -6,6 +6,55 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
8
8
|
|
|
9
|
+
## Version 4.1.0 - 2026-09-04
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Beta: Multitenant provisioning now supports HANA Cloud serverless, resolving one HDI container per tenant through the serverless Tenant API.
|
|
14
|
+
- Beta: The MTX job service can now use native javascript workers to make the MTX sidecar more responsive while jobs (e. g. tenant upgrade) are running.
|
|
15
|
+
Native workers can be enabled by `nativeWorkers` in the jobs configuration, e. g.
|
|
16
|
+
```jsonc
|
|
17
|
+
"cds.xt.SaasProvisioningService": {
|
|
18
|
+
"jobs": {
|
|
19
|
+
"nativeWorkers": true,
|
|
20
|
+
"workerSize": 3,
|
|
21
|
+
"clusterSize": 3
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
```
|
|
25
|
+
- With configuration `cds.requires['cds.xt.SaasProvisioning'].upgrade.skipAlreadyUpgraded: true` or `cds.requires['cds.xt.SmsProvisioning'].upgrade.skipAlreadyUpgraded: true`,
|
|
26
|
+
already upgraded tenants are skipped when running an upgrade of all tenants (`tenants: ['*']`). Tenants are considered as already upgraded if the last upgrade was run after the
|
|
27
|
+
last deployment of the application.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- `SmsProvisioningService` certificate check is now better aligned with the Java implementation.
|
|
32
|
+
- Extension validation checks have been refined.
|
|
33
|
+
- `POST /-/cds/model-provider/getEdmx` now checks the requested service name more exactly.
|
|
34
|
+
- Callback url for Saas Registry and Subscription Manager is now determined in a more robust way.
|
|
35
|
+
|
|
36
|
+
## Version 4.0.4 - 2026-08-21
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- Version bump
|
|
41
|
+
|
|
42
|
+
## Version 4.0.3 - 2026-08-21
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- `cds-mtx subscribe`, `unsubscribe`, and `upgrade` now take more than one tenant at once, e.g. `cds-mtx upgrade t1 t2` or `cds-mtx upgrade t1,t2`.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- Login endpoints renamed to `/-/cds/login/grant` and `/-/cds/login/grant-metadata` with redirects from former paths.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- HANA credential health-check now detects the available driver via `require.resolve()` instead of reading `package.json`, fixing `MODULE_NOT_FOUND` for apps that follow the "Plug & Play" guidance and don't declare `hdb` or `@sap/hana-client` directly.
|
|
55
|
+
- Tenant upgrade now reports a clear per-tenant `422` error when a stored extension is incompatible with the changed base model, instead of aborting with an uncaught compilation error.
|
|
56
|
+
- `cds-mtx-migrate` now runs more robust with uncommon model configurations.
|
|
57
|
+
|
|
9
58
|
## Version 4.0.2 - 2026-08-07
|
|
10
59
|
|
|
11
60
|
### Fixed
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/* eslint-disable no-console */
|
|
3
3
|
|
|
4
|
+
const cds = require('@sap/cds')
|
|
5
|
+
const { _import, isfile, local, path } = cds.utils
|
|
6
|
+
|
|
4
7
|
async function run(param, options) {
|
|
5
8
|
const { dry, genTenantList, syncTenantList, cleanup, directory, tag, tagRule } = options
|
|
6
9
|
const skipVerification = options['skip-verification'] || options.skipVerification
|
|
@@ -26,7 +29,6 @@ async function run(param, options) {
|
|
|
26
29
|
_handleError(`The migration from @sap/cds-mtx to @sap/cds-mtxs has been removed. Only --init-tenant-list and --sync-tenant-list are still supported.`)
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
const cds = require('@sap/cds')
|
|
30
32
|
const { syncTenantList: synchronizeTenantList, _hasMtEnv } = require('../lib/migration/migration')
|
|
31
33
|
|
|
32
34
|
async function cds_mtx_migrate(tenants, options) {
|
|
@@ -45,11 +47,20 @@ async function run(param, options) {
|
|
|
45
47
|
services.push('@sap/cds-mtxs/srv/extensibility-service')
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
await
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
await _local_server_js()
|
|
51
|
+
cds.emit ('bootstrap')
|
|
52
|
+
const model = await cds.load([...services, '@sap/cds-mtxs/db/extensions'])
|
|
53
|
+
cds.model = cds.compile.for.nodejs(model)
|
|
54
|
+
const d = 'cds.xt.DeploymentService'
|
|
55
|
+
const unserved = !cds.services[d]
|
|
56
|
+
if (unserved) {
|
|
57
|
+
await cds.serve(services)
|
|
58
|
+
}
|
|
59
|
+
await cds.plugins
|
|
60
|
+
|
|
61
|
+
await cds.emit('served', cds.services)
|
|
51
62
|
|
|
52
|
-
|
|
63
|
+
cds.env.requires.multitenancy ??= true // needed for current sidecar config that only enables services
|
|
53
64
|
|
|
54
65
|
if (genTenantList) {
|
|
55
66
|
const tenantListResult = await synchronizeTenantList(tenantList, options)
|
|
@@ -73,6 +84,16 @@ async function run(param, options) {
|
|
|
73
84
|
await cds_mtx_migrate(param, options).catch(console.error)
|
|
74
85
|
}
|
|
75
86
|
|
|
87
|
+
// copied from cds.serve
|
|
88
|
+
async function _local_server_js() {
|
|
89
|
+
const _local = file => isfile(file) || isfile (path.join(cds.env.folders.srv,file))
|
|
90
|
+
const cli_js = process.env.CDS_TYPESCRIPT && _local('cli.ts') || _local('cli.js')
|
|
91
|
+
if (cli_js) {
|
|
92
|
+
console.log ('[cds] - loading server from', { file: local(cli_js) })
|
|
93
|
+
await _import(cli_js)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
76
97
|
function getParameterValue(options, parameter) {
|
|
77
98
|
const index = options.indexOf(parameter)
|
|
78
99
|
const value = (index > -1) ? options[index + 1] : undefined
|
|
@@ -10,15 +10,16 @@ const SUPPORTED = ['subscribe', 'unsubscribe', 'upgrade']
|
|
|
10
10
|
// This function must only be changed in a backward compatible way. It is used by DwC.
|
|
11
11
|
async function cds_mtx(cmd, tenant, body) {
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
const tenants = (Array.isArray(tenant) ? tenant : [tenant]).flatMap(t => t.split(','))
|
|
14
|
+
const one = tenants.length === 1 ? tenants[0] : undefined
|
|
15
|
+
|
|
16
|
+
let connectedTenants = []
|
|
14
17
|
|
|
15
18
|
try {
|
|
16
19
|
if (!cmd) return _usage()
|
|
17
20
|
if (!SUPPORTED.includes(cmd)) return _usage(`Unknown command ${cmd}.`)
|
|
18
21
|
if (!_hasMtEnv()) return _handleError(`cds-mtx ${cmd} operation can only be run inside a multitenant application environment using @sap/cds-mtxs.`)
|
|
19
|
-
if (!
|
|
20
|
-
if (/.*,.*/.test(tenant)) return _handleError(`List of tenants not supported: ${tenant}`)
|
|
21
|
-
// parse body and handle error
|
|
22
|
+
if (!tenants.length) return _handleError(`Please provide a tenant: cds-mtx ${cmd} <tenant>`)
|
|
22
23
|
let parsedMetadata
|
|
23
24
|
if (body) {
|
|
24
25
|
try {
|
|
@@ -46,18 +47,27 @@ async function cds_mtx(cmd, tenant, body) {
|
|
|
46
47
|
await cds.connect.to('db')
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
if (
|
|
50
|
+
if (tenants.includes('*')) {
|
|
50
51
|
if (cmd !== 'upgrade') return _handleError('"*" only supported for upgrade command')
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
if (tenants.length > 1) return _handleError('"*" cannot be combined with other tenants')
|
|
53
|
+
const all = await sps.read('tenant')
|
|
54
|
+
connectedTenants = all.map(t => t.subscribedTenantId)
|
|
53
55
|
await sps.tx({ user: new cds.User.Privileged() }, async (tx) => {
|
|
54
|
-
await tx.upgrade([
|
|
56
|
+
await tx.upgrade(['*'], parsedMetadata)
|
|
55
57
|
})
|
|
56
58
|
return
|
|
57
|
-
} else if (
|
|
59
|
+
} else if (one === 't0') {
|
|
58
60
|
return
|
|
59
61
|
}
|
|
60
|
-
|
|
62
|
+
connectedTenants = tenants
|
|
63
|
+
|
|
64
|
+
if (cmd === 'upgrade') {
|
|
65
|
+
await sps.tx({ user: new cds.User.Privileged() }, async (tx) => {
|
|
66
|
+
await tx.upgrade(tenants, parsedMetadata)
|
|
67
|
+
})
|
|
68
|
+
} else {
|
|
69
|
+
for (const t of tenants) await ds[cmd](t, parsedMetadata, parsedMetadata)
|
|
70
|
+
}
|
|
61
71
|
} catch(e) {
|
|
62
72
|
if (isCli) {
|
|
63
73
|
console.error(e.message)
|
|
@@ -68,6 +78,9 @@ async function cds_mtx(cmd, tenant, body) {
|
|
|
68
78
|
cds.db.disconnect(cds.env.requires.multitenancy.t0 ?? 't0')
|
|
69
79
|
for (const t of connectedTenants) cds.db.disconnect(t)
|
|
70
80
|
}
|
|
81
|
+
if (isCli) {
|
|
82
|
+
await cds.exit()
|
|
83
|
+
}
|
|
71
84
|
}
|
|
72
85
|
}
|
|
73
86
|
|
|
@@ -109,27 +122,35 @@ async function _usage(message = '') {
|
|
|
109
122
|
USAGE
|
|
110
123
|
|
|
111
124
|
cds-mtx <command> <tenant> [--body <json>]
|
|
125
|
+
cds-mtx <command> <tenant1> [<tenant2> ...] [--body <json>]
|
|
126
|
+
cds-mtx <command> <t1,t2,...> [--body <json>]
|
|
112
127
|
|
|
113
128
|
COMMANDS
|
|
114
129
|
|
|
115
|
-
subscribe subscribe
|
|
116
|
-
unsubscribe unsubscribe
|
|
117
|
-
upgrade upgrade
|
|
130
|
+
subscribe subscribe one or more tenants
|
|
131
|
+
unsubscribe unsubscribe one or more tenants
|
|
132
|
+
upgrade upgrade one or more tenants
|
|
118
133
|
|
|
119
134
|
EXAMPLES
|
|
120
135
|
|
|
121
136
|
cds-mtx subscribe t1
|
|
137
|
+
cds-mtx subscribe t1 t2 t3
|
|
122
138
|
cds-mtx subscribe t1 --body '{ "_": { "hdi": { "create": { "database_id": "<database id>" } } } }'
|
|
123
139
|
cds-mtx unsubscribe t1
|
|
124
140
|
cds-mtx upgrade t1
|
|
141
|
+
cds-mtx upgrade t1 t2 t3
|
|
142
|
+
cds-mtx upgrade t1,t2,t3
|
|
125
143
|
cds-mtx upgrade "*"
|
|
126
144
|
`
|
|
127
145
|
)
|
|
128
146
|
}
|
|
129
147
|
|
|
130
148
|
if (isCli) {
|
|
131
|
-
const [, , cmd,
|
|
132
|
-
|
|
133
|
-
|
|
149
|
+
const [, , cmd, ...rest] = process.argv
|
|
150
|
+
const bodyIdx = rest.indexOf('--body')
|
|
151
|
+
const tenants = bodyIdx === -1 ? rest : rest.slice(0, bodyIdx)
|
|
152
|
+
const body = bodyIdx !== -1 ? rest[bodyIdx + 1] : undefined
|
|
153
|
+
if (bodyIdx !== -1 && !body) _usage(`Missing value for --body`)
|
|
154
|
+
;(async () => await cds_mtx(cmd, tenants, body))()
|
|
134
155
|
}
|
|
135
156
|
module.exports = { cds_mtx }
|
|
@@ -89,15 +89,30 @@ const t0_ = async (query) => retry(async () => {
|
|
|
89
89
|
return cds.tx({ tenant: t0 }, tx => { tx.model = _t0Csn; return tx.run(query) })
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
const SUBDOMAIN_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i
|
|
93
|
+
|
|
94
|
+
function authUrl(baseUrl, subdomain, pathname) {
|
|
95
|
+
const u = new URL(baseUrl)
|
|
96
|
+
if (subdomain) {
|
|
97
|
+
if (typeof subdomain !== 'string' || !SUBDOMAIN_RE.test(subdomain)) {
|
|
98
|
+
throw cds.error('invalid subdomain', { status: 400 })
|
|
99
|
+
}
|
|
100
|
+
const dot = u.hostname.indexOf('.')
|
|
101
|
+
u.hostname = subdomain + (dot >= 0 ? u.hostname.slice(dot) : '')
|
|
102
|
+
}
|
|
103
|
+
u.pathname = pathname
|
|
104
|
+
return u
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function token(credentials, { query = {}, form = {}, subdomain, path = '/oauth/token' } = {}) {
|
|
93
108
|
const { clientid, clientsecret, certurl, url, certificate, key } = credentials ?? {}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
u.pathname = '/oauth/token'
|
|
109
|
+
if (!form.grant_type) throw cds.error('grant_type is required', { status: 400 })
|
|
110
|
+
const u = authUrl(certurl || url, subdomain, path)
|
|
97
111
|
Object.entries(query).forEach(([k, v]) => u.searchParams.append(k, v))
|
|
98
|
-
const body = new URLSearchParams({
|
|
112
|
+
const body = new URLSearchParams({ client_id: clientid, ...form })
|
|
99
113
|
const headers = { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' }
|
|
100
114
|
let raw
|
|
115
|
+
const _fail = (statusCode, respBody) => new Error(`token request to ${u.origin}${u.pathname} for client ${clientid} failed (${statusCode}): ${respBody}`, { status: statusCode })
|
|
101
116
|
if (certificate) {
|
|
102
117
|
raw = await new Promise((resolve, reject) => {
|
|
103
118
|
const req = require('node:https').request({
|
|
@@ -112,7 +127,7 @@ async function token(credentials, { query = {}, form = {}, subdomain } = {}) {
|
|
|
112
127
|
res.on('data', x => chunks.push(x))
|
|
113
128
|
res.on('end', () => {
|
|
114
129
|
if (res.statusCode >= 200 && res.statusCode < 300) resolve(Buffer.concat(chunks).toString())
|
|
115
|
-
else reject(
|
|
130
|
+
else reject(_fail(res.statusCode, Buffer.concat(chunks).toString()))
|
|
116
131
|
})
|
|
117
132
|
})
|
|
118
133
|
req.on('error', reject)
|
|
@@ -162,7 +177,7 @@ async function fetchResiliently(url, req, { retriesLeft = maxRetries, maxRetryAf
|
|
|
162
177
|
const data = contentType.includes('application/json') ? await res.json() : await res.text()
|
|
163
178
|
|
|
164
179
|
if (!res.ok) {
|
|
165
|
-
let msg = [status, statusText, data?.message, typeof data?.error === 'object' ? JSON.stringify(data.error) : data?.error, data?.description, data?.cause].filter(Boolean).join(' – ')
|
|
180
|
+
let msg = [status, statusText, data?.message, typeof data?.error === 'object' ? JSON.stringify(data.error) : data?.error, data?.description, data?.cause, typeof data?.details === 'object' ? JSON.stringify(data.details) : data?.details].filter(Boolean).join(' – ')
|
|
166
181
|
if (res.status === 429 && res.headers.get('retry-after')) msg += ` – retry after ${res.headers.get('retry-after')}`
|
|
167
182
|
throw Object.assign(new Error(msg), { status, statusText, headers, code: data?.error?.code })
|
|
168
183
|
}
|
|
@@ -249,6 +264,7 @@ module.exports = {
|
|
|
249
264
|
readData,
|
|
250
265
|
readDataCsv,
|
|
251
266
|
token,
|
|
267
|
+
authUrl,
|
|
252
268
|
fetchResiliently,
|
|
253
269
|
getAppId,
|
|
254
270
|
deepMerge
|
|
@@ -5,6 +5,7 @@ const { fetchResiliently, token } = require('../../lib/utils')
|
|
|
5
5
|
const DeploymentService = 'cds.xt.DeploymentService'
|
|
6
6
|
const JobsService = 'cds.xt.JobsService'
|
|
7
7
|
const Tenants = 'cds.xt.Tenants'
|
|
8
|
+
const { SKIPPED_DB } = require('../jobs-service')
|
|
8
9
|
|
|
9
10
|
// we need to run in multitenancy mode for t0 ops
|
|
10
11
|
if (!cds.env.requires.multitenancy) cds.env.requires.multitenancy = true
|
|
@@ -112,9 +113,9 @@ module.exports = class ProvisioningService extends cds.ApplicationService {
|
|
|
112
113
|
)).map(({ ID, metadata, createdAt, modifiedAt }) => ({ subscribedTenantId: ID, ...this._toMetadataJson(metadata, ID), createdAt, modifiedAt }))
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
async _getTenants() {
|
|
116
|
+
async _getTenants(options) {
|
|
116
117
|
const ds = await cds.connect.to(DeploymentService)
|
|
117
|
-
const all = await ds.getTenants()
|
|
118
|
+
const all = await ds.getTenants(options)
|
|
118
119
|
if (cds.env.requires[this.name]?.upgrade?.ignoreNonExistingContainers) {
|
|
119
120
|
const containers = await ds.getContainers()
|
|
120
121
|
return all.filter(t => containers.includes(t))
|
|
@@ -129,9 +130,37 @@ module.exports = class ProvisioningService extends cds.ApplicationService {
|
|
|
129
130
|
const all = tenantIds.includes('*')
|
|
130
131
|
const sharedGenDir = !main.requires.extensibility
|
|
131
132
|
if (sharedGenDir) options.skipResources ??= sharedGenDir
|
|
132
|
-
|
|
133
|
+
|
|
134
|
+
// Merge static upgrade config with dynamic options (dynamic takes precedence)
|
|
135
|
+
const upgradeConfig = cds.env.requires[this.name]?.upgrade
|
|
136
|
+
if (upgradeConfig) {
|
|
137
|
+
options.upgrade = cds.utils.merge({ ...upgradeConfig }, options.upgrade)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Determine skipAfter: from explicit option or derived from package.json mtime
|
|
141
|
+
let skipAfter = options?.upgrade?.skipAfter
|
|
142
|
+
if (!skipAfter && options?.upgrade?.skipAlreadyUpgraded) {
|
|
143
|
+
const { stat } = require('fs/promises')
|
|
144
|
+
const { mtime } = await stat(cds.root + '/package.json')
|
|
145
|
+
skipAfter = mtime.toISOString()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const tenantsOptions = skipAfter ? { skipAfter } : undefined
|
|
149
|
+
let tenants
|
|
150
|
+
let skipped = []
|
|
151
|
+
if (all) {
|
|
152
|
+
tenants = await this._getTenants(tenantsOptions)
|
|
153
|
+
} else if (skipAfter) {
|
|
154
|
+
const upgradeable = await this._getTenants(tenantsOptions)
|
|
155
|
+
tenants = tenantIds.filter(t => upgradeable.includes(t))
|
|
156
|
+
// Requested tenants filtered out by skipAfter are considered already upgraded.
|
|
157
|
+
skipped = tenantIds.filter(t => !upgradeable.includes(t))
|
|
158
|
+
} else {
|
|
159
|
+
tenants = tenantIds
|
|
160
|
+
}
|
|
161
|
+
|
|
133
162
|
const { isSync } = this._parseHeaders(cds.context.http?.req.headers)
|
|
134
|
-
if (!tenants.length && isSync) return
|
|
163
|
+
if (!tenants.length && !skipped.length && isSync) return
|
|
135
164
|
if (sharedGenDir && cds.requires.db.kind === 'hana') { // REVISIT: Ideally part of HANA plugin
|
|
136
165
|
const { resources4, csvs4, directory4 } = require('../plugins/hana')
|
|
137
166
|
const out = await directory4('base', true)
|
|
@@ -141,13 +170,26 @@ module.exports = class ProvisioningService extends cds.ApplicationService {
|
|
|
141
170
|
const { 'cds.xt.DeploymentService': ds } = cds.services
|
|
142
171
|
const js = await cds.connect.to(JobsService)
|
|
143
172
|
const { clusterSize = 3 } = js._config()
|
|
144
|
-
const dbToTenants = (clusterSize > 1 && ds) ? await ds.tenantsByDb(tenants) : [new Set(tenants)]
|
|
145
|
-
|
|
173
|
+
const dbToTenants = (tenants.length && clusterSize > 1 && ds) ? await ds.tenantsByDb(tenants) : [new Set(tenants)]
|
|
174
|
+
// Skipped tenants go into a separate pseudo-db cluster so JobsService records them (and their
|
|
175
|
+
// tasks) as FINISHED without upgrading.
|
|
176
|
+
let clusters = dbToTenants
|
|
177
|
+
if (skipped.length) {
|
|
178
|
+
// Normalize array-form to a db-id-keyed object so skipped tenants get their own cluster.
|
|
179
|
+
clusters = { ...dbToTenants }
|
|
180
|
+
// Drop skipped tenants from regular clusters (some tenantsByDb impls ignore the tenant list).
|
|
181
|
+
const skippedSet = new Set(skipped)
|
|
182
|
+
for (const db of Object.keys(clusters)) {
|
|
183
|
+
clusters[db] = new Set([...clusters[db]].filter(t => tenants.includes(t) && !skippedSet.has(t)))
|
|
184
|
+
}
|
|
185
|
+
clusters[SKIPPED_DB] = skippedSet
|
|
186
|
+
}
|
|
187
|
+
LOG.info('upgrading', { tenants, skipped })
|
|
146
188
|
|
|
147
189
|
|
|
148
190
|
return await new Promise((resolve, reject) => {
|
|
149
191
|
const tx = js.tx({ tenant: cds.context.tenant, user: new cds.User.Privileged() })
|
|
150
|
-
const job = tx.enqueue(DeploymentService, 'upgrade',
|
|
192
|
+
const job = tx.enqueue(DeploymentService, 'upgrade', clusters, { options }, async error => {
|
|
151
193
|
if (error) {
|
|
152
194
|
await this._sendCallback('FAILED', `Tenant upgrade failed with error '${error}'`)
|
|
153
195
|
if (isSync) reject(new Error(error))
|
|
@@ -291,7 +333,7 @@ module.exports = class ProvisioningService extends cds.ApplicationService {
|
|
|
291
333
|
async _token() {
|
|
292
334
|
const serviceName = 'cds.xt.' + this.constructor.name
|
|
293
335
|
const cred = this._getCredentials()
|
|
294
|
-
const raw = await token(cred, { query: { response_type: 'token' } })
|
|
336
|
+
const raw = await token(cred, { query: { response_type: 'token' }, form: { grant_type: 'client_credentials' } })
|
|
295
337
|
const { access_token } = JSON.parse(raw)
|
|
296
338
|
if (!access_token) cds.error(`could not get token for ${serviceName}: token is empty`)
|
|
297
339
|
return access_token
|
|
@@ -303,4 +345,17 @@ module.exports = class ProvisioningService extends cds.ApplicationService {
|
|
|
303
345
|
const cred = s?.credentials ?? multitenancy?.credentials ?? {}
|
|
304
346
|
return cred
|
|
305
347
|
}
|
|
348
|
+
|
|
349
|
+
_resolveCallbackUrl(callbackPath, baseUrl) {
|
|
350
|
+
if (!callbackPath || !baseUrl) return undefined
|
|
351
|
+
if (typeof callbackPath !== 'string' || !/^\/(?![/\\])/.test(callbackPath) || callbackPath.includes('\\')) {
|
|
352
|
+
throw cds.error(`Invalid callback path: ${callbackPath}`, { status: 400 })
|
|
353
|
+
}
|
|
354
|
+
const base = new URL(baseUrl)
|
|
355
|
+
const resolved = new URL(callbackPath, base)
|
|
356
|
+
if (resolved.origin !== base.origin) {
|
|
357
|
+
throw cds.error(`Invalid callback path: ${callbackPath}`, { status: 400 })
|
|
358
|
+
}
|
|
359
|
+
return resolved.toString()
|
|
360
|
+
}
|
|
306
361
|
}
|
|
@@ -28,7 +28,7 @@ module.exports = class SaasProvisioningService extends require('./abstract-provi
|
|
|
28
28
|
_parseHeaders(headers) {
|
|
29
29
|
const { prefer, status_callback, mtx_status_callback } = headers ?? {}
|
|
30
30
|
const { saas_registry_url } = this._getCredentials()
|
|
31
|
-
const callbackUrl = mtx_status_callback ?? (status_callback
|
|
31
|
+
const callbackUrl = mtx_status_callback ?? this._resolveCallbackUrl(status_callback, saas_registry_url)
|
|
32
32
|
return {
|
|
33
33
|
callbackUrl,
|
|
34
34
|
isCustomCallback: !!mtx_status_callback,
|
|
@@ -53,7 +53,7 @@ module.exports = class SmsProvisioningService extends require('./abstract-provis
|
|
|
53
53
|
_parseHeaders(headers) {
|
|
54
54
|
const { prefer, status_callback } = headers ?? {}
|
|
55
55
|
const { subscription_manager_url } = this._getCredentials()
|
|
56
|
-
const callbackUrl = (status_callback
|
|
56
|
+
const callbackUrl = this._resolveCallbackUrl(status_callback, subscription_manager_url)
|
|
57
57
|
return {
|
|
58
58
|
callbackUrl,
|
|
59
59
|
isSync: !(prefer?.includes('respond-async') || callbackUrl)
|
|
@@ -102,11 +102,31 @@ module.exports = class SmsProvisioningService extends require('./abstract-provis
|
|
|
102
102
|
const isIssuerValid = isValid(callback_certificate_issuer, cert.issuer)
|
|
103
103
|
const isSubjectValid = isValid(callback_certificate_subject, cert.subject)
|
|
104
104
|
|
|
105
|
+
function parseDN(dn) {
|
|
106
|
+
// Node's X509Certificate.issuer/subject uses newline as RDN separator.
|
|
107
|
+
// Each line is a single "attribute=value" pair. Multiple lines with the
|
|
108
|
+
// same attribute (e.g. multiple OU= entries) are all collected into an array.
|
|
109
|
+
const fields = {}
|
|
110
|
+
for (const line of dn.split('\n')) {
|
|
111
|
+
const eq = line.indexOf('=')
|
|
112
|
+
if (eq === -1) continue
|
|
113
|
+
const attr = line.slice(0, eq).trim().toUpperCase()
|
|
114
|
+
const val = line.slice(eq + 1).trim()
|
|
115
|
+
if (fields[attr]) fields[attr].push(val)
|
|
116
|
+
else fields[attr] = [val]
|
|
117
|
+
}
|
|
118
|
+
return fields
|
|
119
|
+
}
|
|
120
|
+
|
|
105
121
|
function isValid(expected, fromCert) {
|
|
122
|
+
const certFields = parseDN(fromCert)
|
|
106
123
|
return Object.entries(JSON.parse(expected)).every(([k, v]) => {
|
|
107
124
|
if (v === '*') return true
|
|
108
|
-
|
|
109
|
-
|
|
125
|
+
const certVals = certFields[k.toUpperCase()] ?? []
|
|
126
|
+
// For array expected values every element must be present in the cert.
|
|
127
|
+
// For a scalar expected value the cert must contain that exact value.
|
|
128
|
+
if (Array.isArray(v)) return v.every(allowed => certVals.includes(allowed))
|
|
129
|
+
else return certVals.includes(v)
|
|
110
130
|
})
|
|
111
131
|
}
|
|
112
132
|
|