@sap/cds-dk 10.0.7 → 10.1.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/CHANGELOG.md +94 -0
- package/bin/add.js +7 -23
- package/bin/bind.js +10 -0
- package/bin/build.js +20 -34
- package/bin/cds.js +1 -1
- package/bin/compile/index.js +15 -41
- package/bin/completion/completionFs.js +11 -8
- package/bin/completion/index.js +592 -80
- package/bin/completion/scripts/cds.fish +568 -0
- package/bin/completion/scripts/cds.ps1 +329 -0
- package/bin/completion/scripts/cds.sh +261 -0
- package/bin/completion/scripts/cds.zsh +230 -0
- package/bin/completion/templates/cds-tmpl.fish +65 -0
- package/bin/completion/templates/cds-tmpl.ps1 +208 -0
- package/bin/completion/templates/cds-tmpl.sh +93 -0
- package/bin/completion/templates/cds-tmpl.zsh +74 -0
- package/bin/deploy/index.js +7 -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/export.js +35 -5
- package/bin/help/export.md +3 -0
- package/bin/help.js +1 -5
- package/bin/import.js +6 -0
- package/bin/init.js +5 -24
- package/bin/inspect.js +6 -0
- package/bin/login.js +26 -20
- package/bin/pull.js +19 -6
- package/bin/push.js +19 -6
- package/bin/up.js +14 -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 +8 -4
- package/bin/version.js +10 -1
- package/{lib → bin}/watch/hot-reload.js +1 -1
- package/bin/{watch.js → watch/index.js} +17 -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/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 +114 -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/@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 +60 -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 +41 -15
- 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 +2 -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 +3 -0
- package/node_modules/@sap/cds-compiler/lib/transform/db/associations.js +8 -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/effective/annotations.js +40 -0
- package/node_modules/@sap/cds-compiler/lib/transform/effective/main.js +1 -0
- package/node_modules/@sap/cds-compiler/lib/transform/forRelationalDB.js +6 -1
- package/node_modules/@sap/cds-compiler/lib/transform/translateAssocsToJoins.js +19 -8
- package/node_modules/@sap/cds-compiler/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/CHANGELOG.md +15 -0
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx.js +34 -16
- package/node_modules/@sap/cds-mtxs/lib/utils.js +19 -4
- package/node_modules/@sap/cds-mtxs/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/srv/cf/abstract-provisioning-service.js +1 -1
- package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +22 -13
- package/node_modules/@sap/cds-mtxs/srv/model-provider.js +17 -2
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/ctnr-mgr-base.js +1 -1
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +4 -8
- 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 +3 -3
- 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/bin/completion/index.js
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
1
2
|
const path = require('node:path');
|
|
2
3
|
|
|
3
4
|
const cds = require('../../bin/cds');
|
|
4
5
|
const { bold } = require('../../lib/util/term');
|
|
6
|
+
const completionFs = require('./completionFs');
|
|
7
|
+
const completionSetup = require('../../lib/init/template/completion/completionSetup');
|
|
5
8
|
|
|
9
|
+
// Sentinel factories — returned by staticCompletions() to mark special argument types.
|
|
10
|
+
const FILE = (exts) => exts ? { type: ':file', ext: exts.split(',').map(e => e.trim()) } : ':file';
|
|
11
|
+
const DIR = () => ':dir';
|
|
12
|
+
const COMMA = (values) => ({ type: ':comma', values });
|
|
6
13
|
|
|
14
|
+
// The '_' key in a staticCompletions map marks the default positional argument.
|
|
15
|
+
const POSITIONAL_KEY = '_';
|
|
16
|
+
|
|
17
|
+
const isFileSentinel = s => s === ':file' || s === ':dir' || (s?.type === ':file' && Array.isArray(s.ext));
|
|
18
|
+
const fileRegex = s => s.ext ? new RegExp(`\\.(${s.ext.join('|')})$`) : null;
|
|
19
|
+
const filePs1 = s => s.ext ? `':file:${s.ext.join(',')}'` : `'${s}'`;
|
|
7
20
|
|
|
8
21
|
module.exports = Object.assign(completion, {
|
|
9
|
-
|
|
22
|
+
staticCompletions: () => ({ '--shell': completionSetup.supportedShells }),
|
|
23
|
+
FILE,
|
|
24
|
+
DIR,
|
|
25
|
+
COMMA,
|
|
10
26
|
options: ['--shell', '--curr', '--line', '--prev'],
|
|
11
27
|
shortcuts: ['-s'],
|
|
12
|
-
flags: ['--add', '--profile', '--remove'],
|
|
28
|
+
flags: ['--add', '--profile', '--generate', '--remove'],
|
|
13
29
|
help: `
|
|
14
30
|
# SYNOPSIS
|
|
15
31
|
|
|
@@ -41,6 +57,13 @@ module.exports = Object.assign(completion, {
|
|
|
41
57
|
*--remove*
|
|
42
58
|
Remove shell completion for cds commands (see examples).
|
|
43
59
|
|
|
60
|
+
*--profile* _(deprecated)_
|
|
61
|
+
Print the path to the completion script for the given shell.
|
|
62
|
+
|
|
63
|
+
*--generate*
|
|
64
|
+
Generate a static completion script for the given shell and print it to stdout.
|
|
65
|
+
Used by 'npm run build:completion' to pre-build the bundled cache files.
|
|
66
|
+
|
|
44
67
|
*--shell* <bash | fish | gitbash | ps | zsh>
|
|
45
68
|
Force the shell completion code to be added to the correct shell profile file.
|
|
46
69
|
bash: macOS, Linux, WSL
|
|
@@ -65,22 +88,13 @@ module.exports = Object.assign(completion, {
|
|
|
65
88
|
function getScriptFile(shell) {
|
|
66
89
|
switch (shell) {
|
|
67
90
|
case 'bash':
|
|
68
|
-
case 'gitbash':
|
|
69
|
-
|
|
70
|
-
case '
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
case 'zsh':
|
|
75
|
-
return 'cds-zsh.sh';
|
|
76
|
-
case '':
|
|
77
|
-
case undefined:
|
|
78
|
-
case null:
|
|
79
|
-
throw 'Internal error: option --shell is missing';
|
|
80
|
-
default: {
|
|
81
|
-
const completionSetup = require('../../lib/init/template/completion/completionSetup');
|
|
91
|
+
case 'gitbash': return 'cds.sh';
|
|
92
|
+
case 'fish': return 'cds.fish';
|
|
93
|
+
case 'ps': return 'cds.ps1';
|
|
94
|
+
case 'zsh': return 'cds.zsh';
|
|
95
|
+
default:
|
|
96
|
+
if (!shell) throw 'Internal error: option --shell is missing';
|
|
82
97
|
throw `Internal error: unsupported shell: ${shell}, supported shells are ${completionSetup.supportedShells.join(', ')}`;
|
|
83
|
-
}
|
|
84
98
|
}
|
|
85
99
|
}
|
|
86
100
|
|
|
@@ -90,7 +104,24 @@ async function completion(_argv, options) {
|
|
|
90
104
|
return console.log(path.join(__dirname, 'scripts', scriptFile));
|
|
91
105
|
}
|
|
92
106
|
|
|
93
|
-
|
|
107
|
+
if (options.generate) {
|
|
108
|
+
if (options.shell) {
|
|
109
|
+
const content = await generateStaticScript(options.shell);
|
|
110
|
+
return content ? process.stdout.write(content) : undefined;
|
|
111
|
+
}
|
|
112
|
+
const shells = { sh: 'bash', zsh: 'zsh', fish: 'fish', ps1: 'ps' };
|
|
113
|
+
const scriptsDir = path.join(__dirname, 'scripts');
|
|
114
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
115
|
+
for (const [ext, shell] of Object.entries(shells)) {
|
|
116
|
+
const content = await generateStaticScript(shell);
|
|
117
|
+
if (content) {
|
|
118
|
+
fs.writeFileSync(path.join(scriptsDir, `cds.${ext}`), content);
|
|
119
|
+
console.log(`Generated cds.${ext}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
94
125
|
if (options.add || options.remove) {
|
|
95
126
|
await completionSetup.setup(options.add, options);
|
|
96
127
|
return;
|
|
@@ -113,124 +144,605 @@ async function completion(_argv, options) {
|
|
|
113
144
|
throw `internal Error: no completion handler found for shell type: ${shell}, supported shells are ${completionSetup.supportedShells.join(', ')}`;
|
|
114
145
|
}
|
|
115
146
|
|
|
116
|
-
const currentWord
|
|
147
|
+
const currentWord = localOptions.curr?.trim().replace(/^'|'$/g, '');
|
|
117
148
|
const previousWord = localOptions.prev?.trim().replace(/^'|'$/g, '');
|
|
118
|
-
const line
|
|
119
|
-
const segments
|
|
120
|
-
const cdsCmd
|
|
149
|
+
const line = localOptions.line?.trim().replace(/^'|'$/g, '');
|
|
150
|
+
const segments = getSegments(line);
|
|
151
|
+
const cdsCmd = segments[1] || '';
|
|
121
152
|
|
|
122
153
|
const completions = await getCompletions(cdsCmd, previousWord, currentWord, segments);
|
|
123
154
|
reply(completions);
|
|
124
155
|
}
|
|
125
156
|
|
|
126
|
-
async function
|
|
127
|
-
const
|
|
128
|
-
|
|
157
|
+
async function buildCompletionMap() {
|
|
158
|
+
const commands = cds.cmds ?? [];
|
|
159
|
+
const commandMap = {};
|
|
160
|
+
for (const cmd of commands) {
|
|
161
|
+
try {
|
|
162
|
+
const obj = cds.load(cmd);
|
|
163
|
+
const raw = await obj.staticCompletions?.() ?? [];
|
|
164
|
+
let positionals = [], optionValues = {}, fileCases = {}, commaCases = {};
|
|
165
|
+
if (Array.isArray(raw)) {
|
|
166
|
+
positionals = raw;
|
|
167
|
+
} else {
|
|
168
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
169
|
+
const resolved = await val;
|
|
170
|
+
if (resolved?.type === ':comma') {
|
|
171
|
+
commaCases[key] = await resolved.values;
|
|
172
|
+
} else if (isFileSentinel(resolved)) {
|
|
173
|
+
fileCases[key] = resolved;
|
|
174
|
+
} else {
|
|
175
|
+
optionValues[key] = resolved;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
commandMap[cmd] = {
|
|
180
|
+
options: (obj.options ?? []).filter(Boolean),
|
|
181
|
+
flags: (obj.flags ?? []).filter(Boolean),
|
|
182
|
+
shortcuts: (obj.shortcuts ?? []).filter(Boolean),
|
|
183
|
+
positionals,
|
|
184
|
+
optionValues,
|
|
185
|
+
fileCases,
|
|
186
|
+
commaCases,
|
|
187
|
+
};
|
|
188
|
+
} catch (e) { cds.debug?.('completion')?.(`skipping command '${cmd}': ${e?.message ?? e}`); }
|
|
189
|
+
}
|
|
190
|
+
return { commands, shortcuts: cds.Shortcuts, commandMap };
|
|
191
|
+
}
|
|
129
192
|
|
|
130
|
-
|
|
131
|
-
|
|
193
|
+
async function generateStaticScript(shell) {
|
|
194
|
+
const map = await buildCompletionMap();
|
|
195
|
+
switch (shell) {
|
|
196
|
+
case 'bash':
|
|
197
|
+
case 'gitbash': return generateBash(map);
|
|
198
|
+
case 'zsh': return generateZsh(map);
|
|
199
|
+
case 'fish': return generateFish(map);
|
|
200
|
+
case 'ps': return generatePs(map);
|
|
201
|
+
default:
|
|
202
|
+
throw `Internal error: unsupported shell: ${shell}`;
|
|
132
203
|
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --- Code generation helpers ---
|
|
207
|
+
|
|
208
|
+
// Serialises an array of strings for embedding in a shell word-list (e.g. compgen -W "...").
|
|
209
|
+
function shellQuoteList(arr) {
|
|
210
|
+
return arr.map(s => JSON.stringify(s)).join(' ');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Returns the single-char shortcuts from the global shortcuts map.
|
|
214
|
+
function singleCharShortcuts(shortcuts) {
|
|
215
|
+
return Object.entries(shortcuts).filter(([k]) => /^[a-z]$/.test(k));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Returns the merged, deduplicated, sorted list of all flags/options for a command.
|
|
219
|
+
function sortedFlags(e) {
|
|
220
|
+
return [...new Set([...e.options, ...e.flags])].sort();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Generates the bash/zsh case block that resolves single-char shortcuts to canonical names.
|
|
224
|
+
function shortcutCaseBlock(shortcuts, indent = ' ') {
|
|
225
|
+
const entries = singleCharShortcuts(shortcuts)
|
|
226
|
+
.map(([k, v]) => `${indent} ${k}) cmd=${v} ;;`)
|
|
227
|
+
.join('\n');
|
|
228
|
+
if (!entries) return '';
|
|
229
|
+
return `${indent}case "$cmd" in\n${entries}\n${indent}esac\n`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Bash: expression that completes to files matching a sentinel's extensions.
|
|
233
|
+
function bashFileExpr(sentinel) {
|
|
234
|
+
if (!sentinel.ext) return 'compgen -f -- "$current"';
|
|
235
|
+
return `compgen -f -- "$current" | grep -E '\\.(${sentinel.ext.join('|')})$'`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Zsh: compadd call that completes to files matching a sentinel's extensions.
|
|
239
|
+
function zshFileComp(sentinel) {
|
|
240
|
+
if (!sentinel.ext) return '_files';
|
|
241
|
+
return `_files -g '(${sentinel.ext.map(e => `*.${e}`).join('|')})'`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Fish: expression that completes to files matching a sentinel's extensions.
|
|
245
|
+
function fishFileComp(sentinel) {
|
|
246
|
+
if (!sentinel.ext) return '__fish_complete_path';
|
|
247
|
+
return `string match -r '\\.(${sentinel.ext.join('|')})\\$' -- (__fish_complete_path)`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Reads a shell template and substitutes {{PLACEHOLDER}} tokens.
|
|
251
|
+
// Note: replacement strings go through String.replaceAll, so '$' must be doubled
|
|
252
|
+
// ('$$$$' → '$$' → '$') to prevent JS from interpreting '$1' etc. as back-references.
|
|
253
|
+
async function applyTemplate(templateFile, replacements) {
|
|
254
|
+
const tmpl = await fs.promises.readFile(path.join(__dirname, 'templates', templateFile), 'utf8');
|
|
255
|
+
return Object.entries(replacements).reduce(
|
|
256
|
+
(s, [k, v]) => s.replaceAll(`{{${k}}}`, v.replaceAll('$', '$$$$')),
|
|
257
|
+
tmpl
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// --- Per-shell case-block generators ---
|
|
262
|
+
|
|
263
|
+
// Bash/zsh: option-value completions (previous word is a known flag → offer its values).
|
|
264
|
+
function optionValueCases(commandMap, emitLine) {
|
|
265
|
+
return Object.entries(commandMap)
|
|
266
|
+
.map(([cmd, e]) => {
|
|
267
|
+
const innerLines = [];
|
|
268
|
+
for (const [flag, val] of Object.entries(e.optionValues ?? {})) {
|
|
269
|
+
innerLines.push(emitLine(flag, val, null));
|
|
270
|
+
}
|
|
271
|
+
for (const [flag, sentinel] of Object.entries(e.fileCases ?? {})) {
|
|
272
|
+
if (flag === POSITIONAL_KEY) continue;
|
|
273
|
+
innerLines.push(emitLine(flag, null, sentinel));
|
|
274
|
+
}
|
|
275
|
+
if (!innerLines.length) return '';
|
|
276
|
+
return ` ${cmd})\n case "$previous" in\n${innerLines.join('\n')}\n esac ;;\n`;
|
|
277
|
+
})
|
|
278
|
+
.filter(Boolean)
|
|
279
|
+
.join('');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function bashOptionValueCases(commandMap) {
|
|
283
|
+
return optionValueCases(commandMap, (flag, val, sentinel) => {
|
|
284
|
+
const expr = sentinel
|
|
285
|
+
? (sentinel === DIR() ? 'compgen -d -- "$current"' : bashFileExpr(sentinel))
|
|
286
|
+
: `compgen -W "${shellQuoteList(val)}" -- "$current"`;
|
|
287
|
+
return ` ${flag}) COMPREPLY=( $(${expr}) ) ; return 0 ;;`;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function zshOptionValueCases(commandMap) {
|
|
292
|
+
return optionValueCases(commandMap, (flag, val, sentinel) => {
|
|
293
|
+
const comp = sentinel
|
|
294
|
+
? (sentinel === DIR() ? '_files -/' : zshFileComp(sentinel))
|
|
295
|
+
: `compadd -S '' -- ${shellQuoteList([...val].sort())}`;
|
|
296
|
+
return ` ${flag}) ${comp} ; return ;;`;
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Bash/zsh: comma-separated option completions.
|
|
301
|
+
// Entries keyed by POSITIONAL_KEY match any previous word (when user didn't type a flag);
|
|
302
|
+
// a guard skips them if the previous word is itself a flag (consuming an option value).
|
|
303
|
+
function bashCommaCases(commandMap) {
|
|
304
|
+
const S = '$'; // prevent JS template literal from interpolating shell variables
|
|
305
|
+
return Object.entries(commandMap)
|
|
306
|
+
.map(([cmd, e]) => {
|
|
307
|
+
// Sort so POSITIONAL_KEY ('_') entries come last — they use a catch-all pattern.
|
|
308
|
+
const entries = Object.entries(e.commaCases ?? {}).sort(([a], [b]) =>
|
|
309
|
+
a === POSITIONAL_KEY ? 1 : b === POSITIONAL_KEY ? -1 : 0
|
|
310
|
+
);
|
|
311
|
+
if (!entries.length) return '';
|
|
312
|
+
const innerLines = entries.map(([flag, val]) => {
|
|
313
|
+
const list = shellQuoteList(val);
|
|
314
|
+
const pattern = flag === POSITIONAL_KEY ? '*' : flag;
|
|
315
|
+
// For positional entries: skip if previous word looks like a flag (it's consuming a value).
|
|
316
|
+
const guard = flag === POSITIONAL_KEY
|
|
317
|
+
? ` [[ "$previous" == -* ]] && return 0\n`
|
|
318
|
+
: '';
|
|
319
|
+
const body = [
|
|
320
|
+
guard + ` local prefix="" partial="" IFS=,`,
|
|
321
|
+
` if [[ "$current" == *,* ]]; then`,
|
|
322
|
+
` prefix="` + S + `{current%,*},"`,
|
|
323
|
+
` partial="` + S + `{current##*,}"`,
|
|
324
|
+
` else`,
|
|
325
|
+
` partial="$current"`,
|
|
326
|
+
` fi`,
|
|
327
|
+
` local -a _used`,
|
|
328
|
+
` read -ra _used <<< "` + S + `{prefix%,}"`,
|
|
329
|
+
` local _all=(${list})`,
|
|
330
|
+
` for v in "` + S + `{_all[@]}"; do`,
|
|
331
|
+
` [[ " ` + S + `{_used[*]} " == *" $v "* ]] && continue`,
|
|
332
|
+
` [[ "$v" == "$partial"* ]] && COMPREPLY+=("$prefix$v")`,
|
|
333
|
+
` done`,
|
|
334
|
+
` [[ ` + S + `{#COMPREPLY[@]} -gt 0 ]] && { compopt -o nospace; return 0; } ;;`,
|
|
335
|
+
].join('\n');
|
|
336
|
+
return ` ${pattern})\n${body}`;
|
|
337
|
+
});
|
|
338
|
+
return ` ${cmd})\n case "$previous" in\n${innerLines.join('\n')}\n esac ;;\n`;
|
|
339
|
+
})
|
|
340
|
+
.filter(Boolean)
|
|
341
|
+
.join('');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function zshCommaCases(commandMap) {
|
|
345
|
+
const S = '$'; // prevent JS template literal from interpolating shell variables
|
|
346
|
+
return Object.entries(commandMap)
|
|
347
|
+
.map(([cmd, e]) => {
|
|
348
|
+
const entries = Object.entries(e.commaCases ?? {}).sort(([a], [b]) =>
|
|
349
|
+
a === POSITIONAL_KEY ? 1 : b === POSITIONAL_KEY ? -1 : 0
|
|
350
|
+
);
|
|
351
|
+
if (!entries.length) return '';
|
|
352
|
+
const innerLines = entries.map(([flag, val]) => {
|
|
353
|
+
const list = shellQuoteList(val);
|
|
354
|
+
const pattern = flag === POSITIONAL_KEY ? `*)` : `${flag})`;
|
|
355
|
+
// For positional entries: skip if previous word looks like a flag (it's consuming a value).
|
|
356
|
+
const guard = flag === POSITIONAL_KEY
|
|
357
|
+
? ` [[ "$previous" == -* ]] && return\n`
|
|
358
|
+
: '';
|
|
359
|
+
const body = [
|
|
360
|
+
guard + ` local -a _used=("` + S + `{(s:,:)current}")`,
|
|
361
|
+
` local prefix="` + S + `{(j:,:)_used[1,-2]}" partial="` + S + `{_used[-1]}"`,
|
|
362
|
+
` [[ -n "$prefix" ]] && prefix="$prefix,"`,
|
|
363
|
+
` local -a _all=(${list}) _result=()`,
|
|
364
|
+
` for v in "` + S + `{_all[@]}"; do`,
|
|
365
|
+
` (( ` + S + `{_used[(Ie)$v]} )) && continue`,
|
|
366
|
+
` [[ "$v" == "$partial"* ]] && _result+=("$v")`,
|
|
367
|
+
` done`,
|
|
368
|
+
` compadd -p "$prefix" -S '' -- "` + S + `{_result[@]}" ; return ;;`,
|
|
369
|
+
].join('\n');
|
|
370
|
+
return ` ${pattern}\n${body}`;
|
|
371
|
+
});
|
|
372
|
+
return ` ${cmd})\n case "$previous" in\n${innerLines.join('\n')}\n esac ;;\n`;
|
|
373
|
+
})
|
|
374
|
+
.filter(Boolean)
|
|
375
|
+
.join('');
|
|
376
|
+
}
|
|
133
377
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
378
|
+
// --- Static script generators ---
|
|
379
|
+
|
|
380
|
+
async function generateBash(map) {
|
|
381
|
+
const { commands, shortcuts, commandMap } = map;
|
|
382
|
+
|
|
383
|
+
const flagCases = Object.entries(commandMap)
|
|
384
|
+
.map(([cmd, e]) => {
|
|
385
|
+
const flags = sortedFlags(e);
|
|
386
|
+
if (!flags.length) return '';
|
|
387
|
+
return ` ${cmd}) COMPREPLY=( $(compgen -W "${shellQuoteList(flags)}" -- "$current") ) ;;`;
|
|
388
|
+
})
|
|
389
|
+
.filter(Boolean)
|
|
390
|
+
.join('\n');
|
|
391
|
+
|
|
392
|
+
const fileCases = Object.entries(commandMap)
|
|
393
|
+
.map(([cmd, e]) => {
|
|
394
|
+
const sentinel = e.fileCases?.[POSITIONAL_KEY];
|
|
395
|
+
if (!sentinel) return '';
|
|
396
|
+
const expr = sentinel === DIR() ? 'compgen -d -- "$current"' : bashFileExpr(sentinel);
|
|
397
|
+
return ` ${cmd}) COMPREPLY=( $(${expr}) ) ;;`;
|
|
398
|
+
})
|
|
399
|
+
.filter(Boolean)
|
|
400
|
+
.join('\n');
|
|
401
|
+
|
|
402
|
+
const stringPositionalCases = Object.entries(commandMap)
|
|
403
|
+
.map(([cmd, e]) => {
|
|
404
|
+
if (e.fileCases?.[POSITIONAL_KEY] || !e.positionals?.length) return '';
|
|
405
|
+
return ` ${cmd}) COMPREPLY=( $(compgen -W "${shellQuoteList([...e.positionals].sort())}" -- "$current") ) ;;`;
|
|
406
|
+
})
|
|
407
|
+
.filter(Boolean)
|
|
408
|
+
.join('\n');
|
|
409
|
+
|
|
410
|
+
return applyTemplate('cds-tmpl.sh', {
|
|
411
|
+
SHORTCUT_CASE: shortcutCaseBlock(shortcuts),
|
|
412
|
+
COMMANDS: shellQuoteList(commands),
|
|
413
|
+
FLAG_CASES: flagCases,
|
|
414
|
+
OPTION_VALUE_CASES: bashOptionValueCases(commandMap),
|
|
415
|
+
COMMA_CASES: bashCommaCases(commandMap),
|
|
416
|
+
FILE_CASES: fileCases,
|
|
417
|
+
STRING_POSITIONAL_CASES: stringPositionalCases,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async function generateZsh(map) {
|
|
422
|
+
const { commands, shortcuts, commandMap } = map;
|
|
423
|
+
|
|
424
|
+
const flagCases = Object.entries(commandMap)
|
|
425
|
+
.map(([cmd, e]) => {
|
|
426
|
+
const flags = sortedFlags(e);
|
|
427
|
+
if (!flags.length) return '';
|
|
428
|
+
return ` ${cmd}) compadd -S '' -- ${shellQuoteList(flags)} ;;`;
|
|
429
|
+
})
|
|
430
|
+
.filter(Boolean)
|
|
431
|
+
.join('\n');
|
|
432
|
+
|
|
433
|
+
const fileCases = Object.entries(commandMap)
|
|
434
|
+
.map(([cmd, e]) => {
|
|
435
|
+
const sentinel = e.fileCases?.[POSITIONAL_KEY];
|
|
436
|
+
if (!sentinel) return '';
|
|
437
|
+
const comp = sentinel === DIR() ? '_files -/' : zshFileComp(sentinel);
|
|
438
|
+
return ` ${cmd}) ${comp} ; return ;;`;
|
|
439
|
+
})
|
|
440
|
+
.filter(Boolean)
|
|
441
|
+
.join('\n');
|
|
442
|
+
|
|
443
|
+
const stringPositionalCases = Object.entries(commandMap)
|
|
444
|
+
.map(([cmd, e]) => {
|
|
445
|
+
if (e.fileCases?.[POSITIONAL_KEY] || !e.positionals?.length) return '';
|
|
446
|
+
return ` ${cmd}) compadd -S '' -- ${shellQuoteList([...e.positionals].sort())} ; return ;;`;
|
|
447
|
+
})
|
|
448
|
+
.filter(Boolean)
|
|
449
|
+
.join('\n');
|
|
450
|
+
|
|
451
|
+
return applyTemplate('cds-tmpl.zsh', {
|
|
452
|
+
SHORTCUT_CASE: shortcutCaseBlock(shortcuts),
|
|
453
|
+
COMMANDS: shellQuoteList(commands),
|
|
454
|
+
FLAG_CASES: flagCases,
|
|
455
|
+
OPTION_VALUE_CASES: zshOptionValueCases(commandMap),
|
|
456
|
+
COMMA_CASES: zshCommaCases(commandMap),
|
|
457
|
+
FILE_CASES: fileCases,
|
|
458
|
+
STRING_POSITIONAL_CASES: stringPositionalCases,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function generateFish(map) {
|
|
463
|
+
const { commands, shortcuts, commandMap } = map;
|
|
464
|
+
|
|
465
|
+
// Reverse map: canonical command name → its single-char shortcut(s).
|
|
466
|
+
const cmdShortcuts = {};
|
|
467
|
+
for (const [shortcut, cmd] of singleCharShortcuts(shortcuts)) {
|
|
468
|
+
(cmdShortcuts[cmd] ??= []).push(shortcut);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Fish condition: fires when the subcommand (or one of its shortcuts) is already on the line.
|
|
472
|
+
const seenCmd = (cmd) =>
|
|
473
|
+
`__fish_seen_subcommand_from ${[cmd, ...(cmdShortcuts[cmd] ?? [])].join(' ')}`;
|
|
474
|
+
|
|
475
|
+
const allCmdsAndShortcuts = [
|
|
476
|
+
...commands,
|
|
477
|
+
...singleCharShortcuts(shortcuts).map(([k]) => k),
|
|
478
|
+
].join(' ');
|
|
479
|
+
|
|
480
|
+
const lines = [];
|
|
481
|
+
|
|
482
|
+
// Top-level commands — only shown before any subcommand is committed.
|
|
483
|
+
for (const cmd of commands) {
|
|
484
|
+
lines.push(`complete -c cds -f -n '__fish_use_subcommand' -a '${cmd}'`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
for (const [cmd, e] of Object.entries(commandMap)) {
|
|
488
|
+
const hasFileSentinel = !!e.fileCases?.[POSITIONAL_KEY];
|
|
489
|
+
|
|
490
|
+
// File-sentinel positionals (e.g. *.cds, *.json for compile).
|
|
491
|
+
for (const [flag, sentinel] of Object.entries(e.fileCases ?? {})) {
|
|
492
|
+
const fishComp = sentinel === DIR() ? '__fish_complete_directories' : fishFileComp(sentinel);
|
|
493
|
+
const cond = flag === POSITIONAL_KEY
|
|
494
|
+
? seenCmd(cmd)
|
|
495
|
+
: `${seenCmd(cmd)} && __fish_prev_arg_in ${flag}`;
|
|
496
|
+
lines.push(`complete -c cds -f -n '${cond}' -a "(${fishComp})"`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// String positionals (e.g. command names for `cds help`).
|
|
500
|
+
for (const pos of [...(e.positionals ?? [])].sort()) {
|
|
501
|
+
lines.push(`complete -c cds -f -n '${seenCmd(cmd)}' -a '${pos}'`);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Flags — gated on current token starting with '-' when a file sentinel is present,
|
|
505
|
+
// so that `cds compile <tab>` shows files only and `cds compile --<tab>` shows flags.
|
|
506
|
+
const flagCond = hasFileSentinel
|
|
507
|
+
? `${seenCmd(cmd)} && string match -q -- "-*" (commandline -ct)`
|
|
508
|
+
: seenCmd(cmd);
|
|
509
|
+
for (const token of sortedFlags(e)) {
|
|
510
|
+
lines.push(`complete -c cds -f -n '${flagCond}' -a '${token}'`);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Option values (previous word is a known flag → offer its static values).
|
|
514
|
+
for (const [flag, val] of Object.entries(e.optionValues ?? {})) {
|
|
515
|
+
lines.push(`complete -c cds -f -n '${seenCmd(cmd)} && __fish_prev_arg_in ${flag}' -a '${[...val].sort().join(' ')}'`);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Comma-separated values — delegated to the _cdsCommaComplete helper in the template.
|
|
519
|
+
for (const [flag, val] of Object.entries(e.commaCases ?? {})) {
|
|
520
|
+
const values = [...val].sort().join(' ');
|
|
521
|
+
const cond = flag === POSITIONAL_KEY
|
|
522
|
+
? seenCmd(cmd)
|
|
523
|
+
: `${seenCmd(cmd)} && __fish_prev_arg_in ${flag}`;
|
|
524
|
+
lines.push(`complete -c cds -f -n '${cond}' -a "(_cdsCommaComplete ${values})"`);
|
|
525
|
+
}
|
|
137
526
|
}
|
|
138
527
|
|
|
139
|
-
return
|
|
528
|
+
return applyTemplate('cds-tmpl.fish', {
|
|
529
|
+
COMMAND_COMPLETIONS: lines.join('\n'),
|
|
530
|
+
CMD_LIST: commands.join(' '),
|
|
531
|
+
CMD_LIST_WITH_SHORTCUTS: allCmdsAndShortcuts,
|
|
532
|
+
});
|
|
140
533
|
}
|
|
141
534
|
|
|
535
|
+
async function generatePs(map) {
|
|
536
|
+
const { commands, shortcuts, commandMap } = map;
|
|
537
|
+
|
|
538
|
+
const cmdArray = commands.map(c => `'${c}'`).join(', ');
|
|
539
|
+
|
|
540
|
+
const shortcutEntries = singleCharShortcuts(shortcuts)
|
|
541
|
+
.map(([k, v]) => ` '${k}' = '${v}'`)
|
|
542
|
+
.join(";\n");
|
|
543
|
+
|
|
544
|
+
const cmdEntries = Object.entries(commandMap)
|
|
545
|
+
.map(([cmd, e]) => {
|
|
546
|
+
const flags = sortedFlags(e).map(t => `'${t}'`).join(', ');
|
|
547
|
+
return ` '${cmd}' = @(${flags})`;
|
|
548
|
+
})
|
|
549
|
+
.join(";\n");
|
|
550
|
+
|
|
551
|
+
const positionalEntries = Object.entries(commandMap)
|
|
552
|
+
.filter(([, e]) => e.positionals?.length)
|
|
553
|
+
.map(([cmd, e]) => ` '${cmd}' = @(${[...e.positionals].sort().map(p => `'${p}'`).join(', ')})`)
|
|
554
|
+
.join(";\n");
|
|
555
|
+
|
|
556
|
+
const optionValueEntries = Object.entries(commandMap)
|
|
557
|
+
.filter(([, e]) => Object.keys(e.optionValues ?? {}).length || Object.keys(e.fileCases ?? {}).length)
|
|
558
|
+
.map(([cmd, e]) => {
|
|
559
|
+
const inner = [];
|
|
560
|
+
for (const [flag, val] of Object.entries(e.optionValues ?? {})) {
|
|
561
|
+
inner.push(` '${flag}' = @(${[...val].sort().map(v => `'${v}'`).join(', ')})`);
|
|
562
|
+
}
|
|
563
|
+
for (const [flag, sentinel] of Object.entries(e.fileCases ?? {})) {
|
|
564
|
+
inner.push(` '${flag}' = ${filePs1(sentinel)}`);
|
|
565
|
+
}
|
|
566
|
+
return ` '${cmd}' = @{\n${inner.join(";\n")}\n }`;
|
|
567
|
+
})
|
|
568
|
+
.join(";\n");
|
|
569
|
+
|
|
570
|
+
const commaEntries = Object.entries(commandMap)
|
|
571
|
+
.filter(([, e]) => Object.keys(e.commaCases ?? {}).length)
|
|
572
|
+
.map(([cmd, e]) => {
|
|
573
|
+
const inner = Object.entries(e.commaCases).map(([flag, val]) =>
|
|
574
|
+
` '${flag}' = @(${[...val].sort().map(v => `'${v}'`).join(', ')})`
|
|
575
|
+
);
|
|
576
|
+
return ` '${cmd}' = @{\n${inner.join(";\n")}\n }`;
|
|
577
|
+
})
|
|
578
|
+
.join(";\n");
|
|
579
|
+
|
|
580
|
+
return applyTemplate('cds-tmpl.ps1', {
|
|
581
|
+
CMD_ARRAY: cmdArray,
|
|
582
|
+
SHORTCUT_ENTRIES: shortcutEntries,
|
|
583
|
+
POSITIONAL_ENTRIES: positionalEntries,
|
|
584
|
+
OPTION_VALUE_ENTRIES: optionValueEntries,
|
|
585
|
+
COMMA_ENTRIES: commaEntries,
|
|
586
|
+
CMD_ENTRIES: cmdEntries,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// --- Completion engine ---
|
|
591
|
+
|
|
142
592
|
function reply(values) {
|
|
143
|
-
if (!Array.isArray(values) || values.length === 0)
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
593
|
+
if (!Array.isArray(values) || values.length === 0) return;
|
|
146
594
|
|
|
147
|
-
values = values.filter(e =>
|
|
148
|
-
values = [...new Set(values)];
|
|
595
|
+
values = [...new Set(values.filter(e => typeof e === 'string' && e.trim()))];
|
|
149
596
|
values.sort((a, b) => {
|
|
150
|
-
|
|
151
|
-
|
|
597
|
+
// Files / positionals before flags, then alphabetical within each group.
|
|
598
|
+
const aIsFlag = a.startsWith('-');
|
|
599
|
+
const bIsFlag = b.startsWith('-');
|
|
600
|
+
if (aIsFlag !== bIsFlag) return aIsFlag ? 1 : -1;
|
|
152
601
|
return a.localeCompare(b);
|
|
153
602
|
});
|
|
154
603
|
|
|
155
604
|
console.log(values.join('\n'));
|
|
156
605
|
}
|
|
157
606
|
|
|
607
|
+
// Splits a shell command line into tokens, respecting quoted strings and escaped spaces.
|
|
158
608
|
function getSegments(line) {
|
|
159
609
|
const cliSplit = /'(.*?)'|"(.*?)"|(\\,|\\ |[^, ])+/gs;
|
|
160
|
-
|
|
161
|
-
return result.map(s =>
|
|
610
|
+
return line.match(cliSplit).map(s =>
|
|
162
611
|
s.trim()
|
|
163
612
|
.replace(/(\\ )/g, ' ')
|
|
164
613
|
.replace(/^["']|["']$/g, '')
|
|
165
614
|
);
|
|
166
615
|
}
|
|
167
616
|
|
|
617
|
+
// Loads the cds command object for the given name, resolving single-char shortcuts first.
|
|
168
618
|
function getCdsCmd(cdsCmd) {
|
|
169
619
|
cdsCmd = cds.Shortcuts?.[cdsCmd] ?? cdsCmd;
|
|
170
620
|
return cds.load(cdsCmd);
|
|
171
621
|
}
|
|
172
622
|
|
|
623
|
+
// Returns all completable flags/options for a command, excluding flags already used on the line.
|
|
173
624
|
function getAllOptions(cdsCmdObj, segments) {
|
|
174
625
|
return [
|
|
175
626
|
...cdsCmdObj.options ?? [],
|
|
176
|
-
...cdsCmdObj.flags?.filter(o => !segments.includes(o)) ?? []
|
|
627
|
+
...cdsCmdObj.flags?.filter(o => !segments.includes(o)) ?? [],
|
|
177
628
|
];
|
|
178
629
|
}
|
|
179
630
|
|
|
180
|
-
function getUtil() {
|
|
181
|
-
const completionFs = require('./completionFs');
|
|
182
|
-
return {
|
|
183
|
-
cds,
|
|
184
|
-
completionFs
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
631
|
async function getCompletions(cdsCmd, previousWord, currentWord, segments) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
632
|
+
// Returns completions filtered by currentWord prefix, unless commaFiltered is set
|
|
633
|
+
// (comma logic pre-filters its own results to avoid double-filtering).
|
|
634
|
+
let commaFiltered = false;
|
|
635
|
+
|
|
636
|
+
const resolveComma = (values, commaStr, partial) => {
|
|
637
|
+
commaFiltered = true;
|
|
638
|
+
const parts = commaStr.split(',');
|
|
639
|
+
const alreadyUsed = new Set(parts.slice(0, -1).map(s => s.trim()).filter(Boolean));
|
|
640
|
+
const prefix = parts.slice(0, -1).join(',') + (parts.length > 1 ? ',' : '');
|
|
641
|
+
return values.filter(v => !alreadyUsed.has(v) && v.startsWith(partial)).map(v => prefix + v);
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
// Reads the file/dir sentinel for a resolved entry and returns matching paths.
|
|
645
|
+
const resolveSentinel = (sentinel, word) => {
|
|
646
|
+
if (sentinel === DIR()) return completionFs.readdir(word, { files: false });
|
|
647
|
+
if (isFileSentinel(sentinel)) return completionFs.readdir(word, fileRegex(sentinel) ? { fileRegex: fileRegex(sentinel) } : {});
|
|
648
|
+
return null;
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
const computeCompletions = async () => {
|
|
652
|
+
// If the cursor is right after 'cds', offer top-level command names.
|
|
194
653
|
if (previousWord === 'cds') {
|
|
195
|
-
if (!currentWord)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
cdsCmd = 'compile';
|
|
203
|
-
previousWord = 'compile';
|
|
204
|
-
}
|
|
654
|
+
if (!currentWord) return cds.cmds;
|
|
655
|
+
const matching = cds.cmds.filter(c => c.startsWith(currentWord));
|
|
656
|
+
if (matching.length) return matching;
|
|
657
|
+
// No command matched the typed prefix — fall through as if typing a compile file arg.
|
|
658
|
+
cdsCmd = 'compile';
|
|
659
|
+
previousWord = 'compile';
|
|
205
660
|
}
|
|
206
661
|
|
|
207
662
|
try {
|
|
208
663
|
const cdsCmdObj = getCdsCmd(cdsCmd);
|
|
209
|
-
if (typeof cdsCmdObj.handleCompletion === 'function') {
|
|
210
|
-
return await cdsCmdObj.handleCompletion(currentWord, previousWord, segments, getUtil());
|
|
211
|
-
}
|
|
212
664
|
|
|
213
|
-
|
|
214
|
-
|
|
665
|
+
// Current token starts with '-' → offer flags/options.
|
|
666
|
+
if (currentWord?.startsWith('-')) return getAllOptions(cdsCmdObj, segments);
|
|
667
|
+
|
|
668
|
+
const raw = await cdsCmdObj.staticCompletions?.();
|
|
669
|
+
if (raw && !Array.isArray(raw)) {
|
|
670
|
+
// Resolve single-char option shortcuts (e.g. -4 → --for) so we can look up
|
|
671
|
+
// the right key in the staticCompletions map.
|
|
672
|
+
const cmdOptions = cdsCmdObj.options ?? [];
|
|
673
|
+
const cmdShortcuts = cdsCmdObj.shortcuts ?? [];
|
|
674
|
+
const shortcutIdx = cmdShortcuts.indexOf(previousWord);
|
|
675
|
+
const lookupKey = shortcutIdx !== -1 ? (cmdOptions[shortcutIdx] ?? previousWord) : previousWord;
|
|
676
|
+
|
|
677
|
+
// Comma-separated values come in two forms:
|
|
678
|
+
// inline: "--add approuter,m" → prev="--add" curr="approuter,m"
|
|
679
|
+
// space-separated: "--add approuter, m" → prev="approuter," curr="m"
|
|
680
|
+
const isSpaceSeparated = lookupKey.endsWith(',');
|
|
681
|
+
const isInlineComma = !isSpaceSeparated && currentWord?.includes(',');
|
|
682
|
+
if (isSpaceSeparated || isInlineComma) {
|
|
683
|
+
// For space-separated form, find the originating option flag in the segments.
|
|
684
|
+
const optKey = isSpaceSeparated
|
|
685
|
+
? cmdOptions.find(o =>
|
|
686
|
+
segments.includes(o) ||
|
|
687
|
+
cmdShortcuts.some((s, i) => segments.includes(s) && cmdOptions[i] === o)
|
|
688
|
+
)
|
|
689
|
+
: lookupKey;
|
|
690
|
+
const resolved = optKey ? await raw[optKey] : undefined;
|
|
691
|
+
if (resolved?.type === ':comma') {
|
|
692
|
+
const values = await resolved.values;
|
|
693
|
+
const commaStr = isSpaceSeparated ? previousWord : currentWord;
|
|
694
|
+
const partial = isSpaceSeparated ? (currentWord ?? '') : commaStr.split(',').at(-1);
|
|
695
|
+
return resolveComma(values, commaStr, partial);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Previous word is a known option → offer its static values or file completions.
|
|
700
|
+
const entry = raw[lookupKey];
|
|
701
|
+
if (entry !== undefined) {
|
|
702
|
+
const resolved = await entry;
|
|
703
|
+
if (resolved?.type === ':comma') {
|
|
704
|
+
const values = await resolved.values;
|
|
705
|
+
return resolveComma(values, currentWord ?? '', (currentWord ?? '').split(',').at(-1));
|
|
706
|
+
}
|
|
707
|
+
return resolveSentinel(resolved, currentWord) ?? resolved;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// No option matched — check the default positional sentinel.
|
|
711
|
+
const defaultPositional = raw[POSITIONAL_KEY] !== undefined ? await raw[POSITIONAL_KEY] : undefined;
|
|
712
|
+
if (defaultPositional !== undefined) {
|
|
713
|
+
if (defaultPositional?.type === ':comma') {
|
|
714
|
+
const values = await defaultPositional.values;
|
|
715
|
+
return resolveComma(values, currentWord ?? '', (currentWord ?? '').split(',').at(-1));
|
|
716
|
+
}
|
|
717
|
+
return resolveSentinel(defaultPositional, currentWord) ?? [];
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Previous word is a consumed option value (e.g. "approuter" after "--add").
|
|
721
|
+
// Don't offer more positionals — the user has already provided one.
|
|
722
|
+
const allOpts = [...cmdOptions, ...cmdShortcuts, ...(cdsCmdObj.flags ?? [])];
|
|
723
|
+
if (previousWord !== cdsCmd && !allOpts.includes(previousWord)) return [];
|
|
215
724
|
}
|
|
216
725
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
all.push(...allOptions);
|
|
726
|
+
// Array form of staticCompletions → fixed positional list (e.g. cds help <tab>).
|
|
727
|
+
if (Array.isArray(raw) && raw.length) {
|
|
728
|
+
const prevIsOption = cdsCmdObj.options?.includes(previousWord) || cdsCmdObj.shortcuts?.includes(previousWord);
|
|
729
|
+
return prevIsOption ? [] : raw;
|
|
222
730
|
}
|
|
223
731
|
|
|
224
|
-
|
|
732
|
+
// No staticCompletions — fall back to files + available flags.
|
|
733
|
+
const files = await completionFs.readdir(currentWord);
|
|
734
|
+
const prevIsOption = cdsCmdObj.options?.includes(previousWord) || cdsCmdObj.shortcuts?.includes(previousWord);
|
|
735
|
+
if (!prevIsOption) files.push(...getAllOptions(cdsCmdObj, segments));
|
|
736
|
+
return files;
|
|
737
|
+
|
|
225
738
|
} catch {
|
|
226
739
|
return [];
|
|
227
740
|
}
|
|
228
|
-
}
|
|
741
|
+
};
|
|
229
742
|
|
|
230
|
-
let completions = await
|
|
231
|
-
if (currentWord) {
|
|
232
|
-
completions =
|
|
743
|
+
let completions = await computeCompletions();
|
|
744
|
+
if (currentWord && !commaFiltered) {
|
|
745
|
+
completions = completions.filter(c => c?.startsWith(currentWord));
|
|
233
746
|
}
|
|
234
|
-
|
|
235
747
|
return completions;
|
|
236
748
|
}
|