dedalus-cli 0.0.0-stage → 0.8.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/LICENSE +201 -0
- package/README.md +306 -2
- package/SKILL.md +73 -0
- package/api.md +340 -0
- package/dist/cjs/bin.d.ts +3 -0
- package/dist/cjs/bin.d.ts.map +1 -0
- package/dist/cjs/bin.js +7 -0
- package/dist/cjs/bin.js.map +1 -0
- package/dist/cjs/cli/completions.d.ts +6 -0
- package/dist/cjs/cli/completions.d.ts.map +1 -0
- package/dist/cjs/cli/completions.js +11 -0
- package/dist/cjs/cli/completions.js.map +1 -0
- package/dist/cjs/cli/credentials.d.ts +169 -0
- package/dist/cjs/cli/credentials.d.ts.map +1 -0
- package/dist/cjs/cli/credentials.js +630 -0
- package/dist/cjs/cli/credentials.js.map +1 -0
- package/dist/cjs/cli/keychain.d.ts +34 -0
- package/dist/cjs/cli/keychain.d.ts.map +1 -0
- package/dist/cjs/cli/keychain.js +246 -0
- package/dist/cjs/cli/keychain.js.map +1 -0
- package/dist/cjs/cli/list.d.ts +23 -0
- package/dist/cjs/cli/list.d.ts.map +1 -0
- package/dist/cjs/cli/list.js +32 -0
- package/dist/cjs/cli/list.js.map +1 -0
- package/dist/cjs/cli/login.d.ts +137 -0
- package/dist/cjs/cli/login.d.ts.map +1 -0
- package/dist/cjs/cli/login.js +2060 -0
- package/dist/cjs/cli/login.js.map +1 -0
- package/dist/cjs/cli/runtime.d.ts +98 -0
- package/dist/cjs/cli/runtime.d.ts.map +1 -0
- package/dist/cjs/cli/runtime.js +1356 -0
- package/dist/cjs/cli/runtime.js.map +1 -0
- package/dist/cjs/cli/toon.d.ts +6 -0
- package/dist/cjs/cli/toon.d.ts.map +1 -0
- package/dist/cjs/cli/toon.js +165 -0
- package/dist/cjs/cli/toon.js.map +1 -0
- package/dist/cjs/cli/upgrade.d.ts +28 -0
- package/dist/cjs/cli/upgrade.d.ts.map +1 -0
- package/dist/cjs/cli/upgrade.js +1191 -0
- package/dist/cjs/cli/upgrade.js.map +1 -0
- package/dist/cjs/commands/index.d.ts +47 -0
- package/dist/cjs/commands/index.d.ts.map +1 -0
- package/dist/cjs/commands/index.js +1202 -0
- package/dist/cjs/commands/index.js.map +1 -0
- package/dist/cjs/custom/browser-routing.d.ts +4 -0
- package/dist/cjs/custom/browser-routing.d.ts.map +1 -0
- package/dist/cjs/custom/browser-routing.js +51 -0
- package/dist/cjs/custom/browser-routing.js.map +1 -0
- package/dist/cjs/custom/images.d.ts +6 -0
- package/dist/cjs/custom/images.d.ts.map +1 -0
- package/dist/cjs/custom/images.js +74 -0
- package/dist/cjs/custom/images.js.map +1 -0
- package/dist/cjs/custom/machines.d.ts +20 -0
- package/dist/cjs/custom/machines.d.ts.map +1 -0
- package/dist/cjs/custom/machines.js +84 -0
- package/dist/cjs/custom/machines.js.map +1 -0
- package/dist/cjs/custom/ssh-picker.d.ts +19 -0
- package/dist/cjs/custom/ssh-picker.d.ts.map +1 -0
- package/dist/cjs/custom/ssh-picker.js +157 -0
- package/dist/cjs/custom/ssh-picker.js.map +1 -0
- package/dist/cjs/custom/ssh.d.ts +18 -0
- package/dist/cjs/custom/ssh.d.ts.map +1 -0
- package/dist/cjs/custom/ssh.js +152 -0
- package/dist/cjs/custom/ssh.js.map +1 -0
- package/dist/cjs/feedback/bundle.d.ts +41 -0
- package/dist/cjs/feedback/bundle.d.ts.map +1 -0
- package/dist/cjs/feedback/bundle.js +88 -0
- package/dist/cjs/feedback/bundle.js.map +1 -0
- package/dist/cjs/feedback/command.d.ts +6 -0
- package/dist/cjs/feedback/command.d.ts.map +1 -0
- package/dist/cjs/feedback/command.js +100 -0
- package/dist/cjs/feedback/command.js.map +1 -0
- package/dist/cjs/feedback/diagnostics.d.ts +30 -0
- package/dist/cjs/feedback/diagnostics.d.ts.map +1 -0
- package/dist/cjs/feedback/diagnostics.js +278 -0
- package/dist/cjs/feedback/diagnostics.js.map +1 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +11 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/sdk/api-promise.d.ts +31 -0
- package/dist/cjs/sdk/api-promise.d.ts.map +1 -0
- package/dist/cjs/sdk/api-promise.js +73 -0
- package/dist/cjs/sdk/api-promise.js.map +1 -0
- package/dist/cjs/sdk/client.d.ts +267 -0
- package/dist/cjs/sdk/client.d.ts.map +1 -0
- package/dist/cjs/sdk/client.js +962 -0
- package/dist/cjs/sdk/client.js.map +1 -0
- package/dist/cjs/sdk/core/api-promise.d.ts +3 -0
- package/dist/cjs/sdk/core/api-promise.d.ts.map +1 -0
- package/dist/cjs/sdk/core/api-promise.js +8 -0
- package/dist/cjs/sdk/core/api-promise.js.map +1 -0
- package/dist/cjs/sdk/core/error.d.ts +46 -0
- package/dist/cjs/sdk/core/error.d.ts.map +1 -0
- package/dist/cjs/sdk/core/error.js +135 -0
- package/dist/cjs/sdk/core/error.js.map +1 -0
- package/dist/cjs/sdk/core/pagination.d.ts +38 -0
- package/dist/cjs/sdk/core/pagination.d.ts.map +1 -0
- package/dist/cjs/sdk/core/pagination.js +98 -0
- package/dist/cjs/sdk/core/pagination.js.map +1 -0
- package/dist/cjs/sdk/core/resource.d.ts +2 -0
- package/dist/cjs/sdk/core/resource.d.ts.map +1 -0
- package/dist/cjs/sdk/core/resource.js +7 -0
- package/dist/cjs/sdk/core/resource.js.map +1 -0
- package/dist/cjs/sdk/core/uploads.d.ts +3 -0
- package/dist/cjs/sdk/core/uploads.d.ts.map +1 -0
- package/dist/cjs/sdk/core/uploads.js +7 -0
- package/dist/cjs/sdk/core/uploads.js.map +1 -0
- package/dist/cjs/sdk/error.d.ts +3 -0
- package/dist/cjs/sdk/error.d.ts.map +1 -0
- package/dist/cjs/sdk/error.js +20 -0
- package/dist/cjs/sdk/error.js.map +1 -0
- package/dist/cjs/sdk/helpers/images.d.ts +31 -0
- package/dist/cjs/sdk/helpers/images.d.ts.map +1 -0
- package/dist/cjs/sdk/helpers/images.js +195 -0
- package/dist/cjs/sdk/helpers/images.js.map +1 -0
- package/dist/cjs/sdk/index.d.ts +7 -0
- package/dist/cjs/sdk/index.d.ts.map +1 -0
- package/dist/cjs/sdk/index.js +29 -0
- package/dist/cjs/sdk/index.js.map +1 -0
- package/dist/cjs/sdk/internal/builtin-types.d.ts +73 -0
- package/dist/cjs/sdk/internal/builtin-types.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/builtin-types.js +4 -0
- package/dist/cjs/sdk/internal/builtin-types.js.map +1 -0
- package/dist/cjs/sdk/internal/detect-platform.d.ts +15 -0
- package/dist/cjs/sdk/internal/detect-platform.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/detect-platform.js +185 -0
- package/dist/cjs/sdk/internal/detect-platform.js.map +1 -0
- package/dist/cjs/sdk/internal/errors.d.ts +3 -0
- package/dist/cjs/sdk/internal/errors.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/errors.js +41 -0
- package/dist/cjs/sdk/internal/errors.js.map +1 -0
- package/dist/cjs/sdk/internal/headers.d.ts +43 -0
- package/dist/cjs/sdk/internal/headers.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/headers.js +142 -0
- package/dist/cjs/sdk/internal/headers.js.map +1 -0
- package/dist/cjs/sdk/internal/parse.d.ts +3 -0
- package/dist/cjs/sdk/internal/parse.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/parse.js +7 -0
- package/dist/cjs/sdk/internal/parse.js.map +1 -0
- package/dist/cjs/sdk/internal/qs/formats.d.ts +7 -0
- package/dist/cjs/sdk/internal/qs/formats.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/qs/formats.js +13 -0
- package/dist/cjs/sdk/internal/qs/formats.js.map +1 -0
- package/dist/cjs/sdk/internal/qs/index.d.ts +10 -0
- package/dist/cjs/sdk/internal/qs/index.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/qs/index.js +14 -0
- package/dist/cjs/sdk/internal/qs/index.js.map +1 -0
- package/dist/cjs/sdk/internal/qs/stringify.d.ts +3 -0
- package/dist/cjs/sdk/internal/qs/stringify.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/qs/stringify.js +278 -0
- package/dist/cjs/sdk/internal/qs/stringify.js.map +1 -0
- package/dist/cjs/sdk/internal/qs/types.d.ts +57 -0
- package/dist/cjs/sdk/internal/qs/types.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/qs/types.js +3 -0
- package/dist/cjs/sdk/internal/qs/types.js.map +1 -0
- package/dist/cjs/sdk/internal/qs/utils.d.ts +15 -0
- package/dist/cjs/sdk/internal/qs/utils.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/qs/utils.js +230 -0
- package/dist/cjs/sdk/internal/qs/utils.js.map +1 -0
- package/dist/cjs/sdk/internal/request-options.d.ts +33 -0
- package/dist/cjs/sdk/internal/request-options.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/request-options.js +15 -0
- package/dist/cjs/sdk/internal/request-options.js.map +1 -0
- package/dist/cjs/sdk/internal/shim-types.d.ts +17 -0
- package/dist/cjs/sdk/internal/shim-types.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/shim-types.js +4 -0
- package/dist/cjs/sdk/internal/shim-types.js.map +1 -0
- package/dist/cjs/sdk/internal/shims.d.ts +26 -0
- package/dist/cjs/sdk/internal/shims.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/shims.js +92 -0
- package/dist/cjs/sdk/internal/shims.js.map +1 -0
- package/dist/cjs/sdk/internal/to-file.d.ts +45 -0
- package/dist/cjs/sdk/internal/to-file.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/to-file.js +91 -0
- package/dist/cjs/sdk/internal/to-file.js.map +1 -0
- package/dist/cjs/sdk/internal/types.d.ts +63 -0
- package/dist/cjs/sdk/internal/types.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/types.js +4 -0
- package/dist/cjs/sdk/internal/types.js.map +1 -0
- package/dist/cjs/sdk/internal/uploads.d.ts +42 -0
- package/dist/cjs/sdk/internal/uploads.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/uploads.js +158 -0
- package/dist/cjs/sdk/internal/uploads.js.map +1 -0
- package/dist/cjs/sdk/internal/utils/base64.d.ts +3 -0
- package/dist/cjs/sdk/internal/utils/base64.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/utils/base64.js +38 -0
- package/dist/cjs/sdk/internal/utils/base64.js.map +1 -0
- package/dist/cjs/sdk/internal/utils/body.d.ts +40 -0
- package/dist/cjs/sdk/internal/utils/body.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/utils/body.js +148 -0
- package/dist/cjs/sdk/internal/utils/body.js.map +1 -0
- package/dist/cjs/sdk/internal/utils/bytes.d.ts +4 -0
- package/dist/cjs/sdk/internal/utils/bytes.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/utils/bytes.js +31 -0
- package/dist/cjs/sdk/internal/utils/bytes.js.map +1 -0
- package/dist/cjs/sdk/internal/utils/env.d.ts +9 -0
- package/dist/cjs/sdk/internal/utils/env.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/utils/env.js +22 -0
- package/dist/cjs/sdk/internal/utils/env.js.map +1 -0
- package/dist/cjs/sdk/internal/utils/log.d.ts +52 -0
- package/dist/cjs/sdk/internal/utils/log.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/utils/log.js +413 -0
- package/dist/cjs/sdk/internal/utils/log.js.map +1 -0
- package/dist/cjs/sdk/internal/utils/path.d.ts +41 -0
- package/dist/cjs/sdk/internal/utils/path.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/utils/path.js +115 -0
- package/dist/cjs/sdk/internal/utils/path.js.map +1 -0
- package/dist/cjs/sdk/internal/utils/query-string.d.ts +21 -0
- package/dist/cjs/sdk/internal/utils/query-string.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/utils/query-string.js +128 -0
- package/dist/cjs/sdk/internal/utils/query-string.js.map +1 -0
- package/dist/cjs/sdk/internal/utils/query.d.ts +2 -0
- package/dist/cjs/sdk/internal/utils/query.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/utils/query.js +12 -0
- package/dist/cjs/sdk/internal/utils/query.js.map +1 -0
- package/dist/cjs/sdk/internal/utils/sleep.d.ts +2 -0
- package/dist/cjs/sdk/internal/utils/sleep.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/utils/sleep.js +7 -0
- package/dist/cjs/sdk/internal/utils/sleep.js.map +1 -0
- package/dist/cjs/sdk/internal/utils/uuid.d.ts +5 -0
- package/dist/cjs/sdk/internal/utils/uuid.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/utils/uuid.js +19 -0
- package/dist/cjs/sdk/internal/utils/uuid.js.map +1 -0
- package/dist/cjs/sdk/internal/utils/uuid7.d.ts +6 -0
- package/dist/cjs/sdk/internal/utils/uuid7.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/utils/uuid7.js +35 -0
- package/dist/cjs/sdk/internal/utils/uuid7.js.map +1 -0
- package/dist/cjs/sdk/internal/utils/values.d.ts +18 -0
- package/dist/cjs/sdk/internal/utils/values.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/utils/values.js +112 -0
- package/dist/cjs/sdk/internal/utils/values.js.map +1 -0
- package/dist/cjs/sdk/internal/utils.d.ts +8 -0
- package/dist/cjs/sdk/internal/utils.d.ts.map +1 -0
- package/dist/cjs/sdk/internal/utils.js +25 -0
- package/dist/cjs/sdk/internal/utils.js.map +1 -0
- package/dist/cjs/sdk/pagination.d.ts +3 -0
- package/dist/cjs/sdk/pagination.d.ts.map +1 -0
- package/dist/cjs/sdk/pagination.js +20 -0
- package/dist/cjs/sdk/pagination.js.map +1 -0
- package/dist/cjs/sdk/resource.d.ts +6 -0
- package/dist/cjs/sdk/resource.d.ts.map +1 -0
- package/dist/cjs/sdk/resource.js +12 -0
- package/dist/cjs/sdk/resource.js.map +1 -0
- package/dist/cjs/sdk/resources/images/images.d.ts +202 -0
- package/dist/cjs/sdk/resources/images/images.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/images/images.js +139 -0
- package/dist/cjs/sdk/resources/images/images.js.map +1 -0
- package/dist/cjs/sdk/resources/images/index.d.ts +5 -0
- package/dist/cjs/sdk/resources/images/index.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/images/index.js +9 -0
- package/dist/cjs/sdk/resources/images/index.js.map +1 -0
- package/dist/cjs/sdk/resources/images/uploads.d.ts +50 -0
- package/dist/cjs/sdk/resources/images/uploads.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/images/uploads.js +28 -0
- package/dist/cjs/sdk/resources/images/uploads.js.map +1 -0
- package/dist/cjs/sdk/resources/images.d.ts +2 -0
- package/dist/cjs/sdk/resources/images.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/images.js +19 -0
- package/dist/cjs/sdk/resources/images.js.map +1 -0
- package/dist/cjs/sdk/resources/index.d.ts +6 -0
- package/dist/cjs/sdk/resources/index.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/index.js +11 -0
- package/dist/cjs/sdk/resources/index.js.map +1 -0
- package/dist/cjs/sdk/resources/machines/autoresizing.d.ts +116 -0
- package/dist/cjs/sdk/resources/machines/autoresizing.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/machines/autoresizing.js +46 -0
- package/dist/cjs/sdk/resources/machines/autoresizing.js.map +1 -0
- package/dist/cjs/sdk/resources/machines/executions/executions.d.ts +341 -0
- package/dist/cjs/sdk/resources/machines/executions/executions.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/machines/executions/executions.js +161 -0
- package/dist/cjs/sdk/resources/machines/executions/executions.js.map +1 -0
- package/dist/cjs/sdk/resources/machines/executions/index.d.ts +5 -0
- package/dist/cjs/sdk/resources/machines/executions/index.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/machines/executions/index.js +9 -0
- package/dist/cjs/sdk/resources/machines/executions/index.js.map +1 -0
- package/dist/cjs/sdk/resources/machines/executions/logs.d.ts +103 -0
- package/dist/cjs/sdk/resources/machines/executions/logs.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/machines/executions/logs.js +48 -0
- package/dist/cjs/sdk/resources/machines/executions/logs.js.map +1 -0
- package/dist/cjs/sdk/resources/machines/executions.d.ts +2 -0
- package/dist/cjs/sdk/resources/machines/executions.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/machines/executions.js +19 -0
- package/dist/cjs/sdk/resources/machines/executions.js.map +1 -0
- package/dist/cjs/sdk/resources/machines/index.d.ts +9 -0
- package/dist/cjs/sdk/resources/machines/index.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/machines/index.js +13 -0
- package/dist/cjs/sdk/resources/machines/index.js.map +1 -0
- package/dist/cjs/sdk/resources/machines/machines.d.ts +497 -0
- package/dist/cjs/sdk/resources/machines/machines.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/machines/machines.js +220 -0
- package/dist/cjs/sdk/resources/machines/machines.js.map +1 -0
- package/dist/cjs/sdk/resources/machines/ssh.d.ts +175 -0
- package/dist/cjs/sdk/resources/machines/ssh.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/machines/ssh.js +89 -0
- package/dist/cjs/sdk/resources/machines/ssh.js.map +1 -0
- package/dist/cjs/sdk/resources/machines.d.ts +2 -0
- package/dist/cjs/sdk/resources/machines.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/machines.js +19 -0
- package/dist/cjs/sdk/resources/machines.js.map +1 -0
- package/dist/cjs/sdk/resources/organization/autoresizing.d.ts +87 -0
- package/dist/cjs/sdk/resources/organization/autoresizing.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/organization/autoresizing.js +38 -0
- package/dist/cjs/sdk/resources/organization/autoresizing.js.map +1 -0
- package/dist/cjs/sdk/resources/organization/index.d.ts +4 -0
- package/dist/cjs/sdk/resources/organization/index.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/organization/index.js +9 -0
- package/dist/cjs/sdk/resources/organization/index.js.map +1 -0
- package/dist/cjs/sdk/resources/organization/organization.d.ts +10 -0
- package/dist/cjs/sdk/resources/organization/organization.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/organization/organization.js +46 -0
- package/dist/cjs/sdk/resources/organization/organization.js.map +1 -0
- package/dist/cjs/sdk/resources/organization.d.ts +2 -0
- package/dist/cjs/sdk/resources/organization.d.ts.map +1 -0
- package/dist/cjs/sdk/resources/organization.js +19 -0
- package/dist/cjs/sdk/resources/organization.js.map +1 -0
- package/dist/cjs/sdk/resources.d.ts +3 -0
- package/dist/cjs/sdk/resources.d.ts.map +1 -0
- package/dist/cjs/sdk/resources.js +9 -0
- package/dist/cjs/sdk/resources.js.map +1 -0
- package/dist/cjs/sdk/uploads.d.ts +2 -0
- package/dist/cjs/sdk/uploads.d.ts.map +1 -0
- package/dist/cjs/sdk/uploads.js +7 -0
- package/dist/cjs/sdk/uploads.js.map +1 -0
- package/dist/cjs/sdk/version.d.ts +2 -0
- package/dist/cjs/sdk/version.d.ts.map +1 -0
- package/dist/cjs/sdk/version.js +6 -0
- package/dist/cjs/sdk/version.js.map +1 -0
- package/dist/esm/bin.d.ts +3 -0
- package/dist/esm/bin.d.ts.map +1 -0
- package/dist/esm/bin.js +5 -0
- package/dist/esm/bin.js.map +1 -0
- package/dist/esm/cli/completions.d.ts +6 -0
- package/dist/esm/cli/completions.d.ts.map +1 -0
- package/dist/esm/cli/completions.js +8 -0
- package/dist/esm/cli/completions.js.map +1 -0
- package/dist/esm/cli/credentials.d.ts +169 -0
- package/dist/esm/cli/credentials.d.ts.map +1 -0
- package/dist/esm/cli/credentials.js +614 -0
- package/dist/esm/cli/credentials.js.map +1 -0
- package/dist/esm/cli/keychain.d.ts +34 -0
- package/dist/esm/cli/keychain.d.ts.map +1 -0
- package/dist/esm/cli/keychain.js +237 -0
- package/dist/esm/cli/keychain.js.map +1 -0
- package/dist/esm/cli/list.d.ts +23 -0
- package/dist/esm/cli/list.d.ts.map +1 -0
- package/dist/esm/cli/list.js +27 -0
- package/dist/esm/cli/list.js.map +1 -0
- package/dist/esm/cli/login.d.ts +137 -0
- package/dist/esm/cli/login.d.ts.map +1 -0
- package/dist/esm/cli/login.js +2053 -0
- package/dist/esm/cli/login.js.map +1 -0
- package/dist/esm/cli/runtime.d.ts +98 -0
- package/dist/esm/cli/runtime.d.ts.map +1 -0
- package/dist/esm/cli/runtime.js +1345 -0
- package/dist/esm/cli/runtime.js.map +1 -0
- package/dist/esm/cli/toon.d.ts +6 -0
- package/dist/esm/cli/toon.d.ts.map +1 -0
- package/dist/esm/cli/toon.js +161 -0
- package/dist/esm/cli/toon.js.map +1 -0
- package/dist/esm/cli/upgrade.d.ts +28 -0
- package/dist/esm/cli/upgrade.d.ts.map +1 -0
- package/dist/esm/cli/upgrade.js +1186 -0
- package/dist/esm/cli/upgrade.js.map +1 -0
- package/dist/esm/commands/index.d.ts +47 -0
- package/dist/esm/commands/index.d.ts.map +1 -0
- package/dist/esm/commands/index.js +1195 -0
- package/dist/esm/commands/index.js.map +1 -0
- package/dist/esm/custom/browser-routing.d.ts +4 -0
- package/dist/esm/custom/browser-routing.d.ts.map +1 -0
- package/dist/esm/custom/browser-routing.js +47 -0
- package/dist/esm/custom/browser-routing.js.map +1 -0
- package/dist/esm/custom/images.d.ts +6 -0
- package/dist/esm/custom/images.d.ts.map +1 -0
- package/dist/esm/custom/images.js +68 -0
- package/dist/esm/custom/images.js.map +1 -0
- package/dist/esm/custom/machines.d.ts +20 -0
- package/dist/esm/custom/machines.d.ts.map +1 -0
- package/dist/esm/custom/machines.js +76 -0
- package/dist/esm/custom/machines.js.map +1 -0
- package/dist/esm/custom/ssh-picker.d.ts +19 -0
- package/dist/esm/custom/ssh-picker.d.ts.map +1 -0
- package/dist/esm/custom/ssh-picker.js +149 -0
- package/dist/esm/custom/ssh-picker.js.map +1 -0
- package/dist/esm/custom/ssh.d.ts +18 -0
- package/dist/esm/custom/ssh.d.ts.map +1 -0
- package/dist/esm/custom/ssh.js +147 -0
- package/dist/esm/custom/ssh.js.map +1 -0
- package/dist/esm/feedback/bundle.d.ts +41 -0
- package/dist/esm/feedback/bundle.d.ts.map +1 -0
- package/dist/esm/feedback/bundle.js +83 -0
- package/dist/esm/feedback/bundle.js.map +1 -0
- package/dist/esm/feedback/command.d.ts +6 -0
- package/dist/esm/feedback/command.d.ts.map +1 -0
- package/dist/esm/feedback/command.js +92 -0
- package/dist/esm/feedback/command.js.map +1 -0
- package/dist/esm/feedback/diagnostics.d.ts +30 -0
- package/dist/esm/feedback/diagnostics.d.ts.map +1 -0
- package/dist/esm/feedback/diagnostics.js +270 -0
- package/dist/esm/feedback/diagnostics.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/sdk/api-promise.d.ts +31 -0
- package/dist/esm/sdk/api-promise.d.ts.map +1 -0
- package/dist/esm/sdk/api-promise.js +68 -0
- package/dist/esm/sdk/api-promise.js.map +1 -0
- package/dist/esm/sdk/client.d.ts +267 -0
- package/dist/esm/sdk/client.d.ts.map +1 -0
- package/dist/esm/sdk/client.js +925 -0
- package/dist/esm/sdk/client.js.map +1 -0
- package/dist/esm/sdk/core/api-promise.d.ts +3 -0
- package/dist/esm/sdk/core/api-promise.d.ts.map +1 -0
- package/dist/esm/sdk/core/api-promise.js +3 -0
- package/dist/esm/sdk/core/api-promise.js.map +1 -0
- package/dist/esm/sdk/core/error.d.ts +46 -0
- package/dist/esm/sdk/core/error.d.ts.map +1 -0
- package/dist/esm/sdk/core/error.js +119 -0
- package/dist/esm/sdk/core/error.js.map +1 -0
- package/dist/esm/sdk/core/pagination.d.ts +38 -0
- package/dist/esm/sdk/core/pagination.d.ts.map +1 -0
- package/dist/esm/sdk/core/pagination.js +92 -0
- package/dist/esm/sdk/core/pagination.js.map +1 -0
- package/dist/esm/sdk/core/resource.d.ts +2 -0
- package/dist/esm/sdk/core/resource.d.ts.map +1 -0
- package/dist/esm/sdk/core/resource.js +3 -0
- package/dist/esm/sdk/core/resource.js.map +1 -0
- package/dist/esm/sdk/core/uploads.d.ts +3 -0
- package/dist/esm/sdk/core/uploads.d.ts.map +1 -0
- package/dist/esm/sdk/core/uploads.js +3 -0
- package/dist/esm/sdk/core/uploads.js.map +1 -0
- package/dist/esm/sdk/error.d.ts +3 -0
- package/dist/esm/sdk/error.d.ts.map +1 -0
- package/dist/esm/sdk/error.js +4 -0
- package/dist/esm/sdk/error.js.map +1 -0
- package/dist/esm/sdk/helpers/images.d.ts +31 -0
- package/dist/esm/sdk/helpers/images.d.ts.map +1 -0
- package/dist/esm/sdk/helpers/images.js +189 -0
- package/dist/esm/sdk/helpers/images.js.map +1 -0
- package/dist/esm/sdk/index.d.ts +7 -0
- package/dist/esm/sdk/index.d.ts.map +1 -0
- package/dist/esm/sdk/index.js +8 -0
- package/dist/esm/sdk/index.js.map +1 -0
- package/dist/esm/sdk/internal/builtin-types.d.ts +73 -0
- package/dist/esm/sdk/internal/builtin-types.d.ts.map +1 -0
- package/dist/esm/sdk/internal/builtin-types.js +3 -0
- package/dist/esm/sdk/internal/builtin-types.js.map +1 -0
- package/dist/esm/sdk/internal/detect-platform.d.ts +15 -0
- package/dist/esm/sdk/internal/detect-platform.d.ts.map +1 -0
- package/dist/esm/sdk/internal/detect-platform.js +180 -0
- package/dist/esm/sdk/internal/detect-platform.js.map +1 -0
- package/dist/esm/sdk/internal/errors.d.ts +3 -0
- package/dist/esm/sdk/internal/errors.d.ts.map +1 -0
- package/dist/esm/sdk/internal/errors.js +36 -0
- package/dist/esm/sdk/internal/errors.js.map +1 -0
- package/dist/esm/sdk/internal/headers.d.ts +43 -0
- package/dist/esm/sdk/internal/headers.d.ts.map +1 -0
- package/dist/esm/sdk/internal/headers.js +133 -0
- package/dist/esm/sdk/internal/headers.js.map +1 -0
- package/dist/esm/sdk/internal/parse.d.ts +3 -0
- package/dist/esm/sdk/internal/parse.d.ts.map +1 -0
- package/dist/esm/sdk/internal/parse.js +3 -0
- package/dist/esm/sdk/internal/parse.js.map +1 -0
- package/dist/esm/sdk/internal/qs/formats.d.ts +7 -0
- package/dist/esm/sdk/internal/qs/formats.d.ts.map +1 -0
- package/dist/esm/sdk/internal/qs/formats.js +9 -0
- package/dist/esm/sdk/internal/qs/formats.js.map +1 -0
- package/dist/esm/sdk/internal/qs/index.d.ts +10 -0
- package/dist/esm/sdk/internal/qs/index.d.ts.map +1 -0
- package/dist/esm/sdk/internal/qs/index.js +10 -0
- package/dist/esm/sdk/internal/qs/index.js.map +1 -0
- package/dist/esm/sdk/internal/qs/stringify.d.ts +3 -0
- package/dist/esm/sdk/internal/qs/stringify.d.ts.map +1 -0
- package/dist/esm/sdk/internal/qs/stringify.js +275 -0
- package/dist/esm/sdk/internal/qs/stringify.js.map +1 -0
- package/dist/esm/sdk/internal/qs/types.d.ts +57 -0
- package/dist/esm/sdk/internal/qs/types.d.ts.map +1 -0
- package/dist/esm/sdk/internal/qs/types.js +2 -0
- package/dist/esm/sdk/internal/qs/types.js.map +1 -0
- package/dist/esm/sdk/internal/qs/utils.d.ts +15 -0
- package/dist/esm/sdk/internal/qs/utils.d.ts.map +1 -0
- package/dist/esm/sdk/internal/qs/utils.js +217 -0
- package/dist/esm/sdk/internal/qs/utils.js.map +1 -0
- package/dist/esm/sdk/internal/request-options.d.ts +33 -0
- package/dist/esm/sdk/internal/request-options.d.ts.map +1 -0
- package/dist/esm/sdk/internal/request-options.js +11 -0
- package/dist/esm/sdk/internal/request-options.js.map +1 -0
- package/dist/esm/sdk/internal/shim-types.d.ts +17 -0
- package/dist/esm/sdk/internal/shim-types.d.ts.map +1 -0
- package/dist/esm/sdk/internal/shim-types.js +3 -0
- package/dist/esm/sdk/internal/shim-types.js.map +1 -0
- package/dist/esm/sdk/internal/shims.d.ts +26 -0
- package/dist/esm/sdk/internal/shims.d.ts.map +1 -0
- package/dist/esm/sdk/internal/shims.js +85 -0
- package/dist/esm/sdk/internal/shims.js.map +1 -0
- package/dist/esm/sdk/internal/to-file.d.ts +45 -0
- package/dist/esm/sdk/internal/to-file.d.ts.map +1 -0
- package/dist/esm/sdk/internal/to-file.js +88 -0
- package/dist/esm/sdk/internal/to-file.js.map +1 -0
- package/dist/esm/sdk/internal/types.d.ts +63 -0
- package/dist/esm/sdk/internal/types.d.ts.map +1 -0
- package/dist/esm/sdk/internal/types.js +3 -0
- package/dist/esm/sdk/internal/types.js.map +1 -0
- package/dist/esm/sdk/internal/uploads.d.ts +42 -0
- package/dist/esm/sdk/internal/uploads.d.ts.map +1 -0
- package/dist/esm/sdk/internal/uploads.js +148 -0
- package/dist/esm/sdk/internal/uploads.js.map +1 -0
- package/dist/esm/sdk/internal/utils/base64.d.ts +3 -0
- package/dist/esm/sdk/internal/utils/base64.d.ts.map +1 -0
- package/dist/esm/sdk/internal/utils/base64.js +33 -0
- package/dist/esm/sdk/internal/utils/base64.js.map +1 -0
- package/dist/esm/sdk/internal/utils/body.d.ts +40 -0
- package/dist/esm/sdk/internal/utils/body.d.ts.map +1 -0
- package/dist/esm/sdk/internal/utils/body.js +140 -0
- package/dist/esm/sdk/internal/utils/body.js.map +1 -0
- package/dist/esm/sdk/internal/utils/bytes.d.ts +4 -0
- package/dist/esm/sdk/internal/utils/bytes.d.ts.map +1 -0
- package/dist/esm/sdk/internal/utils/bytes.js +26 -0
- package/dist/esm/sdk/internal/utils/bytes.js.map +1 -0
- package/dist/esm/sdk/internal/utils/env.d.ts +9 -0
- package/dist/esm/sdk/internal/utils/env.d.ts.map +1 -0
- package/dist/esm/sdk/internal/utils/env.js +18 -0
- package/dist/esm/sdk/internal/utils/env.js.map +1 -0
- package/dist/esm/sdk/internal/utils/log.d.ts +52 -0
- package/dist/esm/sdk/internal/utils/log.d.ts.map +1 -0
- package/dist/esm/sdk/internal/utils/log.js +405 -0
- package/dist/esm/sdk/internal/utils/log.js.map +1 -0
- package/dist/esm/sdk/internal/utils/path.d.ts +41 -0
- package/dist/esm/sdk/internal/utils/path.d.ts.map +1 -0
- package/dist/esm/sdk/internal/utils/path.js +108 -0
- package/dist/esm/sdk/internal/utils/path.js.map +1 -0
- package/dist/esm/sdk/internal/utils/query-string.d.ts +21 -0
- package/dist/esm/sdk/internal/utils/query-string.d.ts.map +1 -0
- package/dist/esm/sdk/internal/utils/query-string.js +125 -0
- package/dist/esm/sdk/internal/utils/query-string.js.map +1 -0
- package/dist/esm/sdk/internal/utils/query.d.ts +2 -0
- package/dist/esm/sdk/internal/utils/query.d.ts.map +1 -0
- package/dist/esm/sdk/internal/utils/query.js +9 -0
- package/dist/esm/sdk/internal/utils/query.js.map +1 -0
- package/dist/esm/sdk/internal/utils/sleep.d.ts +2 -0
- package/dist/esm/sdk/internal/utils/sleep.d.ts.map +1 -0
- package/dist/esm/sdk/internal/utils/sleep.js +3 -0
- package/dist/esm/sdk/internal/utils/sleep.js.map +1 -0
- package/dist/esm/sdk/internal/utils/uuid.d.ts +5 -0
- package/dist/esm/sdk/internal/utils/uuid.d.ts.map +1 -0
- package/dist/esm/sdk/internal/utils/uuid.js +15 -0
- package/dist/esm/sdk/internal/utils/uuid.js.map +1 -0
- package/dist/esm/sdk/internal/utils/uuid7.d.ts +6 -0
- package/dist/esm/sdk/internal/utils/uuid7.d.ts.map +1 -0
- package/dist/esm/sdk/internal/utils/uuid7.js +31 -0
- package/dist/esm/sdk/internal/utils/uuid7.js.map +1 -0
- package/dist/esm/sdk/internal/utils/values.d.ts +18 -0
- package/dist/esm/sdk/internal/utils/values.d.ts.map +1 -0
- package/dist/esm/sdk/internal/utils/values.js +94 -0
- package/dist/esm/sdk/internal/utils/values.js.map +1 -0
- package/dist/esm/sdk/internal/utils.d.ts +8 -0
- package/dist/esm/sdk/internal/utils.d.ts.map +1 -0
- package/dist/esm/sdk/internal/utils.js +9 -0
- package/dist/esm/sdk/internal/utils.js.map +1 -0
- package/dist/esm/sdk/pagination.d.ts +3 -0
- package/dist/esm/sdk/pagination.d.ts.map +1 -0
- package/dist/esm/sdk/pagination.js +4 -0
- package/dist/esm/sdk/pagination.js.map +1 -0
- package/dist/esm/sdk/resource.d.ts +6 -0
- package/dist/esm/sdk/resource.d.ts.map +1 -0
- package/dist/esm/sdk/resource.js +8 -0
- package/dist/esm/sdk/resource.js.map +1 -0
- package/dist/esm/sdk/resources/images/images.d.ts +202 -0
- package/dist/esm/sdk/resources/images/images.d.ts.map +1 -0
- package/dist/esm/sdk/resources/images/images.js +102 -0
- package/dist/esm/sdk/resources/images/images.js.map +1 -0
- package/dist/esm/sdk/resources/images/index.d.ts +5 -0
- package/dist/esm/sdk/resources/images/index.d.ts.map +1 -0
- package/dist/esm/sdk/resources/images/index.js +4 -0
- package/dist/esm/sdk/resources/images/index.js.map +1 -0
- package/dist/esm/sdk/resources/images/uploads.d.ts +50 -0
- package/dist/esm/sdk/resources/images/uploads.d.ts.map +1 -0
- package/dist/esm/sdk/resources/images/uploads.js +24 -0
- package/dist/esm/sdk/resources/images/uploads.js.map +1 -0
- package/dist/esm/sdk/resources/images.d.ts +2 -0
- package/dist/esm/sdk/resources/images.d.ts.map +1 -0
- package/dist/esm/sdk/resources/images.js +3 -0
- package/dist/esm/sdk/resources/images.js.map +1 -0
- package/dist/esm/sdk/resources/index.d.ts +6 -0
- package/dist/esm/sdk/resources/index.d.ts.map +1 -0
- package/dist/esm/sdk/resources/index.js +5 -0
- package/dist/esm/sdk/resources/index.js.map +1 -0
- package/dist/esm/sdk/resources/machines/autoresizing.d.ts +116 -0
- package/dist/esm/sdk/resources/machines/autoresizing.d.ts.map +1 -0
- package/dist/esm/sdk/resources/machines/autoresizing.js +42 -0
- package/dist/esm/sdk/resources/machines/autoresizing.js.map +1 -0
- package/dist/esm/sdk/resources/machines/executions/executions.d.ts +341 -0
- package/dist/esm/sdk/resources/machines/executions/executions.d.ts.map +1 -0
- package/dist/esm/sdk/resources/machines/executions/executions.js +124 -0
- package/dist/esm/sdk/resources/machines/executions/executions.js.map +1 -0
- package/dist/esm/sdk/resources/machines/executions/index.d.ts +5 -0
- package/dist/esm/sdk/resources/machines/executions/index.d.ts.map +1 -0
- package/dist/esm/sdk/resources/machines/executions/index.js +4 -0
- package/dist/esm/sdk/resources/machines/executions/index.js.map +1 -0
- package/dist/esm/sdk/resources/machines/executions/logs.d.ts +103 -0
- package/dist/esm/sdk/resources/machines/executions/logs.d.ts.map +1 -0
- package/dist/esm/sdk/resources/machines/executions/logs.js +44 -0
- package/dist/esm/sdk/resources/machines/executions/logs.js.map +1 -0
- package/dist/esm/sdk/resources/machines/executions.d.ts +2 -0
- package/dist/esm/sdk/resources/machines/executions.d.ts.map +1 -0
- package/dist/esm/sdk/resources/machines/executions.js +3 -0
- package/dist/esm/sdk/resources/machines/executions.js.map +1 -0
- package/dist/esm/sdk/resources/machines/index.d.ts +9 -0
- package/dist/esm/sdk/resources/machines/index.d.ts.map +1 -0
- package/dist/esm/sdk/resources/machines/index.js +6 -0
- package/dist/esm/sdk/resources/machines/index.js.map +1 -0
- package/dist/esm/sdk/resources/machines/machines.d.ts +497 -0
- package/dist/esm/sdk/resources/machines/machines.d.ts.map +1 -0
- package/dist/esm/sdk/resources/machines/machines.js +183 -0
- package/dist/esm/sdk/resources/machines/machines.js.map +1 -0
- package/dist/esm/sdk/resources/machines/ssh.d.ts +175 -0
- package/dist/esm/sdk/resources/machines/ssh.d.ts.map +1 -0
- package/dist/esm/sdk/resources/machines/ssh.js +85 -0
- package/dist/esm/sdk/resources/machines/ssh.js.map +1 -0
- package/dist/esm/sdk/resources/machines.d.ts +2 -0
- package/dist/esm/sdk/resources/machines.d.ts.map +1 -0
- package/dist/esm/sdk/resources/machines.js +3 -0
- package/dist/esm/sdk/resources/machines.js.map +1 -0
- package/dist/esm/sdk/resources/organization/autoresizing.d.ts +87 -0
- package/dist/esm/sdk/resources/organization/autoresizing.d.ts.map +1 -0
- package/dist/esm/sdk/resources/organization/autoresizing.js +34 -0
- package/dist/esm/sdk/resources/organization/autoresizing.js.map +1 -0
- package/dist/esm/sdk/resources/organization/index.d.ts +4 -0
- package/dist/esm/sdk/resources/organization/index.d.ts.map +1 -0
- package/dist/esm/sdk/resources/organization/index.js +4 -0
- package/dist/esm/sdk/resources/organization/index.js.map +1 -0
- package/dist/esm/sdk/resources/organization/organization.d.ts +10 -0
- package/dist/esm/sdk/resources/organization/organization.d.ts.map +1 -0
- package/dist/esm/sdk/resources/organization/organization.js +9 -0
- package/dist/esm/sdk/resources/organization/organization.js.map +1 -0
- package/dist/esm/sdk/resources/organization.d.ts +2 -0
- package/dist/esm/sdk/resources/organization.d.ts.map +1 -0
- package/dist/esm/sdk/resources/organization.js +3 -0
- package/dist/esm/sdk/resources/organization.js.map +1 -0
- package/dist/esm/sdk/resources.d.ts +3 -0
- package/dist/esm/sdk/resources.d.ts.map +1 -0
- package/dist/esm/sdk/resources.js +3 -0
- package/dist/esm/sdk/resources.js.map +1 -0
- package/dist/esm/sdk/uploads.d.ts +2 -0
- package/dist/esm/sdk/uploads.d.ts.map +1 -0
- package/dist/esm/sdk/uploads.js +3 -0
- package/dist/esm/sdk/uploads.js.map +1 -0
- package/dist/esm/sdk/version.d.ts +2 -0
- package/dist/esm/sdk/version.d.ts.map +1 -0
- package/dist/esm/sdk/version.js +3 -0
- package/dist/esm/sdk/version.js.map +1 -0
- package/man/dedalus-completion.1 +35 -0
- package/man/dedalus-images-create.1 +126 -0
- package/man/dedalus-images-delete.1 +102 -0
- package/man/dedalus-images-import.1 +34 -0
- package/man/dedalus-images-list.1 +105 -0
- package/man/dedalus-images-retrieve.1 +102 -0
- package/man/dedalus-images-update.1 +108 -0
- package/man/dedalus-images-uploads-create.1 +112 -0
- package/man/dedalus-login.1 +47 -0
- package/man/dedalus-logout.1 +28 -0
- package/man/dedalus-machines-autoresizing-retrieve.1 +104 -0
- package/man/dedalus-machines-autoresizing-update.1 +116 -0
- package/man/dedalus-machines-create.1 +119 -0
- package/man/dedalus-machines-delete.1 +107 -0
- package/man/dedalus-machines-executions-create.1 +120 -0
- package/man/dedalus-machines-executions-delete.1 +108 -0
- package/man/dedalus-machines-executions-events.1 +118 -0
- package/man/dedalus-machines-executions-list.1 +114 -0
- package/man/dedalus-machines-executions-logs-create-token.1 +108 -0
- package/man/dedalus-machines-executions-logs-retrieve.1 +108 -0
- package/man/dedalus-machines-executions-output.1 +108 -0
- package/man/dedalus-machines-executions-retrieve.1 +108 -0
- package/man/dedalus-machines-list.1 +108 -0
- package/man/dedalus-machines-reboot.1 +107 -0
- package/man/dedalus-machines-resize.1 +110 -0
- package/man/dedalus-machines-retrieve.1 +104 -0
- package/man/dedalus-machines-sleep.1 +104 -0
- package/man/dedalus-machines-ssh-create.1 +108 -0
- package/man/dedalus-machines-ssh-delete.1 +108 -0
- package/man/dedalus-machines-ssh-list.1 +114 -0
- package/man/dedalus-machines-ssh-retrieve.1 +108 -0
- package/man/dedalus-machines-update.1 +110 -0
- package/man/dedalus-machines-wake.1 +104 -0
- package/man/dedalus-organization-autoresizing-retrieve.1 +99 -0
- package/man/dedalus-organization-autoresizing-update.1 +111 -0
- package/man/dedalus-uninstall.1 +23 -0
- package/man/dedalus-upgrade.1 +32 -0
- package/man/dedalus.1 +222 -0
- package/package.json +88 -4
|
@@ -0,0 +1,1191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addUpgradeCommands = exports.notifyIfOutdated = void 0;
|
|
4
|
+
// Generated by the Scalar SDK Generator. Edits here are overwritten on the next generation.
|
|
5
|
+
const node_child_process_1 = require("node:child_process");
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_os_1 = require("node:os");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
/**
|
|
11
|
+
* Release coordinates and the per-manager upgrade commands, fixed at generation time.
|
|
12
|
+
*
|
|
13
|
+
* Injected as one JSON literal rather than field-by-field interpolation: `JSON.stringify` is the
|
|
14
|
+
* escaper, so a repository or package name carrying a quote or a backslash cannot terminate a
|
|
15
|
+
* string literal in this file. Absent entries mean the config does not publish that channel.
|
|
16
|
+
*/
|
|
17
|
+
const RELEASE = {
|
|
18
|
+
repo: 'dedalus-labs/dedalus-cli',
|
|
19
|
+
npmPackage: 'dedalus-cli',
|
|
20
|
+
brewFormula: 'dedalus',
|
|
21
|
+
windowsArchive: true,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Whether this CLI registers `login`/`logout`, fixed at generation time. `uninstall` names `logout` as
|
|
25
|
+
* the way to clear a stored credential, and a CLI with no sign-in has neither the command nor the
|
|
26
|
+
* credential to clear.
|
|
27
|
+
*/
|
|
28
|
+
const HAS_LOGIN = true;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the release ships `install.sh`, fixed at generation time. That script can append a PATH line
|
|
31
|
+
* to a shell startup file, which `uninstall` then names as something removing the executable leaves.
|
|
32
|
+
*/
|
|
33
|
+
const HAS_INSTALL_SCRIPT = true;
|
|
34
|
+
/**
|
|
35
|
+
* npm's package-name charset. The name reaches a spawned argument list, and on Windows that list goes
|
|
36
|
+
* through `cmd.exe` (a `.cmd` shim cannot be spawned without a shell), so a name outside the charset
|
|
37
|
+
* is never run. It is fixed at generation time, so this only ever rejects a config npm itself rejects.
|
|
38
|
+
*/
|
|
39
|
+
const NPM_PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/u;
|
|
40
|
+
/** Resolves symlinks, falling back to the path itself when it cannot be read, with `/` separators. */
|
|
41
|
+
const resolvedPath = (path) => {
|
|
42
|
+
let resolved = path;
|
|
43
|
+
try {
|
|
44
|
+
resolved = (0, node_fs_1.realpathSync)(path);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// An unreadable link target is not a reason to refuse; the unresolved path still classifies, and a
|
|
48
|
+
// replacement that cannot write it fails later with a real permission error.
|
|
49
|
+
}
|
|
50
|
+
return resolved.replace(/\\/gu, '/');
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Classifies the running executable's install method from its resolved path.
|
|
54
|
+
*
|
|
55
|
+
* There is deliberately no install receipt to read. A receipt written by one channel would be absent
|
|
56
|
+
* for every other, so the path still has to be classified — and a receipt that drifted from reality
|
|
57
|
+
* (a binary moved, or a package manager adopting the path) would be believed over the filesystem.
|
|
58
|
+
*
|
|
59
|
+
* Symlinks are resolved first because that is what distinguishes the cases: Homebrew links
|
|
60
|
+
* `/usr/local/bin/<cmd>` into its Cellar, so only the real path shows which manager owns the file.
|
|
61
|
+
*/
|
|
62
|
+
const detectInstall = (binaryName) => {
|
|
63
|
+
const executable = process.execPath;
|
|
64
|
+
// A compiled standalone executable *is* the command; running under `node`/`bun` instead means this
|
|
65
|
+
// is the npm package (or a checkout of it), and the script's own path says which manager owns it.
|
|
66
|
+
if (!runsCompiled(executable, binaryName))
|
|
67
|
+
return detectPackageInstall();
|
|
68
|
+
const path = resolvedPath(executable);
|
|
69
|
+
// `Caskroom` as well as `Cellar`, and that is not redundant with the `homebrew`/`linuxbrew` segments
|
|
70
|
+
// beside it: on an **Intel** Mac Homebrew's prefix is `/usr/local`, so a cask stages its executable
|
|
71
|
+
// at `/usr/local/Caskroom/<token>/<version>/<bin>` — a path carrying none of the other three. Without
|
|
72
|
+
// this, such an install classified as self-managed and `upgrade` renamed a downloaded binary over the
|
|
73
|
+
// file inside the Caskroom, which is admin-owned on Intel Homebrew and so writable without `sudo`.
|
|
74
|
+
// Brew went on recording the old version and reverted the binary on the next `brew upgrade`. Apple
|
|
75
|
+
// Silicon and Linux casks were caught only because `/opt/homebrew` and `/home/linuxbrew` happen to
|
|
76
|
+
// contain a matching segment.
|
|
77
|
+
if (/\/(Cellar|Caskroom|homebrew|linuxbrew)\//u.test(path) && RELEASE.brewFormula) {
|
|
78
|
+
// A cask is upgraded and removed through `--cask`. The token is the formula's own lowercased name,
|
|
79
|
+
// which is what the generated cask is declared under, so one value serves both spellings.
|
|
80
|
+
const cask = /\/Caskroom\//u.test(path);
|
|
81
|
+
const kind = cask ? ['--cask'] : [];
|
|
82
|
+
const args = ['upgrade', ...kind, RELEASE.brewFormula];
|
|
83
|
+
const brew = brewProgram(path);
|
|
84
|
+
return {
|
|
85
|
+
kind: 'managed',
|
|
86
|
+
manager: 'Homebrew',
|
|
87
|
+
// `brew update` first because `brew upgrade` reads the local clone of the tap, which Homebrew
|
|
88
|
+
// refreshes at most once a day: without it a release this command has just announced is not
|
|
89
|
+
// there yet, and `brew upgrade` reports the old version as up to date and exits 0.
|
|
90
|
+
upgrade: `brew update && brew ${args.join(' ')}`,
|
|
91
|
+
remove: `brew uninstall ${cask ? '--cask ' : ''}${RELEASE.brewFormula}`,
|
|
92
|
+
...(brew
|
|
93
|
+
? {
|
|
94
|
+
run: {
|
|
95
|
+
program: brew,
|
|
96
|
+
args,
|
|
97
|
+
refresh: ['update'],
|
|
98
|
+
installedVersions: ['list', ...kind, '--versions', RELEASE.brewFormula],
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
: {}),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// `/usr/bin` is where a distro package manager installs; the documented direct download goes to
|
|
105
|
+
// `/usr/local/bin`, so it does not land here.
|
|
106
|
+
//
|
|
107
|
+
// Fails closed, always. Falling through to `self-managed` would overwrite a file dpkg or rpm owns —
|
|
108
|
+
// the manager's database still records the shipped checksum, so `dpkg -V` reports a mismatch and the
|
|
109
|
+
// next `apt upgrade` silently reverts the binary. Nothing this config publishes installs to
|
|
110
|
+
// `/usr/bin`, so a binary found there was put there by something else and is never ours to replace.
|
|
111
|
+
if (path.startsWith('/usr/bin/')) {
|
|
112
|
+
return {
|
|
113
|
+
kind: 'managed',
|
|
114
|
+
manager: "this system's package manager",
|
|
115
|
+
upgrade: `upgrade ${binaryName} the way it was installed; ${path} is owned by a package manager, so replacing it in place would corrupt that manager's database`,
|
|
116
|
+
remove: `remove ${binaryName} with the package manager that installed ${path}`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// Windows is never replaced in place from here. Its release asset is a `.zip` rather than the
|
|
120
|
+
// `.tar.gz` the replace path unpacks, and Windows refuses to overwrite a running executable at all,
|
|
121
|
+
// so the honest answer is to name the archive and let the user put it in place. Reported as
|
|
122
|
+
// `managed` because the outcome is the same: someone else does the work.
|
|
123
|
+
if (process.platform === 'win32') {
|
|
124
|
+
return RELEASE.windowsArchive
|
|
125
|
+
? {
|
|
126
|
+
kind: 'managed',
|
|
127
|
+
manager: 'a direct download',
|
|
128
|
+
upgrade: `download the latest ${binaryName}-windows-x64.zip from https://github.com/${RELEASE.repo}/releases/latest`,
|
|
129
|
+
remove: `remove ${path}`,
|
|
130
|
+
}
|
|
131
|
+
: // No Windows archive is published for this config, so naming one would be a permanent 404.
|
|
132
|
+
// Reported rather than guessed: whatever put this executable here is what has to replace it.
|
|
133
|
+
{
|
|
134
|
+
kind: 'managed',
|
|
135
|
+
manager: 'however it was installed',
|
|
136
|
+
upgrade: `reinstall ${binaryName} the way it was installed; no Windows archive is published for it to download`,
|
|
137
|
+
remove: `remove ${path}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return { kind: 'self-managed', path };
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Whether this process is the compiled standalone executable rather than the npm package under a
|
|
144
|
+
* JavaScript runtime.
|
|
145
|
+
*
|
|
146
|
+
* The executable's own name says so, unless someone renamed it (`~/bin/wdg`); then the main module
|
|
147
|
+
* says so, since `bun build --compile` serves it from Bun's embedded filesystem (`/$bunfs/…`, or
|
|
148
|
+
* `B:/~BUN/…` on Windows) and no installed package ever runs from there.
|
|
149
|
+
*/
|
|
150
|
+
const runsCompiled = (executable, binaryName) => (0, node_path_1.basename)(executable).replace(/\.exe$/iu, '') === binaryName ||
|
|
151
|
+
/^(?:\/\$bunfs\/|[A-Za-z]:[\\/]~BUN[\\/])/u.test(process.argv[1] ?? '');
|
|
152
|
+
/**
|
|
153
|
+
* The `brew` belonging to the prefix that owns `path`, or undefined when it cannot be located.
|
|
154
|
+
*
|
|
155
|
+
* Taken from the install's own prefix rather than looked up on `PATH`: a machine can carry two
|
|
156
|
+
* Homebrews (an Intel `/usr/local` one beside an Apple Silicon `/opt/homebrew` one, after a migration),
|
|
157
|
+
* and only the one whose Cellar holds this executable can upgrade it. Nothing found means the command
|
|
158
|
+
* is printed rather than run.
|
|
159
|
+
*/
|
|
160
|
+
const brewProgram = (path) => {
|
|
161
|
+
const prefix = /^(.*?)\/(?:Cellar|Caskroom)\//u.exec(path)?.[1];
|
|
162
|
+
if (!prefix)
|
|
163
|
+
return undefined;
|
|
164
|
+
const brew = `${prefix}/bin/brew`;
|
|
165
|
+
return (0, node_fs_1.existsSync)(brew) ? brew : undefined;
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Classifies the npm package, run by a JavaScript runtime rather than as a compiled executable.
|
|
169
|
+
*
|
|
170
|
+
* Which manager owns it is read off the path of the running *script*, resolved through the bin link
|
|
171
|
+
* every manager creates, because each one keeps its global packages in its own tree. npm's tree has
|
|
172
|
+
* no fixed marker — its prefix is configurable — so it is what remains once the others are ruled out,
|
|
173
|
+
* and whether it is global at all is settled by asking npm when `upgrade` actually runs.
|
|
174
|
+
*/
|
|
175
|
+
const detectPackageInstall = () => {
|
|
176
|
+
const script = resolvedPath(process.argv[1] ?? '');
|
|
177
|
+
const pkg = RELEASE.npmPackage;
|
|
178
|
+
// Not inside any `node_modules`: this is a checkout being run directly, which only git can update.
|
|
179
|
+
if (!script.includes('/node_modules/') || !pkg) {
|
|
180
|
+
// Inside a `node_modules` yet not from an npm channel this config publishes: a package manager
|
|
181
|
+
// installed it from somewhere else, so that manager's command is the only one that can change it.
|
|
182
|
+
if (script.includes('/node_modules/')) {
|
|
183
|
+
return {
|
|
184
|
+
kind: 'managed',
|
|
185
|
+
manager: 'the package manager that installed it',
|
|
186
|
+
upgrade: `reinstall ${RELEASE.repo} with the package manager that installed it; no npm package is published for it`,
|
|
187
|
+
remove: 'remove it with the package manager that installed it',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
kind: 'managed',
|
|
192
|
+
manager: 'this checkout',
|
|
193
|
+
upgrade: 'git pull && npm install',
|
|
194
|
+
remove: 'remove the checkout',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// `npx`, `bunx` and `pnpm dlx` run from a cache rather than an install, and already fetch a newer
|
|
198
|
+
// release when asked for `@latest`; there is nothing installed to upgrade. `bunx` stages each run
|
|
199
|
+
// from its own cache in current releases and from `$TMPDIR/bunx-<uid>-<pkg>@<version>` in older ones,
|
|
200
|
+
// where a scoped name nests a `/` into that directory, so both spellings are matched. pnpm keeps its
|
|
201
|
+
// dlx cache under `pnpm/dlx`, or `pnpm-cache/dlx` on Windows.
|
|
202
|
+
const runner = /\/_npx\//u.test(script)
|
|
203
|
+
? 'npx'
|
|
204
|
+
: /\/\.bun\/install\/cache\/|\/bunx-\d+-.*?\/node_modules\//u.test(script)
|
|
205
|
+
? 'bunx'
|
|
206
|
+
: /\/pnpm(?:-cache)?\/dlx\//iu.test(script)
|
|
207
|
+
? 'pnpm dlx'
|
|
208
|
+
: undefined;
|
|
209
|
+
if (runner) {
|
|
210
|
+
return {
|
|
211
|
+
kind: 'managed',
|
|
212
|
+
manager: runner,
|
|
213
|
+
upgrade: `${runner} ${pkg}@latest`,
|
|
214
|
+
remove: `nothing to remove; ${runner} keeps the package only in its cache`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// Yarn's Plug'n'Play has no `node_modules` tree: it serves each package out of a zip, in the project's
|
|
218
|
+
// `.yarn/cache` or (Yarn 4's default) the global `~/.yarn/berry/cache`, and sets `process.versions.pnp`.
|
|
219
|
+
// Either way the package is a project dependency whose version is the project's lockfile's to set,
|
|
220
|
+
// and the zip's path says nothing about which project. Yarn exports the project root as `PROJECT_CWD`
|
|
221
|
+
// for the scripts and bins it runs, which is the best name available for it.
|
|
222
|
+
if (/\.zip\/node_modules\//u.test(script) || process.versions['pnp']) {
|
|
223
|
+
const project = process.env['PROJECT_CWD'];
|
|
224
|
+
return {
|
|
225
|
+
kind: 'managed',
|
|
226
|
+
manager: `Yarn Plug'n'Play${project ? ` in ${project}` : ''}`,
|
|
227
|
+
upgrade: `upgrade ${pkg} in the package.json that lists it, with the project's Yarn`,
|
|
228
|
+
remove: `remove ${pkg} from the package.json that lists it, with the project's Yarn`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const safe = NPM_PACKAGE_NAME.test(pkg);
|
|
232
|
+
const bunRoot = /^(.*)\/install\/global\/node_modules\//u.exec(script)?.[1];
|
|
233
|
+
if (bunRoot) {
|
|
234
|
+
// Bun's own binary sits beside its global tree (`$BUN_INSTALL/bin/bun`); `bun` on PATH otherwise.
|
|
235
|
+
const local = (0, node_path_1.join)(bunRoot, 'bin', process.platform === 'win32' ? 'bun.exe' : 'bun');
|
|
236
|
+
const bun = (0, node_fs_1.existsSync)(local) ? local : 'bun';
|
|
237
|
+
return managerInstall('Bun', bun, ['add', '-g', `${pkg}@latest`], `bun remove -g ${pkg}`, safe);
|
|
238
|
+
}
|
|
239
|
+
// pnpm's global tree is `<PNPM_HOME>/global/<layout version>/`, holding its `.pnpm` store or the
|
|
240
|
+
// `node_modules` linked from it. Matched on that layout rather than on a `pnpm` segment, because
|
|
241
|
+
// `PNPM_HOME` is whatever the user set (`~/.pnpm-home`, say), and nothing else nests a numbered
|
|
242
|
+
// directory under `global` — Bun's and Yarn's trees put `node_modules` directly beneath it.
|
|
243
|
+
if (/\/\.?pnpm\/global\/|\/global\/\d+\/(?:\.pnpm|node_modules)\//iu.test(script)) {
|
|
244
|
+
return managerInstall('pnpm', 'pnpm', ['add', '-g', `${pkg}@latest`], `pnpm remove -g ${pkg}`, safe);
|
|
245
|
+
}
|
|
246
|
+
// Yarn classic keeps its global tree under `~/.config/yarn/global` and, on Windows,
|
|
247
|
+
// `%LOCALAPPDATA%\Yarn\Data\global`, hence the optional segment and the case-insensitive match.
|
|
248
|
+
if (/\/yarn\/(?:data\/)?global\/node_modules\//iu.test(script)) {
|
|
249
|
+
return managerInstall('Yarn', 'yarn', ['global', 'add', `${pkg}@latest`], `yarn global remove ${pkg}`, safe);
|
|
250
|
+
}
|
|
251
|
+
// The npm that ships beside the running `node`, which is the one that installed into this runtime's
|
|
252
|
+
// global tree; `npm` on PATH only when it is not there. It is also no less trusted than the `node`
|
|
253
|
+
// already executing this file, which the bin link's shebang resolved the same way.
|
|
254
|
+
const local = (0, node_path_1.join)((0, node_path_1.dirname)(process.execPath), process.platform === 'win32' ? 'npm.cmd' : 'npm');
|
|
255
|
+
const npm = (0, node_fs_1.existsSync)(local) ? local : 'npm';
|
|
256
|
+
const install = managerInstall('npm', npm, ['install', '-g', `${pkg}@latest`], `npm uninstall -g ${pkg}`, safe);
|
|
257
|
+
return install.kind === 'managed' && install.run
|
|
258
|
+
? { ...install, run: { ...install.run, npmGlobal: { script, pkg } } }
|
|
259
|
+
: install;
|
|
260
|
+
};
|
|
261
|
+
/** A managed install whose upgrade `upgrade` runs, unless the package name failed the charset check. */
|
|
262
|
+
const managerInstall = (manager, program, args, remove, safe) => ({
|
|
263
|
+
kind: 'managed',
|
|
264
|
+
manager,
|
|
265
|
+
upgrade: `${(0, node_path_1.basename)(program).replace(/\.(cmd|exe)$/iu, '')} ${args.join(' ')}`,
|
|
266
|
+
remove,
|
|
267
|
+
...(safe ? { run: { program, args } } : {}),
|
|
268
|
+
});
|
|
269
|
+
/**
|
|
270
|
+
* Where npm keeps global packages, or undefined when npm cannot say.
|
|
271
|
+
*
|
|
272
|
+
* Asked rather than derived, because the global prefix is user configuration (`npm config set prefix`
|
|
273
|
+
* is the documented fix for a root-owned one) and no path rule recovers it.
|
|
274
|
+
*/
|
|
275
|
+
const npmGlobalRoot = (program) => {
|
|
276
|
+
const result = spawnManager(program, ['root', '-g'], {
|
|
277
|
+
encoding: 'utf8',
|
|
278
|
+
timeout: MANAGER_QUERY_TIMEOUT_MS,
|
|
279
|
+
});
|
|
280
|
+
const root = result.status === 0 ? String(result.stdout).trim() : '';
|
|
281
|
+
return root ? resolvedPath(root) : undefined;
|
|
282
|
+
};
|
|
283
|
+
/** How long `npm root -g` may take; it reads config and prints a path, so this is generous. */
|
|
284
|
+
const MANAGER_QUERY_TIMEOUT_MS = 15_000;
|
|
285
|
+
/**
|
|
286
|
+
* Spawns a package manager with its arguments, the one way that works on each platform.
|
|
287
|
+
*
|
|
288
|
+
* macOS and Linux get an argument list and no shell. Windows package-manager shims are `.cmd` files,
|
|
289
|
+
* which Node refuses to spawn without a shell, so there the command runs through `cmd.exe` as a single
|
|
290
|
+
* line built here: passing an argument list alongside `shell: true` is deprecated (DEP0190), and
|
|
291
|
+
* Node would only join it unquoted anyway. The program is always quoted, because a profile path can
|
|
292
|
+
* carry `&`, `(` or `^` as well as spaces; every argument is a fixed word, the charset-checked package
|
|
293
|
+
* name, or the formula name, so none of them needs quoting.
|
|
294
|
+
*/
|
|
295
|
+
const spawnManager = (program, args, options) => {
|
|
296
|
+
if (process.platform !== 'win32')
|
|
297
|
+
return (0, node_child_process_1.spawnSync)(program, [...args], options);
|
|
298
|
+
// A `"` cannot be escaped inside a `cmd.exe` quoted string, and no Windows path contains one, so
|
|
299
|
+
// refusing it costs nothing and keeps the quoting sound.
|
|
300
|
+
if (program.includes('"'))
|
|
301
|
+
throw new Error(`refusing to run ${program}: its path contains a quote`);
|
|
302
|
+
return (0, node_child_process_1.spawnSync)([`"${program}"`, ...args].join(' '), { ...options, shell: true });
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* Whether `path` lies inside `directory`.
|
|
306
|
+
*
|
|
307
|
+
* Case-insensitive on Windows, whose filesystem is, and where `realpathSync` keeps whatever case the
|
|
308
|
+
* path was spelled in — so `argv[1]` reached through a PATH entry typed `c:\users` and npm's own
|
|
309
|
+
* `C:\Users` answer would otherwise never match.
|
|
310
|
+
*/
|
|
311
|
+
const isInside = (path, directory) => process.platform === 'win32'
|
|
312
|
+
? path.toLowerCase().startsWith(`${directory.toLowerCase()}/`)
|
|
313
|
+
: path.startsWith(`${directory}/`);
|
|
314
|
+
/**
|
|
315
|
+
* The package manager the project at `project` uses, read off what it has on disk: pnpm's `.pnpm`
|
|
316
|
+
* store in the script's own path, then the lockfile, and npm when nothing else is recorded.
|
|
317
|
+
*
|
|
318
|
+
* Named rather than turned into a command. The right command depends on what this cannot see — which
|
|
319
|
+
* workspace member lists the CLI, and which major version of Yarn the project runs — and a command
|
|
320
|
+
* that is wrong for either fails or edits a `package.json` other than the one that lists it.
|
|
321
|
+
*/
|
|
322
|
+
const projectManager = (project, script) => {
|
|
323
|
+
/** Whether the project root holds this file, which is how a lockfile names the manager. */
|
|
324
|
+
const has = (file) => (0, node_fs_1.existsSync)((0, node_path_1.join)(project, file));
|
|
325
|
+
if (script.includes('/node_modules/.pnpm/') || has('pnpm-lock.yaml'))
|
|
326
|
+
return 'pnpm';
|
|
327
|
+
if (has('yarn.lock'))
|
|
328
|
+
return 'Yarn';
|
|
329
|
+
if (has('bun.lock') || has('bun.lockb'))
|
|
330
|
+
return 'Bun';
|
|
331
|
+
return 'npm';
|
|
332
|
+
};
|
|
333
|
+
/**
|
|
334
|
+
* Settles what {@link detectInstall} leaves to a subprocess: whether an npm install is the global one
|
|
335
|
+
* this runtime's npm owns, a project's dependency, or another npm's global tree.
|
|
336
|
+
*
|
|
337
|
+
* npm links a project dependency and a global install the same way, so the path alone cannot tell
|
|
338
|
+
* them apart; only `npm root -g` can. Asked by `upgrade`, `upgrade --check` and `uninstall` — explicit
|
|
339
|
+
* commands, where one subprocess is cheap — and never by the daily notice, which only ever says to run
|
|
340
|
+
* `upgrade`, where this is settled before anything is done.
|
|
341
|
+
*
|
|
342
|
+
* A `package.json` beside the `node_modules` makes it a project's dependency: that project's lockfile
|
|
343
|
+
* decides its version, so neither a global upgrade nor a global uninstall is right for it — the global
|
|
344
|
+
* command would add a second copy, or remove a different one. Without one it is a global tree belonging
|
|
345
|
+
* to a different npm (Volta's per-package images, a second Node install): the global command is still
|
|
346
|
+
* right, but only *that* npm can run it, so it is printed rather than run through this one.
|
|
347
|
+
*/
|
|
348
|
+
const settledInstall = (install) => {
|
|
349
|
+
if (install.kind !== 'managed' || !install.run?.npmGlobal)
|
|
350
|
+
return install;
|
|
351
|
+
const { script, pkg } = install.run.npmGlobal;
|
|
352
|
+
const root = npmGlobalRoot(install.run.program);
|
|
353
|
+
if (!root || isInside(script, root))
|
|
354
|
+
return install;
|
|
355
|
+
const project = script.slice(0, script.indexOf('/node_modules/'));
|
|
356
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(project, 'package.json'))) {
|
|
357
|
+
const manager = projectManager(project, script);
|
|
358
|
+
return {
|
|
359
|
+
kind: 'managed',
|
|
360
|
+
manager: `${manager}, as a dependency of the project in ${project}`,
|
|
361
|
+
upgrade: `upgrade ${pkg} in the package.json that lists it, with the project's ${manager}`,
|
|
362
|
+
remove: `remove ${pkg} from the package.json that lists it, with the project's ${manager}`,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
kind: 'managed',
|
|
367
|
+
manager: `a different npm than the one beside this Node.js, into ${project}`,
|
|
368
|
+
upgrade: install.upgrade,
|
|
369
|
+
remove: install.remove,
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
/**
|
|
373
|
+
* Runs the owning manager's upgrade, with its output on this terminal.
|
|
374
|
+
*
|
|
375
|
+
* Delegated rather than reproduced: the manager records what it installed, so only its own upgrade
|
|
376
|
+
* leaves that record true. A spawn that fails outright (the manager is gone from PATH) prints the
|
|
377
|
+
* command so the user can run it themselves. Returns whether the manager ran and succeeded.
|
|
378
|
+
*/
|
|
379
|
+
const runManagerUpgrade = (binaryName, install, run) => {
|
|
380
|
+
process.stderr.write(`${binaryName} was installed with ${install.manager}. Upgrading with:\n ${install.upgrade}\n\n`);
|
|
381
|
+
// A failed refresh does not stop the upgrade: the manager has said why on this terminal, the local
|
|
382
|
+
// copy may already have the release, and `confirmManagerUpgrade` catches the case where it does not.
|
|
383
|
+
if (run.refresh)
|
|
384
|
+
spawnManager(run.program, run.refresh, { stdio: 'inherit' });
|
|
385
|
+
let result;
|
|
386
|
+
try {
|
|
387
|
+
result = spawnManager(run.program, run.args, { stdio: 'inherit' });
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
result = { error: error };
|
|
391
|
+
}
|
|
392
|
+
// `cmd.exe` reports a program it cannot find as status 9009 rather than as a spawn error, so on
|
|
393
|
+
// Windows that status is the same "the manager is not installed" case.
|
|
394
|
+
if (result.error || (process.platform === 'win32' && result.status === 9009)) {
|
|
395
|
+
const reason = result.error ? result.error.message : `${(0, node_path_1.basename)(run.program)} was not found`;
|
|
396
|
+
process.stderr.write(`error: could not run ${install.manager}: ${reason}\nRun it yourself:\n ${install.upgrade}\n`);
|
|
397
|
+
process.exitCode = 1;
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
// The manager has already said why on this terminal. Its own status is named but not passed
|
|
401
|
+
// through: npm exits 243 for a permission error and anything may exit 2, which a script reading
|
|
402
|
+
// this command's documented statuses would take for a usage error. 1 is "the command failed".
|
|
403
|
+
if (result.status !== 0) {
|
|
404
|
+
process.stderr.write(`error: ${install.manager} exited with ${result.status === null ? `signal ${result.signal}` : `status ${result.status}`}\n`);
|
|
405
|
+
process.exitCode = 1;
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
return true;
|
|
409
|
+
};
|
|
410
|
+
/**
|
|
411
|
+
* Fails an upgrade that the manager reported as a success without installing anything newer.
|
|
412
|
+
*
|
|
413
|
+
* `brew upgrade` exits 0 on a formula it believes is current, so a tap behind the release looks like a
|
|
414
|
+
* successful upgrade to a script chaining `upgrade && …`. Refreshing first cannot rule that out: the
|
|
415
|
+
* refresh can fail, and a tap published through pull requests lags until the pull request merges.
|
|
416
|
+
*
|
|
417
|
+
* The release is only asked for once the version is known not to have moved, and exiting 0 stays right
|
|
418
|
+
* when nothing newer exists. Not being able to ask is a warning rather than a failure, because the
|
|
419
|
+
* manager did succeed and most such runs are simply already current.
|
|
420
|
+
*/
|
|
421
|
+
const confirmManagerUpgrade = async (binaryName, version, install, run, query) => {
|
|
422
|
+
const result = spawnManager(run.program, query, {
|
|
423
|
+
encoding: 'utf8',
|
|
424
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
425
|
+
timeout: MANAGER_QUERY_TIMEOUT_MS,
|
|
426
|
+
});
|
|
427
|
+
// Several versions when Homebrew kept the old keg, so any other version here means one was installed.
|
|
428
|
+
const installed = result.status === 0 ? String(result.stdout).trim().split(/\s+/u).slice(1) : [];
|
|
429
|
+
if (installed.length === 0 || installed.some((entry) => entry !== version))
|
|
430
|
+
return;
|
|
431
|
+
let tag;
|
|
432
|
+
try {
|
|
433
|
+
tag = await withTimeout((signal) => latestTag(signal), UPGRADE_CHECK_TIMEOUT_MS, 'the release check');
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
process.stderr.write(`warning: could not confirm the upgrade: ${error.message}\n`);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (!isNewerRelease(tag, version))
|
|
440
|
+
return;
|
|
441
|
+
process.stderr.write(`error: ${install.manager} still has ${binaryName} ${version}, but ${tag} is released.\n` +
|
|
442
|
+
`The release may not have reached its tap yet. Try again later, or run:\n ${install.upgrade}\n`);
|
|
443
|
+
process.exitCode = 1;
|
|
444
|
+
};
|
|
445
|
+
/** Maps this machine onto the release asset built for it, matching the names the release step packs. */
|
|
446
|
+
const detectTarget = () => {
|
|
447
|
+
const platforms = { darwin: 'darwin', linux: 'linux' };
|
|
448
|
+
const architectures = { x64: 'x64', arm64: 'arm64' };
|
|
449
|
+
const os = platforms[process.platform];
|
|
450
|
+
const arch = architectures[process.arch];
|
|
451
|
+
if (!os || !arch) {
|
|
452
|
+
throw new Error(`no release binary is published for ${process.platform}/${process.arch}`);
|
|
453
|
+
}
|
|
454
|
+
return `${os}-${arch}`;
|
|
455
|
+
};
|
|
456
|
+
/** Latest published release tag, read from the GitHub API so the command can report the version. */
|
|
457
|
+
const latestTag = async (signal) => {
|
|
458
|
+
const response = await fetch(`https://api.github.com/repos/${RELEASE.repo}/releases/latest`, {
|
|
459
|
+
// `connection: close` because the abort below bounds the *request*, not the process. Node keeps
|
|
460
|
+
// a pooled keep-alive socket referenced after the response resolves, so an unawaited check still
|
|
461
|
+
// held the process open for seconds after the command's own work finished — the delay this is
|
|
462
|
+
// supposed to have no part in. Bun does not exhibit it; the npm channel runs on Node.
|
|
463
|
+
headers: { accept: 'application/vnd.github+json', connection: 'close' },
|
|
464
|
+
...(signal ? { signal } : {}),
|
|
465
|
+
});
|
|
466
|
+
if (!response.ok) {
|
|
467
|
+
// An unauthenticated read past GitHub's per-address limit is refused with 403 or 429, not with the
|
|
468
|
+
// release missing. Said as such, because the status alone reads like a broken release.
|
|
469
|
+
if (response.status === 403 || response.status === 429) {
|
|
470
|
+
throw new Error(`GitHub refused the release lookup (HTTP ${response.status}), usually the unauthenticated API rate limit for this network; try again later`);
|
|
471
|
+
}
|
|
472
|
+
throw new Error(`could not read the latest release (HTTP ${response.status})`);
|
|
473
|
+
}
|
|
474
|
+
// Capped like the archive read is. A release payload is a few kilobytes of JSON, and this is the
|
|
475
|
+
// request the *daily notifier* makes unasked — so a redirect or captive proxy answering with an
|
|
476
|
+
// endless body inside the deadline would buffer it into a CLI the user only ran to do something
|
|
477
|
+
// else. `json()` offers no way to bound it, so the bytes are read first and parsed after.
|
|
478
|
+
const body = JSON.parse((await readCapped(response, response.url || 'the latest release', MAX_METADATA_BYTES)).toString('utf8'));
|
|
479
|
+
if (typeof body.tag_name !== 'string' || body.tag_name.length === 0) {
|
|
480
|
+
throw new Error('the latest release has no tag');
|
|
481
|
+
}
|
|
482
|
+
// Validated here, not only where a *cached* tag is printed. This value goes straight into a download
|
|
483
|
+
// URL and onto the terminal, and the same reasoning applies to both paths: a git refname cannot hold
|
|
484
|
+
// a control character, so one that does was not written by a release — and writing it raw would let
|
|
485
|
+
// cursor-control sequences rewrite the surrounding lines and forge an instruction that appears to
|
|
486
|
+
// come from the CLI itself. Checking the cache and trusting the live response left the guard on the
|
|
487
|
+
// path that had already been through it and off the path the value arrives by.
|
|
488
|
+
if (!RELEASE_TAG.test(body.tag_name)) {
|
|
489
|
+
throw new Error('the latest release has an unusable tag');
|
|
490
|
+
}
|
|
491
|
+
return body.tag_name;
|
|
492
|
+
};
|
|
493
|
+
/**
|
|
494
|
+
* Fetches one release asset, bounded by both a deadline and a size cap.
|
|
495
|
+
*
|
|
496
|
+
* Used for the archive and for `checksums.txt`, so both halves of the integrity check are bounded the
|
|
497
|
+
* same way — a release that answers one of them slowly cannot leave the command waiting indefinitely.
|
|
498
|
+
*/
|
|
499
|
+
const download = async (url) =>
|
|
500
|
+
// The *whole* read runs inside the deadline, headers and body alike. Bounding only the `fetch` call
|
|
501
|
+
// leaves the body unbounded: a proxy that answers 200 and then stops sending bytes resolves the
|
|
502
|
+
// response, the timer is cleared, and the command hangs forever on the read — the exact failure
|
|
503
|
+
// this exists to prevent, one step later than where it was first looked for.
|
|
504
|
+
withTimeout(async (signal) => {
|
|
505
|
+
const response = await fetch(url, { signal });
|
|
506
|
+
if (!response.ok)
|
|
507
|
+
throw new Error(`could not download ${url} (HTTP ${response.status})`);
|
|
508
|
+
return readCapped(response, url);
|
|
509
|
+
}, DOWNLOAD_TIMEOUT_MS, url);
|
|
510
|
+
/**
|
|
511
|
+
* Reads a response body, refusing one larger than {@link MAX_DOWNLOAD_BYTES}.
|
|
512
|
+
*
|
|
513
|
+
* Read chunk by chunk with a running total rather than through `arrayBuffer()`: that buffers the
|
|
514
|
+
* whole body *before* any length can be checked, so a cap applied afterwards has already paid the
|
|
515
|
+
* memory it was meant to refuse. `content-length` is checked first where the server sends one,
|
|
516
|
+
* because that refuses the transfer before a byte of it arrives — but it is a claim, not a
|
|
517
|
+
* guarantee, so the running total is what actually enforces the bound.
|
|
518
|
+
*
|
|
519
|
+
* Buffered whole once read, because the checksum is over the whole archive. A compiled executable
|
|
520
|
+
* for one platform is tens of megabytes, so the cap is loose enough never to reject a real asset.
|
|
521
|
+
*/
|
|
522
|
+
const readCapped = async (response, url, limit = MAX_DOWNLOAD_BYTES) => {
|
|
523
|
+
// Reported in megabytes against whichever limit is in force, since both are whole megabytes. A unit
|
|
524
|
+
// switch would be dead code today; add one here if a sub-megabyte cap is ever introduced.
|
|
525
|
+
/** The refusal, named once because the declared length and the running total both raise it. */
|
|
526
|
+
const tooLarge = () => new Error(`${url} is larger than the ${Math.round(limit / 1_000_000)}MB limit`);
|
|
527
|
+
const declared = Number(response.headers.get('content-length') ?? '');
|
|
528
|
+
if (Number.isFinite(declared) && declared > limit)
|
|
529
|
+
throw tooLarge();
|
|
530
|
+
// No body to stream (a HEAD-like response, or a stub in a test) leaves `arrayBuffer` as the only
|
|
531
|
+
// reader; it is bounded by the same cap on the way out.
|
|
532
|
+
if (!response.body) {
|
|
533
|
+
const whole = Buffer.from(await response.arrayBuffer());
|
|
534
|
+
if (whole.byteLength > limit)
|
|
535
|
+
throw tooLarge();
|
|
536
|
+
return whole;
|
|
537
|
+
}
|
|
538
|
+
const chunks = [];
|
|
539
|
+
let total = 0;
|
|
540
|
+
const reader = response.body.getReader();
|
|
541
|
+
for (;;) {
|
|
542
|
+
const { done, value } = await reader.read();
|
|
543
|
+
if (done)
|
|
544
|
+
break;
|
|
545
|
+
total += value.byteLength;
|
|
546
|
+
if (total > limit) {
|
|
547
|
+
// Released so the connection is torn down rather than left draining in the background.
|
|
548
|
+
await reader.cancel().catch(() => { });
|
|
549
|
+
throw tooLarge();
|
|
550
|
+
}
|
|
551
|
+
chunks.push(Buffer.from(value));
|
|
552
|
+
}
|
|
553
|
+
return Buffer.concat(chunks);
|
|
554
|
+
};
|
|
555
|
+
/** Upper bound on a release asset, so a wrong URL cannot buffer an unbounded body into memory. */
|
|
556
|
+
const MAX_DOWNLOAD_BYTES = 250_000_000;
|
|
557
|
+
/**
|
|
558
|
+
* Upper bound on an API read, which is the release JSON and nothing else.
|
|
559
|
+
*
|
|
560
|
+
* Far tighter than the asset cap because the shape is known: a `releases/latest` payload is a few
|
|
561
|
+
* kilobytes, and generous room for release notes still leaves this three orders of magnitude below
|
|
562
|
+
* the archive bound. This is the request the daily check makes without being asked, so it is the one
|
|
563
|
+
* that most needs a bound the user never has to think about.
|
|
564
|
+
*/
|
|
565
|
+
const MAX_METADATA_BYTES = 2_000_000;
|
|
566
|
+
/** How long the downloaded executable may take to answer `--version` before it counts as broken. */
|
|
567
|
+
const VERSION_PROBE_TIMEOUT_MS = 15_000;
|
|
568
|
+
/** Long enough for a large archive on a slow connection, short enough to fail rather than hang. */
|
|
569
|
+
const DOWNLOAD_TIMEOUT_MS = 120_000;
|
|
570
|
+
/** How long `upgrade` waits on the release API before giving up; it is interactive, so it is short. */
|
|
571
|
+
const UPGRADE_CHECK_TIMEOUT_MS = 10_000;
|
|
572
|
+
/**
|
|
573
|
+
* Runs one request under a deadline, so a stalled connection fails instead of hanging.
|
|
574
|
+
*
|
|
575
|
+
* `fetch` has no default timeout, and a captive portal or a stalled proxy completes the handshake
|
|
576
|
+
* and then never answers — which left `upgrade` sitting with nothing on screen, indefinitely, while
|
|
577
|
+
* the daily notifier on the same machine aborted after two seconds. `.agents/security.md` asks for
|
|
578
|
+
* every request a generated runtime makes to be bounded.
|
|
579
|
+
*/
|
|
580
|
+
const withTimeout = async (run, ms, what) => {
|
|
581
|
+
const controller = new AbortController();
|
|
582
|
+
const timer = setTimeout(() => controller.abort(), ms);
|
|
583
|
+
try {
|
|
584
|
+
return await run(controller.signal);
|
|
585
|
+
}
|
|
586
|
+
catch (error) {
|
|
587
|
+
// `AbortError` names the signal, not the cause, so it is reported as what the user experienced.
|
|
588
|
+
if (controller.signal.aborted)
|
|
589
|
+
throw new Error(`${what} timed out after ${Math.round(ms / 1000)}s`);
|
|
590
|
+
throw error;
|
|
591
|
+
}
|
|
592
|
+
finally {
|
|
593
|
+
clearTimeout(timer);
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
/**
|
|
597
|
+
* Replaces this executable with the one from `tag`, verified against the release's checksums.
|
|
598
|
+
*
|
|
599
|
+
* The integrity check the release's own `checksums.txt` exists for, with bounds on both reads: the archive and
|
|
600
|
+
* the checksum file come from one release, so this catches a corrupted or truncated download rather
|
|
601
|
+
* than a compromised release.
|
|
602
|
+
*/
|
|
603
|
+
const replaceExecutable = async (executable, tag, binaryName) => {
|
|
604
|
+
const asset = `${binaryName}-${detectTarget()}.tar.gz`;
|
|
605
|
+
const base = `https://github.com/${RELEASE.repo}/releases/download/${tag}`;
|
|
606
|
+
const archive = await download(`${base}/${asset}`);
|
|
607
|
+
const checksums = (await download(`${base}/checksums.txt`)).toString('utf8');
|
|
608
|
+
// Same two-field `sha256sum` format the release publishes: "<hash> <file>".
|
|
609
|
+
const expected = checksums
|
|
610
|
+
.split('\n')
|
|
611
|
+
.map((line) => line.trim().split(/\s+/u))
|
|
612
|
+
.find((fields) => fields[1] === asset)?.[0];
|
|
613
|
+
if (!expected)
|
|
614
|
+
throw new Error(`the release publishes no checksum for ${asset}`);
|
|
615
|
+
const actual = (0, node_crypto_1.createHash)('sha256').update(archive).digest('hex');
|
|
616
|
+
if (actual !== expected.toLowerCase()) {
|
|
617
|
+
throw new Error(`checksum mismatch for ${asset} (expected ${expected}, got ${actual})`);
|
|
618
|
+
}
|
|
619
|
+
const staging = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), `${binaryName}-upgrade-`));
|
|
620
|
+
// Written beside the target rather than in the temp dir, so the final rename is within one
|
|
621
|
+
// filesystem and therefore atomic — a cross-device rename would fall back to a copy that can be
|
|
622
|
+
// interrupted halfway, leaving a truncated executable on the user's PATH.
|
|
623
|
+
const pending = (0, node_path_1.join)((0, node_path_1.dirname)(executable), `.${binaryName}.upgrade`);
|
|
624
|
+
try {
|
|
625
|
+
const archivePath = (0, node_path_1.join)(staging, asset);
|
|
626
|
+
(0, node_fs_1.writeFileSync)(archivePath, archive);
|
|
627
|
+
// `tar` is present on every platform this upgrades on (macOS and Linux); Windows self-upgrades
|
|
628
|
+
// are unreachable because its asset is a `.zip` no release publishes a `.tar.gz` for.
|
|
629
|
+
//
|
|
630
|
+
// The child's PATH is pinned, and resolving `tar` absolutely is not enough on its own: GNU tar
|
|
631
|
+
// implements `-z` by *running* `gzip`, looked up through the inherited PATH (verified against GNU
|
|
632
|
+
// tar 1.35). Under `sudo` on a system whose sudoers has no `secure_path` — several distributions
|
|
633
|
+
// ship none — the user's own `gzip` would then run as root, which is the hole pinning `tar` was
|
|
634
|
+
// meant to close, one process along. bsdtar on macOS decompresses in-process and never looks.
|
|
635
|
+
// The rest of the environment is passed through, so locale and proxy settings still apply.
|
|
636
|
+
(0, node_child_process_1.execFileSync)(tarProgram(), ['-xzf', archivePath, '-C', staging], {
|
|
637
|
+
env: { ...process.env, PATH: TRUSTED_TOOL_PATH },
|
|
638
|
+
});
|
|
639
|
+
const extracted = (0, node_path_1.join)(staging, binaryName);
|
|
640
|
+
// The archive layout is a release-time contract this cannot verify ahead of time, and a bare
|
|
641
|
+
// ENOENT from the read below matches none of the guidance `upgrade` prints, so it is checked
|
|
642
|
+
// here, naming the archive rather than letting a bare ENOENT surface from the read below.
|
|
643
|
+
if (!(0, node_fs_1.existsSync)(extracted)) {
|
|
644
|
+
throw new Error(`${asset} did not contain the expected ${binaryName} executable`);
|
|
645
|
+
}
|
|
646
|
+
// `wx` rather than a plain write: the path is a fixed, predictable name in the directory the real
|
|
647
|
+
// executable lives in, and this command tells the user to re-run it under `sudo`. A plain
|
|
648
|
+
// `writeFileSync` follows a symlink, so anyone who can create that one name in a group-writable
|
|
649
|
+
// install directory chooses a file for root to overwrite with content they also control. `wx` is
|
|
650
|
+
// `O_CREAT|O_EXCL`, which refuses an existing file and a symlink alike, so the only way past it is
|
|
651
|
+
// a live race rather than a planted link.
|
|
652
|
+
//
|
|
653
|
+
// Unlinked first, because `wx` refuses a *stale* file too. The `finally` below cleans up after this
|
|
654
|
+
// process, but a run killed outright — SIGKILL, OOM, power loss — leaves the name behind with no
|
|
655
|
+
// cleanup having run, and the next `upgrade` would then fail with a bare `EEXIST` naming a hidden
|
|
656
|
+
// dotfile the user has no reason to know about. That message matches none of the guidance this
|
|
657
|
+
// command prints. Removing the link rather than following it keeps the property: a name re-created
|
|
658
|
+
// between these two lines makes the write fail, which is the right outcome.
|
|
659
|
+
(0, node_fs_1.rmSync)(pending, { force: true });
|
|
660
|
+
(0, node_fs_1.writeFileSync)(pending, (0, node_fs_1.readFileSync)(extracted), { flag: 'wx' });
|
|
661
|
+
(0, node_fs_1.chmodSync)(pending, 0o755);
|
|
662
|
+
// Run before it replaces anything, exactly as `install.sh` does. The checksum proves the archive
|
|
663
|
+
// is the one the release published; it says nothing about whether this machine can execute what
|
|
664
|
+
// came out of it — a 32-bit userland on a 64-bit kernel reports a 64-bit `uname -m`, and a release
|
|
665
|
+
// can simply be built wrong. Replacing the working binary with one that cannot run takes `upgrade`
|
|
666
|
+
// with it, so the tool that would recover the install is the one just broken. Failing here leaves
|
|
667
|
+
// the old executable in place, and the `finally` below removes the staged file.
|
|
668
|
+
//
|
|
669
|
+
// The staged path is absolute and `execFileSync` takes no shell, so nothing here is interpreted.
|
|
670
|
+
// Output is discarded: this asks whether the binary runs, not what it says. The environment is the
|
|
671
|
+
// real one, not the pinned `PATH` the extraction uses — that pin exists because `tar` resolves
|
|
672
|
+
// `gzip` through it, while this runs a known absolute path, and stripping the environment would
|
|
673
|
+
// only risk failing a build that runs perfectly well in the one it is about to be installed into.
|
|
674
|
+
// Bounded, because a build that hangs instead of failing would otherwise hang `upgrade` with it.
|
|
675
|
+
// `--version` is answered by the program before any command runs and makes no request — the
|
|
676
|
+
// update check skips it — so a working build is done in well under a second.
|
|
677
|
+
try {
|
|
678
|
+
(0, node_child_process_1.execFileSync)(pending, ['--version'], {
|
|
679
|
+
stdio: 'ignore',
|
|
680
|
+
timeout: VERSION_PROBE_TIMEOUT_MS,
|
|
681
|
+
killSignal: 'SIGKILL',
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
catch (error) {
|
|
685
|
+
const hung = error.code === 'ETIMEDOUT';
|
|
686
|
+
throw new Error(hung
|
|
687
|
+
? `the downloaded ${binaryName} did not answer --version within ${VERSION_PROBE_TIMEOUT_MS / 1000}s; nothing was changed`
|
|
688
|
+
: `the downloaded ${binaryName} does not run on this machine (${process.platform} ${process.arch}); nothing was changed`);
|
|
689
|
+
}
|
|
690
|
+
// Replacing a running executable is legal on POSIX: the running process keeps the old inode
|
|
691
|
+
// open, and the next invocation resolves the new one.
|
|
692
|
+
(0, node_fs_1.renameSync)(pending, executable);
|
|
693
|
+
}
|
|
694
|
+
finally {
|
|
695
|
+
(0, node_fs_1.rmSync)(staging, { recursive: true, force: true });
|
|
696
|
+
// A failure between the write and the rename — ENOSPC, a signal — would otherwise leave a
|
|
697
|
+
// partial dotfile next to the real executable, owned by root in the `sudo upgrade` flow this
|
|
698
|
+
// command tells the user to run.
|
|
699
|
+
(0, node_fs_1.rmSync)(pending, { force: true });
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
/**
|
|
703
|
+
* The directories a trusted system tool is looked up in, and the `PATH` the extraction runs with.
|
|
704
|
+
*
|
|
705
|
+
* Both uses need the same answer, so they share one value: `tar` is resolved from here rather than
|
|
706
|
+
* through the caller's `PATH`, and the child is given this as its `PATH` because GNU tar implements
|
|
707
|
+
* `-z` by running `gzip`, which it finds the ordinary way. These two cover macOS and every mainstream
|
|
708
|
+
* Linux distribution; an image that keeps `tar` elsewhere is handled by the ownership-checked search
|
|
709
|
+
* below instead of by widening this.
|
|
710
|
+
*/
|
|
711
|
+
const TRUSTED_TOOL_PATH = '/usr/bin:/bin';
|
|
712
|
+
/**
|
|
713
|
+
* Absolute path to `tar`, so the extraction cannot be redirected by `PATH`.
|
|
714
|
+
*
|
|
715
|
+
* `upgrade` tells the user to re-run under `sudo` when the executable is not writable, and macOS
|
|
716
|
+
* ships no `secure_path` in its default sudoers — so a bare `tar` would resolve through the invoking
|
|
717
|
+
* user's `PATH` and run a file they control as root. The two standard locations are tried first
|
|
718
|
+
* because they cover macOS and every mainstream Linux distribution.
|
|
719
|
+
*
|
|
720
|
+
* The `PATH` search that follows is for the images that keep `tar` elsewhere (`/usr/local/bin` under
|
|
721
|
+
* Nix or Homebrew-on-Linux). Absolute entries alone are not enough — `/tmp/evil` is absolute — so a
|
|
722
|
+
* candidate is accepted only if root owns it and no one else can write it or the directory holding
|
|
723
|
+
* it. That is the property that matters under `sudo`, and it is what a store path or a distribution
|
|
724
|
+
* prefix has while an attacker-controlled directory does not. Nothing found means an outright
|
|
725
|
+
* failure: there is no safe way to run a program this cannot locate.
|
|
726
|
+
*/
|
|
727
|
+
const tarProgram = () => {
|
|
728
|
+
for (const candidate of TRUSTED_TOOL_PATH.split(':').map((directory) => `${directory}/tar`)) {
|
|
729
|
+
if ((0, node_fs_1.existsSync)(candidate))
|
|
730
|
+
return candidate;
|
|
731
|
+
}
|
|
732
|
+
for (const entry of (process.env['PATH'] ?? '').split(':')) {
|
|
733
|
+
if (!entry.startsWith('/'))
|
|
734
|
+
continue;
|
|
735
|
+
const candidate = (0, node_path_1.join)(entry, 'tar');
|
|
736
|
+
if ((0, node_fs_1.existsSync)(candidate) && rootOwnedAndProtected(candidate) && rootOwnedAndProtected(entry))
|
|
737
|
+
return candidate;
|
|
738
|
+
}
|
|
739
|
+
throw new Error('no trusted tar was found on PATH, so the archive cannot be extracted');
|
|
740
|
+
};
|
|
741
|
+
/** Whether root owns the path and neither group nor other can write it — checked without following links. */
|
|
742
|
+
const rootOwnedAndProtected = (path) => {
|
|
743
|
+
try {
|
|
744
|
+
const stats = (0, node_fs_1.lstatSync)(path);
|
|
745
|
+
return stats.uid === 0 && (stats.mode & 0o022) === 0;
|
|
746
|
+
}
|
|
747
|
+
catch {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
/**
|
|
752
|
+
* Whether `tag` names a release newer than the running `version`.
|
|
753
|
+
*
|
|
754
|
+
* "Different" is not "newer". `releases/latest` follows whatever release is currently marked latest,
|
|
755
|
+
* so deleting a broken release moves it *backwards* — and a bare inequality would then tell a user
|
|
756
|
+
* on 2.0.1 that "a new release is available: 2.0.1 → v2.0.0". Release-please, which every generated
|
|
757
|
+
* CLI publishes through, produces strict semver, so that is what is compared; anything else falls
|
|
758
|
+
* back to the inequality, on the grounds that an imprecise word beats never mentioning a release at
|
|
759
|
+
* all for a versioning scheme this cannot read.
|
|
760
|
+
*/
|
|
761
|
+
const isNewerRelease = (tag, version) => {
|
|
762
|
+
const candidate = tag.replace(/^v/u, '');
|
|
763
|
+
const latest = parseVersion(candidate);
|
|
764
|
+
const current = parseVersion(version);
|
|
765
|
+
if (!latest || !current)
|
|
766
|
+
return candidate !== version;
|
|
767
|
+
for (let index = 0; index < latest.release.length; index += 1) {
|
|
768
|
+
const next = latest.release[index] ?? 0;
|
|
769
|
+
const now = current.release[index] ?? 0;
|
|
770
|
+
if (next !== now)
|
|
771
|
+
return next > now;
|
|
772
|
+
}
|
|
773
|
+
// Same `major.minor.patch`: semver puts a prerelease below the release it leads to, so `1.2.0` is
|
|
774
|
+
// newer than `1.2.0-rc.1`.
|
|
775
|
+
if (latest.prerelease === current.prerelease)
|
|
776
|
+
return false;
|
|
777
|
+
if (!latest.prerelease)
|
|
778
|
+
return true;
|
|
779
|
+
if (!current.prerelease)
|
|
780
|
+
return false;
|
|
781
|
+
return comparePrerelease(latest.prerelease, current.prerelease) > 0;
|
|
782
|
+
};
|
|
783
|
+
/**
|
|
784
|
+
* Orders two semver prerelease strings, returning >0 when `left` is the newer one.
|
|
785
|
+
*
|
|
786
|
+
* Semver's dotted rule, not a string comparison: identifiers are compared field by field, numeric
|
|
787
|
+
* ones numerically and the rest lexically, with numeric sorting below alphanumeric and a shorter
|
|
788
|
+
* prefix below its extension. Comparing the whole string instead put `rc.10` *below* `rc.9`, which a
|
|
789
|
+
* long prerelease train reaches routinely — a user on `rc.9` would then never be told about `rc.10`,
|
|
790
|
+
* and running `upgrade` would install it while calling the result a move rather than an upgrade.
|
|
791
|
+
*/
|
|
792
|
+
const comparePrerelease = (left, right) => {
|
|
793
|
+
const a = left.split('.');
|
|
794
|
+
const b = right.split('.');
|
|
795
|
+
for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
|
|
796
|
+
const x = a[index];
|
|
797
|
+
const y = b[index];
|
|
798
|
+
// A field the other side does not have makes the longer one greater, per semver.
|
|
799
|
+
if (x === undefined)
|
|
800
|
+
return -1;
|
|
801
|
+
if (y === undefined)
|
|
802
|
+
return 1;
|
|
803
|
+
if (x === y)
|
|
804
|
+
continue;
|
|
805
|
+
const xNumeric = /^\d+$/u.test(x);
|
|
806
|
+
const yNumeric = /^\d+$/u.test(y);
|
|
807
|
+
if (xNumeric && yNumeric)
|
|
808
|
+
return Number(x) - Number(y);
|
|
809
|
+
// Numeric identifiers always sort below alphanumeric ones.
|
|
810
|
+
if (xNumeric !== yNumeric)
|
|
811
|
+
return xNumeric ? -1 : 1;
|
|
812
|
+
return x < y ? -1 : 1;
|
|
813
|
+
}
|
|
814
|
+
return 0;
|
|
815
|
+
};
|
|
816
|
+
/** Splits a strict semver string into its numeric release triple and prerelease text, or undefined. */
|
|
817
|
+
const parseVersion = (value) => {
|
|
818
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u.exec(value);
|
|
819
|
+
if (!match)
|
|
820
|
+
return undefined;
|
|
821
|
+
return { release: [Number(match[1]), Number(match[2]), Number(match[3])], prerelease: match[4] ?? '' };
|
|
822
|
+
};
|
|
823
|
+
/**
|
|
824
|
+
* This command's own opt-out variable, fixed at generation time.
|
|
825
|
+
*
|
|
826
|
+
* Substituted rather than derived from `binaryName` here, because the generated README documents it
|
|
827
|
+
* by name — deriving it twice is how a documented opt-out stops matching the one that is read.
|
|
828
|
+
*/
|
|
829
|
+
const UPDATE_OPT_OUT = 'DEDALUS_NO_UPDATE_NOTIFIER';
|
|
830
|
+
/** Charset a git tag can actually use, applied to anything read back from the cache before printing. */
|
|
831
|
+
const RELEASE_TAG = /^[A-Za-z0-9._+-]{1,64}$/u;
|
|
832
|
+
/** How long a version check is trusted before the next run looks again. */
|
|
833
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
834
|
+
/**
|
|
835
|
+
* Hard bound on the background version check.
|
|
836
|
+
*
|
|
837
|
+
* `fetch` keeps its socket referenced, so an unawaited request still holds the process open after
|
|
838
|
+
* the command's own work is done — a host that accepts the connection and never answers (a captive
|
|
839
|
+
* portal, a stalled proxy) would otherwise hang the CLI indefinitely on every run. The abort caps
|
|
840
|
+
* that at this, and the cache write below caps how often it can happen at all.
|
|
841
|
+
*/
|
|
842
|
+
const CHECK_TIMEOUT_MS = 2_000;
|
|
843
|
+
/** One line naming the installed and available versions, shared by `upgrade` and `upgrade --check`. */
|
|
844
|
+
const versionLine = (binaryName, version, tag) => `${binaryName} ${version} is installed; ${tag} is available.\n`;
|
|
845
|
+
/** Resolves the latest tag, reporting and setting a failing exit code rather than throwing. */
|
|
846
|
+
const resolveLatestTag = async () => {
|
|
847
|
+
try {
|
|
848
|
+
return await withTimeout((signal) => latestTag(signal), UPGRADE_CHECK_TIMEOUT_MS, 'the release check');
|
|
849
|
+
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
process.stderr.write(`error: ${error.message}\n`);
|
|
852
|
+
process.exitCode = 1;
|
|
853
|
+
return undefined;
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
/** Where the last check is remembered, so every invocation does not hit the network. */
|
|
857
|
+
const cachePath = (binaryName) => {
|
|
858
|
+
// Windows sets neither `HOME` nor `XDG_CACHE_HOME` from cmd or PowerShell, so without this the
|
|
859
|
+
// cache lands under the temp directory that Disk Cleanup purges — and the throttle it exists to
|
|
860
|
+
// enforce would reset every time that happened.
|
|
861
|
+
if (process.platform === 'win32') {
|
|
862
|
+
const local = process.env['LOCALAPPDATA'];
|
|
863
|
+
return local ? (0, node_path_1.join)(local, binaryName, 'update-check.json') : undefined;
|
|
864
|
+
}
|
|
865
|
+
// A relative `XDG_CACHE_HOME` is invalid per the XDG base directory spec and ignored, which also
|
|
866
|
+
// keeps the cache off a path that would otherwise be resolved against the working directory: the
|
|
867
|
+
// throttle would then be per-directory, and a repository could carry a writable one.
|
|
868
|
+
const xdg = process.env['XDG_CACHE_HOME'];
|
|
869
|
+
const base = (xdg && (0, node_path_1.isAbsolute)(xdg) ? xdg : undefined) ?? homeCacheBase();
|
|
870
|
+
return base ? (0, node_path_1.join)(base, binaryName, 'update-check.json') : undefined;
|
|
871
|
+
};
|
|
872
|
+
/** Per-user cache root under `$HOME`, or undefined when there is no home directory to put it in. */
|
|
873
|
+
const homeCacheBase = () => {
|
|
874
|
+
const home = process.env['HOME'];
|
|
875
|
+
if (!home)
|
|
876
|
+
return undefined;
|
|
877
|
+
return process.platform === 'darwin' ? (0, node_path_1.join)(home, 'Library', 'Caches') : (0, node_path_1.join)(home, '.cache');
|
|
878
|
+
};
|
|
879
|
+
/**
|
|
880
|
+
* Whether it is safe to print an update hint at all.
|
|
881
|
+
*
|
|
882
|
+
* Deliberately conservative. The notice is a courtesy, and every case below is one where printing it
|
|
883
|
+
* does more harm than the notice is worth:
|
|
884
|
+
*
|
|
885
|
+
* - Not a TTY on stderr: the output is being captured or piped, where an unexpected line is noise at
|
|
886
|
+
* best and corrupts a parsed stream at worst. This is also why the notice only ever goes to
|
|
887
|
+
* stderr — stdout carries the command's real output, which `--format json` callers parse.
|
|
888
|
+
* - `CI`: build logs are read by machines and by people debugging failures, neither of whom asked.
|
|
889
|
+
* - `NO_UPDATE_NOTIFIER` or this command's own opt-out variable: an explicit request to stop.
|
|
890
|
+
*/
|
|
891
|
+
const notificationsAllowed = () => {
|
|
892
|
+
if (process.env['CI'])
|
|
893
|
+
return false;
|
|
894
|
+
if (process.env['NO_UPDATE_NOTIFIER'])
|
|
895
|
+
return false;
|
|
896
|
+
if (process.env[UPDATE_OPT_OUT])
|
|
897
|
+
return false;
|
|
898
|
+
// Never under `sudo`, and the reason is the throttle rather than the notice. `sudo` keeps `HOME` on
|
|
899
|
+
// macOS, so a root run that lands past the 24h window creates the cache directory owned by root —
|
|
900
|
+
// and every later run as the real user then fails to write it, silently. With nothing recorded the
|
|
901
|
+
// throttle never engages, so the unauthenticated API request goes out on *every* invocation, which
|
|
902
|
+
// is exactly the rate-limit exhaustion recording-before-the-request exists to prevent. `upgrade`
|
|
903
|
+
// itself tells the user to re-run under `sudo` for a root-owned install, so this is a path they are
|
|
904
|
+
// actively sent down.
|
|
905
|
+
//
|
|
906
|
+
// Keyed on `SUDO_USER` rather than on being root, deliberately. `SUDO_USER` is set only where sudo
|
|
907
|
+
// elevated from a real user — precisely when `HOME` belongs to somebody else — while plain root
|
|
908
|
+
// (a container image, a root-only appliance) owns its own `$HOME` and has no such problem. Testing
|
|
909
|
+
// `getuid() === 0` would switch the notice off for every containerized CLI, which is a large class
|
|
910
|
+
// of ordinary user, to fix a case they are not in.
|
|
911
|
+
if (process.env['SUDO_USER'])
|
|
912
|
+
return false;
|
|
913
|
+
// Both streams, not just stderr. The documented way to install completions is
|
|
914
|
+
// `eval "$(<bin> completion bash)"` in a shell rc: that captures stdout while leaving stderr
|
|
915
|
+
// attached to the terminal, so a stderr-only gate printed the notice into *every new shell* the
|
|
916
|
+
// user opened. Requiring stdout to be a terminal too is what tells a real interactive run from a
|
|
917
|
+
// command whose output something else is reading.
|
|
918
|
+
return Boolean(process.stderr.isTTY) && Boolean(process.stdout.isTTY);
|
|
919
|
+
};
|
|
920
|
+
/**
|
|
921
|
+
* Tells the user, at most once a day, that a newer release exists.
|
|
922
|
+
*
|
|
923
|
+
* Fire-and-forget on purpose: it is started before the command runs and never awaited, so a slow or
|
|
924
|
+
* unreachable network never delays the command's own work. The process can still be held open for up
|
|
925
|
+
* to {@link CHECK_TIMEOUT_MS} after it finishes, on a stalled connection, at most once a day. Every failure path is silent — a version check that cannot
|
|
926
|
+
* reach GitHub is not something the user asked about, and reporting it would turn a courtesy into
|
|
927
|
+
* noise on every offline run.
|
|
928
|
+
*
|
|
929
|
+
* The notice is printed on the *next* run rather than this one when the cache is cold: the check
|
|
930
|
+
* resolves after the command has usually finished, and interleaving a notice with output already in
|
|
931
|
+
* flight is worse than telling them a moment later.
|
|
932
|
+
*/
|
|
933
|
+
const notifyIfOutdated = (binaryName, version, argv = process.argv) => {
|
|
934
|
+
if (!notificationsAllowed())
|
|
935
|
+
return;
|
|
936
|
+
// Commander answers these and exits before a request could leave the process, but only after the
|
|
937
|
+
// attempt below is recorded — so the day's first `--help` would use up the day's check without
|
|
938
|
+
// making it. Skipped instead, which keeps the throttle and the check.
|
|
939
|
+
if (answeredWithoutRunning(argv.slice(2)))
|
|
940
|
+
return;
|
|
941
|
+
// No private cache directory means no throttle, and an unthrottled check would hit the API on
|
|
942
|
+
// every invocation. It also means the only remaining location would be a world-writable one:
|
|
943
|
+
// with `HOME` unset (`su` without `-`, `env -i`, a minimal container) a `/tmp/<command>/` fallback
|
|
944
|
+
// is a path any local user can create first and then own, and its contents are written to this
|
|
945
|
+
// terminal below. Skipping the check entirely is the cheap, safe answer.
|
|
946
|
+
const path = cachePath(binaryName);
|
|
947
|
+
if (!path)
|
|
948
|
+
return;
|
|
949
|
+
let cached = {};
|
|
950
|
+
try {
|
|
951
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
|
|
952
|
+
// `JSON.parse` returns `null` for the literal `null` without throwing, and `typeof null` is
|
|
953
|
+
// `'object'` — so both are checked. Reading `.latest` off `null` would throw *outside* this
|
|
954
|
+
// catch, and the notifier runs while the program is being built, taking the whole CLI down with
|
|
955
|
+
// an unhandled stack trace on every invocation until the file is deleted by hand.
|
|
956
|
+
if (parsed && typeof parsed === 'object')
|
|
957
|
+
cached = parsed;
|
|
958
|
+
}
|
|
959
|
+
catch {
|
|
960
|
+
// No cache yet, or an unreadable one: treated as "never checked".
|
|
961
|
+
}
|
|
962
|
+
// Report what the last check found before starting another, so a known-stale version is surfaced
|
|
963
|
+
// immediately rather than a day later.
|
|
964
|
+
// Validated before it reaches the terminal. A real `tag_name` cannot contain control characters —
|
|
965
|
+
// git refnames forbid them — so anything that does was not written by this code, and writing it
|
|
966
|
+
// raw would let cursor-control sequences rewrite the surrounding lines and forge an instruction
|
|
967
|
+
// that appears to come from the CLI itself.
|
|
968
|
+
if (cached.latest && RELEASE_TAG.test(cached.latest) && isNewerRelease(cached.latest, version)) {
|
|
969
|
+
const install = detectInstall(binaryName);
|
|
970
|
+
// `<bin> upgrade` wherever it can do the work itself — in place, or by running the owning manager
|
|
971
|
+
// — and the manager's own instruction only where it can do neither.
|
|
972
|
+
const how = install.kind === 'managed' && !install.run ? install.upgrade : `${binaryName} upgrade`;
|
|
973
|
+
process.stderr.write(`\nA new release of ${binaryName} is available: ${version} → ${cached.latest}\n ${how}\n\n`);
|
|
974
|
+
}
|
|
975
|
+
// A negative delta satisfies a bare `<`, so a `checkedAt` written while the clock was ahead — a
|
|
976
|
+
// dead RTC battery, a hypervisor clock jump on resume — would suppress every later check for the
|
|
977
|
+
// whole skew and the user would never hear about another release. Out-of-range in either direction
|
|
978
|
+
// means "check again".
|
|
979
|
+
const since = Date.now() - (cached.checkedAt ?? 0);
|
|
980
|
+
if (cached.checkedAt && since >= 0 && since < CHECK_INTERVAL_MS)
|
|
981
|
+
return;
|
|
982
|
+
// The attempt is recorded *before* the request, not when it resolves. Commander exits the process
|
|
983
|
+
// outright for `--help`, `--version` and any usage error, which kills the in-flight check before
|
|
984
|
+
// it can write anything — so a user who mostly types `--help` would issue one unauthenticated API
|
|
985
|
+
// request per invocation forever, never cache a result, and eventually exhaust GitHub's 60/hr
|
|
986
|
+
// limit for their whole NAT. Writing up front costs one stale-by-a-day notice at worst.
|
|
987
|
+
recordCheck(path, cached.latest);
|
|
988
|
+
const controller = new AbortController();
|
|
989
|
+
const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
|
|
990
|
+
// The timer itself would hold the event loop open for its full duration; `unref` exists on Node
|
|
991
|
+
// and Bun timers but not in the DOM typing, hence the guarded call.
|
|
992
|
+
timeout.unref?.();
|
|
993
|
+
void latestTag(controller.signal)
|
|
994
|
+
.then((tag) => recordCheck(path, tag))
|
|
995
|
+
// Failure needs no second write: the attempt was already recorded above, so the throttle holds
|
|
996
|
+
// whether the check succeeded, failed, or never finished.
|
|
997
|
+
.catch(() => { })
|
|
998
|
+
.finally(() => clearTimeout(timeout));
|
|
999
|
+
};
|
|
1000
|
+
exports.notifyIfOutdated = notifyIfOutdated;
|
|
1001
|
+
/**
|
|
1002
|
+
* Whether Commander answers these arguments itself — help or the version — and exits without running
|
|
1003
|
+
* a command: no arguments at all (the program prints its help), `help`, `-v`/`--version` on their own,
|
|
1004
|
+
* or `-h`/`--help` anywhere before a `--` that ends option parsing.
|
|
1005
|
+
*/
|
|
1006
|
+
const answeredWithoutRunning = (args) => {
|
|
1007
|
+
const options = args.includes('--') ? args.slice(0, args.indexOf('--')) : args;
|
|
1008
|
+
if (options.length === 0 || options[0] === 'help')
|
|
1009
|
+
return true;
|
|
1010
|
+
if (options.length === 1 && (options[0] === '-v' || options[0] === '--version'))
|
|
1011
|
+
return true;
|
|
1012
|
+
return options.includes('-h') || options.includes('--help');
|
|
1013
|
+
};
|
|
1014
|
+
/** Remembers when the check last ran, and what it found, so the throttle above can engage. */
|
|
1015
|
+
const recordCheck = (path, latest) => {
|
|
1016
|
+
try {
|
|
1017
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
1018
|
+
(0, node_fs_1.writeFileSync)(path, JSON.stringify({ checkedAt: Date.now(), ...(latest ? { latest } : {}) }));
|
|
1019
|
+
}
|
|
1020
|
+
catch {
|
|
1021
|
+
// An unwritable cache directory is not worth reporting: the check is a courtesy, and the only
|
|
1022
|
+
// consequence is that it runs again next time.
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
/**
|
|
1026
|
+
* Reports `upgrade --check`: already the latest, a newer release and the command that installs it, or
|
|
1027
|
+
* a latest release older than this one.
|
|
1028
|
+
*
|
|
1029
|
+
* "Different" is not "newer" — `releases/latest` moves backwards when a broken release is deleted — so
|
|
1030
|
+
* the ordering decides the wording. `canMove` says whether `how` would install an older release: true
|
|
1031
|
+
* for a self-managed install, which `upgrade` replaces with whatever is latest; false for a managed
|
|
1032
|
+
* one, whose manager never downgrades.
|
|
1033
|
+
*/
|
|
1034
|
+
const reportCheck = (binaryName, version, tag, how, canMove) => {
|
|
1035
|
+
if (tag.replace(/^v/u, '') === version) {
|
|
1036
|
+
process.stderr.write(`${binaryName} ${version} is already the latest release.\n`);
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
process.stderr.write(versionLine(binaryName, version, tag));
|
|
1040
|
+
if (isNewerRelease(tag, version)) {
|
|
1041
|
+
process.stderr.write(`Upgrade it with:\n ${how}\n`);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
process.stderr.write(canMove
|
|
1045
|
+
? `That is older than what you have. Move to it with:\n ${how}\n`
|
|
1046
|
+
: 'That is older than what you have, so there is nothing to upgrade.\n');
|
|
1047
|
+
};
|
|
1048
|
+
/** Whether this process can create and rename files in `directory`. */
|
|
1049
|
+
const writableDirectory = (directory) => {
|
|
1050
|
+
try {
|
|
1051
|
+
(0, node_fs_1.accessSync)(directory, node_fs_1.constants.W_OK);
|
|
1052
|
+
return true;
|
|
1053
|
+
}
|
|
1054
|
+
catch {
|
|
1055
|
+
return false;
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
/**
|
|
1059
|
+
* Registers `upgrade` and `uninstall`, which both hinge on how this executable was installed.
|
|
1060
|
+
*
|
|
1061
|
+
* `upgrade` upgrades every install it can identify, but never by writing over a file another package
|
|
1062
|
+
* manager owns. Overwriting a Homebrew- or npm-managed binary in place leaves that manager's database
|
|
1063
|
+
* describing a file that is no longer there: `brew` still reports the old version, and the next managed
|
|
1064
|
+
* upgrade silently reverts this one. So a managed install is upgraded by *running* its manager's own
|
|
1065
|
+
* command, and only a download nothing else manages is replaced in place. A binary in `/usr/bin` is
|
|
1066
|
+
* refused outright, because nothing published here installs there — so something else owns it, and
|
|
1067
|
+
* this cannot name what.
|
|
1068
|
+
*/
|
|
1069
|
+
const addUpgradeCommands = (program, binaryName, version) => {
|
|
1070
|
+
program
|
|
1071
|
+
.command('upgrade')
|
|
1072
|
+
.description('Upgrade to the latest release')
|
|
1073
|
+
.option('--check', 'Report the latest version without installing it')
|
|
1074
|
+
.action(async (options) => {
|
|
1075
|
+
const install = settledInstall(detectInstall(binaryName));
|
|
1076
|
+
// A managed install returns unconditionally, which is also what narrows `install` to the
|
|
1077
|
+
// self-managed variant for the replace below — the alternative, testing `kind` again further
|
|
1078
|
+
// down, reads as a guard against a state that cannot happen.
|
|
1079
|
+
if (install.kind === 'managed') {
|
|
1080
|
+
if (options.check) {
|
|
1081
|
+
const tag = await resolveLatestTag();
|
|
1082
|
+
if (!tag)
|
|
1083
|
+
return;
|
|
1084
|
+
process.stderr.write(`${binaryName} was installed with ${install.manager}.\n`);
|
|
1085
|
+
// A manager only ever moves forward, so a `releases/latest` that went backwards is nothing
|
|
1086
|
+
// to act on here — unlike a self-managed install, which `upgrade` would move to it.
|
|
1087
|
+
reportCheck(binaryName, version, tag, install.run ? `${binaryName} upgrade` : install.upgrade, false);
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
if (install.run) {
|
|
1091
|
+
const upgraded = runManagerUpgrade(binaryName, install, install.run);
|
|
1092
|
+
if (upgraded && install.run.installedVersions) {
|
|
1093
|
+
await confirmManagerUpgrade(binaryName, version, install, install.run, install.run.installedVersions);
|
|
1094
|
+
}
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
process.stderr.write(`${binaryName} was installed with ${install.manager}, which owns the installed files.\n` +
|
|
1098
|
+
`Upgrade it with:\n ${install.upgrade}\n`);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
const tag = await resolveLatestTag();
|
|
1102
|
+
if (!tag)
|
|
1103
|
+
return;
|
|
1104
|
+
if (options.check) {
|
|
1105
|
+
reportCheck(binaryName, version, tag, `${binaryName} upgrade`, true);
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
// Compared as strings after stripping the tag's `v`: the release tag is the package version,
|
|
1109
|
+
// so whether to act at all is an equality question, not an ordering one.
|
|
1110
|
+
if (tag.replace(/^v/u, '') === version) {
|
|
1111
|
+
process.stderr.write(`${binaryName} ${version} is already the latest release.\n`);
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
// Checked before the download rather than discovered at the rename: a root-owned install
|
|
1115
|
+
// directory otherwise costs the whole archive before failing with the same permission error.
|
|
1116
|
+
if (!writableDirectory((0, node_path_1.dirname)(install.path))) {
|
|
1117
|
+
process.stderr.write(`error: ${(0, node_path_1.dirname)(install.path)} is not writable, so ${install.path} cannot be replaced.\n` +
|
|
1118
|
+
`Try again with elevated permissions.\n`);
|
|
1119
|
+
process.exitCode = 1;
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
try {
|
|
1123
|
+
await replaceExecutable(install.path, tag, binaryName);
|
|
1124
|
+
// "moved" rather than "upgraded" when the release moved backwards. `releases/latest` follows
|
|
1125
|
+
// whichever release is marked latest, so deleting a broken one really does point this at an
|
|
1126
|
+
// older build — installing it is right (that is the release the project is publishing), but
|
|
1127
|
+
// calling it an upgrade is not.
|
|
1128
|
+
const direction = isNewerRelease(tag, version) ? 'upgraded' : 'moved';
|
|
1129
|
+
process.stderr.write(`${direction} ${binaryName} to ${tag}\n`);
|
|
1130
|
+
}
|
|
1131
|
+
catch (error) {
|
|
1132
|
+
const message = error.message;
|
|
1133
|
+
// A permission error here is the common case — the binary may sit in a root-owned directory —
|
|
1134
|
+
// so say what to do rather than only what failed.
|
|
1135
|
+
const reinstall = /permission|EACCES|EPERM/iu.test(message)
|
|
1136
|
+
? 'Try again with elevated permissions.\n'
|
|
1137
|
+
: '';
|
|
1138
|
+
process.stderr.write(`error: ${message}\n${reinstall}`);
|
|
1139
|
+
process.exitCode = 1;
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
program
|
|
1143
|
+
.command('uninstall')
|
|
1144
|
+
.description('Show how to remove this installation')
|
|
1145
|
+
.action(() => {
|
|
1146
|
+
const install = settledInstall(detectInstall(binaryName));
|
|
1147
|
+
if (install.kind === 'managed') {
|
|
1148
|
+
process.stderr.write(`${binaryName} was installed with ${install.manager}, which owns the installed files.\n` +
|
|
1149
|
+
`Remove it with:\n ${install.remove}\n`);
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
// Printed rather than performed: a command that deletes the executable running it cannot
|
|
1153
|
+
// report a failure afterwards, and the one-liner is auditable before it is run.
|
|
1154
|
+
// Single-quoted, because this is the one line in the module that is a shell command the user is
|
|
1155
|
+
// meant to paste. The path comes from `realpathSync(process.execPath)` and can carry spaces —
|
|
1156
|
+
// `~/Library/Application Support/...`, or any directory a user named — and an unquoted `rm`
|
|
1157
|
+
// would then name two paths, neither of them the executable. Nothing expands inside single
|
|
1158
|
+
// quotes, and an embedded `'` is closed, escaped and reopened.
|
|
1159
|
+
// `sudo` only where it is needed: a root-owned `/usr/local/bin` refuses a plain `rm`, and a
|
|
1160
|
+
// user-owned `~/.local/bin` should not be handed a root command it does not need.
|
|
1161
|
+
const sudo = writableDirectory((0, node_path_1.dirname)(install.path)) ? '' : 'sudo ';
|
|
1162
|
+
process.stderr.write(`Remove ${binaryName} with:\n ${sudo}rm '${install.path.replace(/'/gu, "'\\''")}'\n${leftoverState(binaryName)}`);
|
|
1163
|
+
});
|
|
1164
|
+
};
|
|
1165
|
+
exports.addUpgradeCommands = addUpgradeCommands;
|
|
1166
|
+
/**
|
|
1167
|
+
* The state removing the executable leaves behind, named so a user can clear it too.
|
|
1168
|
+
*
|
|
1169
|
+
* Three things outlive the binary and none is obvious: a stored credential, which `logout` is the
|
|
1170
|
+
* right way to clear because only the CLI knows where its keychain entry or file lives — named only
|
|
1171
|
+
* when the CLI has sign-in at all — the update-check cache, and the PATH line the install script may
|
|
1172
|
+
* have added to a shell startup file, named only when the release ships that script. Printed rather
|
|
1173
|
+
* than deleted for the same reason the executable is: `uninstall` reports, so that what it says can be
|
|
1174
|
+
* read before it is run.
|
|
1175
|
+
*/
|
|
1176
|
+
const leftoverState = (binaryName) => {
|
|
1177
|
+
const cache = cachePath(binaryName);
|
|
1178
|
+
const leftovers = [
|
|
1179
|
+
...(HAS_LOGIN ? [` a stored credential, if you signed in — clear it with: ${binaryName} logout\n`] : []),
|
|
1180
|
+
...(cache
|
|
1181
|
+
? [` the update-check cache — remove it with: rm -rf '${(0, node_path_1.dirname)(cache).replace(/'/gu, "'\\''")}'\n`]
|
|
1182
|
+
: []),
|
|
1183
|
+
...(HAS_INSTALL_SCRIPT
|
|
1184
|
+
? [
|
|
1185
|
+
` a PATH line, if the install script added one — delete the two lines starting at "# Added by the ${binaryName} installer" in your shell startup file (for fish, the file conf.d/${binaryName}.fish)\n`,
|
|
1186
|
+
]
|
|
1187
|
+
: []),
|
|
1188
|
+
];
|
|
1189
|
+
return leftovers.length > 0 ? `\nThis leaves no other files behind except:\n${leftovers.join('')}` : '';
|
|
1190
|
+
};
|
|
1191
|
+
//# sourceMappingURL=upgrade.js.map
|