monday-cli 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +324 -2
- package/README.md +128 -30
- package/dist/api/board-favorites.d.ts +329 -0
- package/dist/api/board-favorites.d.ts.map +1 -0
- package/dist/api/board-favorites.js +353 -0
- package/dist/api/board-favorites.js.map +1 -0
- package/dist/api/board-mutation-result.d.ts +9 -5
- package/dist/api/board-mutation-result.d.ts.map +1 -1
- package/dist/api/board-mutation-result.js +9 -5
- package/dist/api/board-mutation-result.js.map +1 -1
- package/dist/api/board-relation-validation.d.ts +161 -0
- package/dist/api/board-relation-validation.d.ts.map +1 -0
- package/dist/api/board-relation-validation.js +317 -0
- package/dist/api/board-relation-validation.js.map +1 -0
- package/dist/api/cache.d.ts +14 -5
- package/dist/api/cache.d.ts.map +1 -1
- package/dist/api/cache.js +8 -10
- package/dist/api/cache.js.map +1 -1
- package/dist/api/column-mapping.js +2 -2
- package/dist/api/column-mapping.js.map +1 -1
- package/dist/api/column-mutation-result.d.ts +9 -5
- package/dist/api/column-mutation-result.d.ts.map +1 -1
- package/dist/api/column-mutation-result.js +9 -5
- package/dist/api/column-mutation-result.js.map +1 -1
- package/dist/api/column-types.d.ts +26 -7
- package/dist/api/column-types.d.ts.map +1 -1
- package/dist/api/column-types.js +42 -3
- package/dist/api/column-types.js.map +1 -1
- package/dist/api/column-values.d.ts +228 -31
- package/dist/api/column-values.d.ts.map +1 -1
- package/dist/api/column-values.js +551 -124
- package/dist/api/column-values.js.map +1 -1
- package/dist/api/cross-board-search.d.ts +501 -0
- package/dist/api/cross-board-search.d.ts.map +1 -0
- package/dist/api/cross-board-search.js +547 -0
- package/dist/api/cross-board-search.js.map +1 -0
- package/dist/api/dev-conventions.d.ts +1038 -0
- package/dist/api/dev-conventions.d.ts.map +1 -0
- package/dist/api/dev-conventions.js +1556 -0
- package/dist/api/dev-conventions.js.map +1 -0
- package/dist/api/dry-run.d.ts +32 -5
- package/dist/api/dry-run.d.ts.map +1 -1
- package/dist/api/dry-run.js +149 -32
- package/dist/api/dry-run.js.map +1 -1
- package/dist/api/errors.d.ts.map +1 -1
- package/dist/api/errors.js +28 -7
- package/dist/api/errors.js.map +1 -1
- package/dist/api/group-mutation-result.d.ts +9 -5
- package/dist/api/group-mutation-result.d.ts.map +1 -1
- package/dist/api/group-mutation-result.js +9 -5
- package/dist/api/group-mutation-result.js.map +1 -1
- package/dist/api/item-history-projection.d.ts +919 -0
- package/dist/api/item-history-projection.d.ts.map +1 -0
- package/dist/api/item-history-projection.js +1104 -0
- package/dist/api/item-history-projection.js.map +1 -0
- package/dist/api/item-mutation-execute.d.ts +82 -0
- package/dist/api/item-mutation-execute.d.ts.map +1 -0
- package/dist/api/item-mutation-execute.js +199 -0
- package/dist/api/item-mutation-execute.js.map +1 -0
- package/dist/api/notifications.d.ts +156 -0
- package/dist/api/notifications.d.ts.map +1 -0
- package/dist/api/notifications.js +215 -0
- package/dist/api/notifications.js.map +1 -0
- package/dist/api/oauth-test-helper.d.ts +64 -0
- package/dist/api/oauth-test-helper.d.ts.map +1 -0
- package/dist/api/oauth-test-helper.js +179 -0
- package/dist/api/oauth-test-helper.js.map +1 -0
- package/dist/api/oauth.d.ts +198 -0
- package/dist/api/oauth.d.ts.map +1 -0
- package/dist/api/oauth.js +471 -0
- package/dist/api/oauth.js.map +1 -0
- package/dist/api/partial-success-bulk.d.ts +422 -0
- package/dist/api/partial-success-bulk.d.ts.map +1 -0
- package/dist/api/partial-success-bulk.js +378 -0
- package/dist/api/partial-success-bulk.js.map +1 -0
- package/dist/api/people.d.ts +54 -1
- package/dist/api/people.d.ts.map +1 -1
- package/dist/api/people.js +27 -3
- package/dist/api/people.js.map +1 -1
- package/dist/api/probes.d.ts +487 -0
- package/dist/api/probes.d.ts.map +1 -0
- package/dist/api/probes.js +881 -0
- package/dist/api/probes.js.map +1 -0
- package/dist/api/raw-document.d.ts.map +1 -1
- package/dist/api/raw-document.js +2 -2
- package/dist/api/raw-document.js.map +1 -1
- package/dist/api/raw-write.d.ts.map +1 -1
- package/dist/api/raw-write.js +11 -3
- package/dist/api/raw-write.js.map +1 -1
- package/dist/api/resolution-context.d.ts +23 -11
- package/dist/api/resolution-context.d.ts.map +1 -1
- package/dist/api/resolution-context.js +53 -12
- package/dist/api/resolution-context.js.map +1 -1
- package/dist/api/resolution-pass.d.ts +30 -1
- package/dist/api/resolution-pass.d.ts.map +1 -1
- package/dist/api/resolution-pass.js +36 -1
- package/dist/api/resolution-pass.js.map +1 -1
- package/dist/api/resolve-client.d.ts +11 -0
- package/dist/api/resolve-client.d.ts.map +1 -1
- package/dist/api/resolve-client.js +1 -1
- package/dist/api/resolve-client.js.map +1 -1
- package/dist/api/response-root.d.ts +92 -46
- package/dist/api/response-root.d.ts.map +1 -1
- package/dist/api/response-root.js +93 -41
- package/dist/api/response-root.js.map +1 -1
- package/dist/api/tag-directory.d.ts +154 -0
- package/dist/api/tag-directory.d.ts.map +1 -0
- package/dist/api/tag-directory.js +325 -0
- package/dist/api/tag-directory.js.map +1 -0
- package/dist/api/time-tracking.d.ts +165 -0
- package/dist/api/time-tracking.d.ts.map +1 -0
- package/dist/api/time-tracking.js +135 -0
- package/dist/api/time-tracking.js.map +1 -0
- package/dist/api/transport.js +3 -3
- package/dist/api/transport.js.map +1 -1
- package/dist/api/usage.d.ts +190 -0
- package/dist/api/usage.d.ts.map +1 -0
- package/dist/api/usage.js +194 -0
- package/dist/api/usage.js.map +1 -0
- package/dist/api/users-fan-out-mutation.d.ts.map +1 -1
- package/dist/api/users-fan-out-mutation.js +10 -5
- package/dist/api/users-fan-out-mutation.js.map +1 -1
- package/dist/api/webhooks.d.ts +357 -0
- package/dist/api/webhooks.d.ts.map +1 -0
- package/dist/api/webhooks.js +333 -0
- package/dist/api/webhooks.js.map +1 -0
- package/dist/cli/envelope-out.d.ts +18 -1
- package/dist/cli/envelope-out.d.ts.map +1 -1
- package/dist/cli/envelope-out.js +16 -2
- package/dist/cli/envelope-out.js.map +1 -1
- package/dist/cli/program.d.ts.map +1 -1
- package/dist/cli/program.js +120 -1
- package/dist/cli/program.js.map +1 -1
- package/dist/cli/run.d.ts +12 -0
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +2 -0
- package/dist/cli/run.js.map +1 -1
- package/dist/commands/account/tags.d.ts +37 -0
- package/dist/commands/account/tags.d.ts.map +1 -0
- package/dist/commands/account/tags.js +84 -0
- package/dist/commands/account/tags.js.map +1 -0
- package/dist/commands/auth/login.d.ts +14 -0
- package/dist/commands/auth/login.d.ts.map +1 -0
- package/dist/commands/auth/login.js +314 -0
- package/dist/commands/auth/login.js.map +1 -0
- package/dist/commands/auth/logout.d.ts +28 -0
- package/dist/commands/auth/logout.d.ts.map +1 -0
- package/dist/commands/auth/logout.js +94 -0
- package/dist/commands/auth/logout.js.map +1 -0
- package/dist/commands/board/archive.d.ts.map +1 -1
- package/dist/commands/board/archive.js +14 -14
- package/dist/commands/board/archive.js.map +1 -1
- package/dist/commands/board/column-create.d.ts +3 -3
- package/dist/commands/board/column-create.d.ts.map +1 -1
- package/dist/commands/board/column-create.js +52 -45
- package/dist/commands/board/column-create.js.map +1 -1
- package/dist/commands/board/column-delete.d.ts.map +1 -1
- package/dist/commands/board/column-delete.js +15 -16
- package/dist/commands/board/column-delete.js.map +1 -1
- package/dist/commands/board/column-update.d.ts.map +1 -1
- package/dist/commands/board/column-update.js +23 -22
- package/dist/commands/board/column-update.js.map +1 -1
- package/dist/commands/board/create.d.ts.map +1 -1
- package/dist/commands/board/create.js +14 -17
- package/dist/commands/board/create.js.map +1 -1
- package/dist/commands/board/delete.d.ts.map +1 -1
- package/dist/commands/board/delete.js +12 -15
- package/dist/commands/board/delete.js.map +1 -1
- package/dist/commands/board/describe.d.ts.map +1 -1
- package/dist/commands/board/describe.js +30 -0
- package/dist/commands/board/describe.js.map +1 -1
- package/dist/commands/board/duplicate.d.ts.map +1 -1
- package/dist/commands/board/duplicate.js +12 -13
- package/dist/commands/board/duplicate.js.map +1 -1
- package/dist/commands/board/favorites.d.ts +33 -0
- package/dist/commands/board/favorites.d.ts.map +1 -0
- package/dist/commands/board/favorites.js +74 -0
- package/dist/commands/board/favorites.js.map +1 -0
- package/dist/commands/board/find.d.ts +1 -1
- package/dist/commands/board/group-archive.d.ts.map +1 -1
- package/dist/commands/board/group-archive.js +12 -16
- package/dist/commands/board/group-archive.js.map +1 -1
- package/dist/commands/board/group-create.d.ts.map +1 -1
- package/dist/commands/board/group-create.js +9 -19
- package/dist/commands/board/group-create.js.map +1 -1
- package/dist/commands/board/group-delete.d.ts.map +1 -1
- package/dist/commands/board/group-delete.js +12 -16
- package/dist/commands/board/group-delete.js.map +1 -1
- package/dist/commands/board/group-duplicate.d.ts.map +1 -1
- package/dist/commands/board/group-duplicate.js +12 -16
- package/dist/commands/board/group-duplicate.js.map +1 -1
- package/dist/commands/board/group-update.d.ts.map +1 -1
- package/dist/commands/board/group-update.js +12 -11
- package/dist/commands/board/group-update.js.map +1 -1
- package/dist/commands/board/list.d.ts +1 -1
- package/dist/commands/board/update.d.ts.map +1 -1
- package/dist/commands/board/update.js +16 -11
- package/dist/commands/board/update.js.map +1 -1
- package/dist/commands/cache/list.d.ts +2 -0
- package/dist/commands/cache/list.d.ts.map +1 -1
- package/dist/commands/cache/list.js +2 -2
- package/dist/commands/cache/list.js.map +1 -1
- package/dist/commands/dev/_shared.d.ts +40 -0
- package/dist/commands/dev/_shared.d.ts.map +1 -0
- package/dist/commands/dev/_shared.js +104 -0
- package/dist/commands/dev/_shared.js.map +1 -0
- package/dist/commands/dev/configure.d.ts +36 -0
- package/dist/commands/dev/configure.d.ts.map +1 -0
- package/dist/commands/dev/configure.js +145 -0
- package/dist/commands/dev/configure.js.map +1 -0
- package/dist/commands/dev/discover.d.ts +34 -0
- package/dist/commands/dev/discover.d.ts.map +1 -0
- package/dist/commands/dev/discover.js +117 -0
- package/dist/commands/dev/discover.js.map +1 -0
- package/dist/commands/dev/doctor.d.ts +39 -0
- package/dist/commands/dev/doctor.d.ts.map +1 -0
- package/dist/commands/dev/doctor.js +91 -0
- package/dist/commands/dev/doctor.js.map +1 -0
- package/dist/commands/dev/epic/items.d.ts +24 -0
- package/dist/commands/dev/epic/items.d.ts.map +1 -0
- package/dist/commands/dev/epic/items.js +103 -0
- package/dist/commands/dev/epic/items.js.map +1 -0
- package/dist/commands/dev/epic/list.d.ts +36 -0
- package/dist/commands/dev/epic/list.d.ts.map +1 -0
- package/dist/commands/dev/epic/list.js +120 -0
- package/dist/commands/dev/epic/list.js.map +1 -0
- package/dist/commands/dev/release/list.d.ts +21 -0
- package/dist/commands/dev/release/list.d.ts.map +1 -0
- package/dist/commands/dev/release/list.js +73 -0
- package/dist/commands/dev/release/list.js.map +1 -0
- package/dist/commands/dev/sprint/current.d.ts +24 -0
- package/dist/commands/dev/sprint/current.d.ts.map +1 -0
- package/dist/commands/dev/sprint/current.js +90 -0
- package/dist/commands/dev/sprint/current.js.map +1 -0
- package/dist/commands/dev/sprint/items.d.ts +34 -0
- package/dist/commands/dev/sprint/items.d.ts.map +1 -0
- package/dist/commands/dev/sprint/items.js +118 -0
- package/dist/commands/dev/sprint/items.js.map +1 -0
- package/dist/commands/dev/sprint/list.d.ts +41 -0
- package/dist/commands/dev/sprint/list.d.ts.map +1 -0
- package/dist/commands/dev/sprint/list.js +104 -0
- package/dist/commands/dev/sprint/list.js.map +1 -0
- package/dist/commands/dev/task/block.d.ts +29 -0
- package/dist/commands/dev/task/block.d.ts.map +1 -0
- package/dist/commands/dev/task/block.js +106 -0
- package/dist/commands/dev/task/block.js.map +1 -0
- package/dist/commands/dev/task/done.d.ts +30 -0
- package/dist/commands/dev/task/done.d.ts.map +1 -0
- package/dist/commands/dev/task/done.js +113 -0
- package/dist/commands/dev/task/done.js.map +1 -0
- package/dist/commands/dev/task/list.d.ts +42 -0
- package/dist/commands/dev/task/list.d.ts.map +1 -0
- package/dist/commands/dev/task/list.js +227 -0
- package/dist/commands/dev/task/list.js.map +1 -0
- package/dist/commands/dev/task/start.d.ts +29 -0
- package/dist/commands/dev/task/start.d.ts.map +1 -0
- package/dist/commands/dev/task/start.js +90 -0
- package/dist/commands/dev/task/start.js.map +1 -0
- package/dist/commands/emit.d.ts.map +1 -1
- package/dist/commands/emit.js +5 -3
- package/dist/commands/emit.js.map +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +95 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/item/archive.d.ts.map +1 -1
- package/dist/commands/item/archive.js +11 -0
- package/dist/commands/item/archive.js.map +1 -1
- package/dist/commands/item/clear.d.ts.map +1 -1
- package/dist/commands/item/clear.js +15 -0
- package/dist/commands/item/clear.js.map +1 -1
- package/dist/commands/item/create.d.ts.map +1 -1
- package/dist/commands/item/create.js +41 -8
- package/dist/commands/item/create.js.map +1 -1
- package/dist/commands/item/delete.d.ts.map +1 -1
- package/dist/commands/item/delete.js +11 -0
- package/dist/commands/item/delete.js.map +1 -1
- package/dist/commands/item/duplicate.d.ts.map +1 -1
- package/dist/commands/item/duplicate.js +12 -0
- package/dist/commands/item/duplicate.js.map +1 -1
- package/dist/commands/item/history.d.ts +60 -0
- package/dist/commands/item/history.d.ts.map +1 -0
- package/dist/commands/item/history.js +309 -0
- package/dist/commands/item/history.js.map +1 -0
- package/dist/commands/item/list.d.ts.map +1 -1
- package/dist/commands/item/list.js +16 -13
- package/dist/commands/item/list.js.map +1 -1
- package/dist/commands/item/move.d.ts.map +1 -1
- package/dist/commands/item/move.js +41 -7
- package/dist/commands/item/move.js.map +1 -1
- package/dist/commands/item/search.d.ts +99 -15
- package/dist/commands/item/search.d.ts.map +1 -1
- package/dist/commands/item/search.js +480 -36
- package/dist/commands/item/search.js.map +1 -1
- package/dist/commands/item/set.d.ts.map +1 -1
- package/dist/commands/item/set.js +52 -8
- package/dist/commands/item/set.js.map +1 -1
- package/dist/commands/item/time-track/start.d.ts +61 -0
- package/dist/commands/item/time-track/start.d.ts.map +1 -0
- package/dist/commands/item/time-track/start.js +138 -0
- package/dist/commands/item/time-track/start.js.map +1 -0
- package/dist/commands/item/time-track/stop.d.ts +32 -0
- package/dist/commands/item/time-track/stop.d.ts.map +1 -0
- package/dist/commands/item/time-track/stop.js +97 -0
- package/dist/commands/item/time-track/stop.js.map +1 -0
- package/dist/commands/item/update.d.ts +1 -0
- package/dist/commands/item/update.d.ts.map +1 -1
- package/dist/commands/item/update.js +103 -113
- package/dist/commands/item/update.js.map +1 -1
- package/dist/commands/item/upsert.d.ts.map +1 -1
- package/dist/commands/item/upsert.js +48 -1
- package/dist/commands/item/upsert.js.map +1 -1
- package/dist/commands/notification/send.d.ts +60 -0
- package/dist/commands/notification/send.d.ts.map +1 -0
- package/dist/commands/notification/send.js +147 -0
- package/dist/commands/notification/send.js.map +1 -0
- package/dist/commands/parse-argv.d.ts.map +1 -1
- package/dist/commands/parse-argv.js +14 -4
- package/dist/commands/parse-argv.js.map +1 -1
- package/dist/commands/raw/index.d.ts.map +1 -1
- package/dist/commands/raw/index.js +13 -15
- package/dist/commands/raw/index.js.map +1 -1
- package/dist/commands/run-by-id-lookup.d.ts.map +1 -1
- package/dist/commands/run-by-id-lookup.js +2 -2
- package/dist/commands/run-by-id-lookup.js.map +1 -1
- package/dist/commands/schema/index.d.ts +2 -0
- package/dist/commands/schema/index.d.ts.map +1 -1
- package/dist/commands/status.d.ts +120 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +365 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/update/body-source.d.ts.map +1 -1
- package/dist/commands/update/body-source.js +2 -2
- package/dist/commands/update/body-source.js.map +1 -1
- package/dist/commands/update/create.d.ts +2 -3
- package/dist/commands/update/create.d.ts.map +1 -1
- package/dist/commands/update/create.js +15 -3
- package/dist/commands/update/create.js.map +1 -1
- package/dist/commands/update/delete.d.ts.map +1 -1
- package/dist/commands/update/delete.js +11 -0
- package/dist/commands/update/delete.js.map +1 -1
- package/dist/commands/update/edit.d.ts.map +1 -1
- package/dist/commands/update/edit.js +11 -0
- package/dist/commands/update/edit.js.map +1 -1
- package/dist/commands/update/list.d.ts.map +1 -1
- package/dist/commands/update/list.js +15 -12
- package/dist/commands/update/list.js.map +1 -1
- package/dist/commands/update/reply.d.ts.map +1 -1
- package/dist/commands/update/reply.js +11 -0
- package/dist/commands/update/reply.js.map +1 -1
- package/dist/commands/update/toggle.d.ts.map +1 -1
- package/dist/commands/update/toggle.js +13 -0
- package/dist/commands/update/toggle.js.map +1 -1
- package/dist/commands/usage.d.ts +58 -0
- package/dist/commands/usage.d.ts.map +1 -0
- package/dist/commands/usage.js +94 -0
- package/dist/commands/usage.js.map +1 -0
- package/dist/commands/webhook/create.d.ts +74 -0
- package/dist/commands/webhook/create.d.ts.map +1 -0
- package/dist/commands/webhook/create.js +150 -0
- package/dist/commands/webhook/create.js.map +1 -0
- package/dist/commands/webhook/delete.d.ts +46 -0
- package/dist/commands/webhook/delete.d.ts.map +1 -0
- package/dist/commands/webhook/delete.js +141 -0
- package/dist/commands/webhook/delete.js.map +1 -0
- package/dist/commands/webhook/list.d.ts +23 -0
- package/dist/commands/webhook/list.d.ts.map +1 -0
- package/dist/commands/webhook/list.js +68 -0
- package/dist/commands/webhook/list.js.map +1 -0
- package/dist/commands/workspace/create.d.ts.map +1 -1
- package/dist/commands/workspace/create.js +16 -0
- package/dist/commands/workspace/create.js.map +1 -1
- package/dist/commands/workspace/delete.d.ts.map +1 -1
- package/dist/commands/workspace/delete.js +13 -13
- package/dist/commands/workspace/delete.js.map +1 -1
- package/dist/commands/workspace/list.d.ts +1 -1
- package/dist/commands/workspace/update.d.ts.map +1 -1
- package/dist/commands/workspace/update.js +15 -15
- package/dist/commands/workspace/update.js.map +1 -1
- package/dist/config/credentials.d.ts +189 -0
- package/dist/config/credentials.d.ts.map +1 -0
- package/dist/config/credentials.js +300 -0
- package/dist/config/credentials.js.map +1 -0
- package/dist/config/profiles.d.ts +125 -0
- package/dist/config/profiles.d.ts.map +1 -0
- package/dist/config/profiles.js +227 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/types/global-flags.d.ts +1 -1
- package/dist/types/global-flags.d.ts.map +1 -1
- package/dist/types/global-flags.js +28 -16
- package/dist/types/global-flags.js.map +1 -1
- package/dist/types/ids.d.ts +2 -0
- package/dist/types/ids.d.ts.map +1 -1
- package/dist/types/ids.js +5 -3
- package/dist/types/ids.js.map +1 -1
- package/dist/utils/errors.d.ts +57 -3
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +69 -2
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/fs.d.ts +35 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +36 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/json.d.ts +60 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +86 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/output/ndjson.d.ts +65 -3
- package/dist/utils/output/ndjson.d.ts.map +1 -1
- package/dist/utils/output/ndjson.js +21 -0
- package/dist/utils/output/ndjson.js.map +1 -1
- package/dist/utils/redact.d.ts.map +1 -1
- package/dist/utils/redact.js +31 -0
- package/dist/utils/redact.js.map +1 -1
- package/package.json +2 -1
- package/dist/commands/account/client-helper.d.ts +0 -37
- package/dist/commands/account/client-helper.d.ts.map +0 -1
- package/dist/commands/account/client-helper.js +0 -55
- package/dist/commands/account/client-helper.js.map +0 -1
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-probe primitives for the v0.3-M22 `monday status` verb
|
|
3
|
+
* (cli-design §11.5 — Decision 7 closure: probe by default,
|
|
4
|
+
* `--no-probe` opts out).
|
|
5
|
+
*
|
|
6
|
+
* **What `monday status` answers:** "is everything I need to talk to
|
|
7
|
+
* Monday working, without touching account state?" The verb runs
|
|
8
|
+
* a short, deterministic probe matrix:
|
|
9
|
+
*
|
|
10
|
+
* 1. **DNS** — resolve the configured Monday hostname.
|
|
11
|
+
* 2. **TCP** — open a TCP connection to port 443.
|
|
12
|
+
* 3. **TLS** — complete a TLS handshake (cert validity, SNI).
|
|
13
|
+
* 4. **Auth** — issue the cheapest possible GraphQL read
|
|
14
|
+
* (`query { me { id } }`) against the resolved transport.
|
|
15
|
+
* 5. **Cache writability** — verify the local cache directory
|
|
16
|
+
* (`~/.monday-cli/`) exists with mode `0700` and is writable.
|
|
17
|
+
* 6. **Redaction self-test** — pass a known-fixture token through
|
|
18
|
+
* `redact()` and assert the bytes don't appear in the output.
|
|
19
|
+
* 7. **Env-var pickup summary** — local-only; reports which
|
|
20
|
+
* MONDAY_* env vars influenced the run (set/unset, never
|
|
21
|
+
* values). Helps an agent diagnose "why is the wrong profile
|
|
22
|
+
* being selected?" without spelunking dotfiles.
|
|
23
|
+
*
|
|
24
|
+
* **`--no-probe` semantics.** Skips probes 1–4 (the network-touching
|
|
25
|
+
* ones); probes 5–7 still run because they're local-only and don't
|
|
26
|
+
* touch account state. Use case: offline sanity check after rotating
|
|
27
|
+
* credentials, where the agent wants to confirm the cache + redaction
|
|
28
|
+
* + env-var resolution without firing a real Monday API call.
|
|
29
|
+
*
|
|
30
|
+
* **Empirical probe findings (2026-05-10, against `api.monday.com`,
|
|
31
|
+
* API version `2026-01`) — `scripts/probe/m22-status.ts`:**
|
|
32
|
+
*
|
|
33
|
+
* - **401 envelope shape (auth probe).** Status `401`, content-type
|
|
34
|
+
* `application/json; charset=utf-8`, body
|
|
35
|
+
* `{"errors":[{"message":"Not authenticated","extensions":
|
|
36
|
+
* {"code":"NOT_AUTHENTICATED"}}]}`. Identical envelope for
|
|
37
|
+
* missing-`Authorization` and bad-`Authorization`. The auth-probe
|
|
38
|
+
* step maps this verbatim to `unauthorized` (existing error code;
|
|
39
|
+
* NO new code needed for M22).
|
|
40
|
+
* - **`Bearer <token>` prefix DOES work** on `api.monday.com`
|
|
41
|
+
* alongside bare `<token>`. The CLI's `.claude/rules/security.md`
|
|
42
|
+
* rule against the prefix remains precautionary (proxies / logs
|
|
43
|
+
* sometimes split on `Bearer ` differently), not API-enforced.
|
|
44
|
+
*
|
|
45
|
+
* **Why no new ERROR_CODE for probes.** Each probe maps cleanly to
|
|
46
|
+
* an existing code: DNS / TCP / TLS / network failures →
|
|
47
|
+
* `network_error`; 401 → `unauthorized`; cache-writability failure
|
|
48
|
+
* → `config_error`; redaction self-test failure → `internal_error`
|
|
49
|
+
* (a serious bug, not a user-facing condition). Adding a
|
|
50
|
+
* `probe_failed` umbrella would widen the 29-code registry for
|
|
51
|
+
* marginal benefit; each probe's failure mode is best described by
|
|
52
|
+
* the existing semantic-domain code per cli-design §6.5.
|
|
53
|
+
*
|
|
54
|
+
* **Mockable-seam pattern.** Each probe accepts a per-probe seam
|
|
55
|
+
* (`lookupImpl` / `tcpConnectImpl` / `tlsConnectImpl` / `redactImpl`
|
|
56
|
+
* etc.) so tests don't bind real sockets / open real fetch handles /
|
|
57
|
+
* touch real DNS. Mirrors `src/api/transport.ts`'s `fetchImpl?` slot
|
|
58
|
+
* and the M21 `__test_oauth_helper` env seam: production callers
|
|
59
|
+
* leave the slot unset and the runner uses the real stdlib primitive.
|
|
60
|
+
*/
|
|
61
|
+
import { randomUUID } from 'node:crypto';
|
|
62
|
+
import { promises as dnsPromises } from 'node:dns';
|
|
63
|
+
import { createConnection } from 'node:net';
|
|
64
|
+
import { connect as tlsConnect } from 'node:tls';
|
|
65
|
+
import { access, constants as fsConstants, stat, unlink, writeFile, } from 'node:fs/promises';
|
|
66
|
+
import { homedir } from 'node:os';
|
|
67
|
+
import { join } from 'node:path';
|
|
68
|
+
import { z } from 'zod';
|
|
69
|
+
import { ApiError, errorCode, errorMessage } from '../utils/errors.js';
|
|
70
|
+
import { formatMode, isENOENT } from '../utils/fs.js';
|
|
71
|
+
import { redact as defaultRedactImpl } from '../utils/redact.js';
|
|
72
|
+
import { collectSecrets } from '../cli/envelope-out.js';
|
|
73
|
+
/**
|
|
74
|
+
* Stable iteration order for the status envelope's `probes` record.
|
|
75
|
+
* Matches the cli-design §11.5 probe-matrix narrative.
|
|
76
|
+
*/
|
|
77
|
+
export const STATUS_PROBE_ORDER = [
|
|
78
|
+
'dns',
|
|
79
|
+
'tcp',
|
|
80
|
+
'tls',
|
|
81
|
+
'auth',
|
|
82
|
+
'cache_writability',
|
|
83
|
+
'redaction_self_test',
|
|
84
|
+
'env_var_pickup',
|
|
85
|
+
];
|
|
86
|
+
/**
|
|
87
|
+
* The set of probes a `--no-probe` invocation skips (the network
|
|
88
|
+
* touching ones). Local-only probes always run.
|
|
89
|
+
*/
|
|
90
|
+
export const NO_PROBE_SKIP_SET = new Set([
|
|
91
|
+
'dns',
|
|
92
|
+
'tcp',
|
|
93
|
+
'tls',
|
|
94
|
+
'auth',
|
|
95
|
+
]);
|
|
96
|
+
/**
|
|
97
|
+
* The fixture canary token the redaction self-test scrubs. **Never a
|
|
98
|
+
* real token** — the byte sequence is deliberately distinctive so a
|
|
99
|
+
* leak-test assertion can scan every emitted byte for it.
|
|
100
|
+
*/
|
|
101
|
+
export const REDACTION_SELF_TEST_FIXTURE_TOKEN = 'tok-probe-redaction-self-test-DO-NOT-USE';
|
|
102
|
+
/** Default DNS / TCP / TLS host the probe matrix targets. */
|
|
103
|
+
export const DEFAULT_PROBE_HOSTNAME = 'api.monday.com';
|
|
104
|
+
/** Default port for TCP / TLS probes. */
|
|
105
|
+
export const DEFAULT_PROBE_PORT = 443;
|
|
106
|
+
/** Default per-probe timeout (ms). */
|
|
107
|
+
export const DEFAULT_PROBE_TIMEOUT_MS = 5_000;
|
|
108
|
+
/**
|
|
109
|
+
* The MONDAY_* env-var names the env-var-pickup probe summarises.
|
|
110
|
+
* Each entry maps to `{set: boolean}` in the probe's `details` —
|
|
111
|
+
* the **values are never echoed**, only the set/unset signal.
|
|
112
|
+
*/
|
|
113
|
+
export const ENV_VAR_PICKUP_KEYS = [
|
|
114
|
+
'MONDAY_API_TOKEN',
|
|
115
|
+
'MONDAY_PROFILE',
|
|
116
|
+
'MONDAY_API_VERSION',
|
|
117
|
+
'MONDAY_API_URL',
|
|
118
|
+
'MONDAY_OUTPUT',
|
|
119
|
+
'MONDAY_REQUEST_TIMEOUT_MS',
|
|
120
|
+
];
|
|
121
|
+
/**
|
|
122
|
+
* Cache directory name under HOME. Mirrors {@link
|
|
123
|
+
* import('../config/credentials.js').CREDENTIALS_DIR_NAME} verbatim —
|
|
124
|
+
* the cache + credentials share the same `~/.monday-cli/` parent.
|
|
125
|
+
*/
|
|
126
|
+
const CACHE_DIR_NAME = '.monday-cli';
|
|
127
|
+
/**
|
|
128
|
+
* Required directory mode for the cache parent. Anything more
|
|
129
|
+
* permissive than `0700` is flagged `mode_insecure`.
|
|
130
|
+
*/
|
|
131
|
+
const CACHE_DIR_REQUIRED_MODE_BITS = 0o077;
|
|
132
|
+
/**
|
|
133
|
+
* The 6 redaction-leak contexts cli-design §7.4.3 pins for the
|
|
134
|
+
* canary self-test (Codex M21 P1 ratification). Each entry's path is
|
|
135
|
+
* a stable diagnostic key the probe surfaces under
|
|
136
|
+
* `details.contexts_present`; the canary lives at each one.
|
|
137
|
+
*/
|
|
138
|
+
const REDACTION_LEAK_CONTEXT_KEYS = [
|
|
139
|
+
'error.message',
|
|
140
|
+
'error.stack',
|
|
141
|
+
'error.cause.message',
|
|
142
|
+
'headers.authorization',
|
|
143
|
+
'url',
|
|
144
|
+
'debug',
|
|
145
|
+
];
|
|
146
|
+
/**
|
|
147
|
+
* Coerces `AbortSignal.reason` (typed `any`) into an `Error` for the
|
|
148
|
+
* Promise-reject path. Standardises the abort-bubbling shape so
|
|
149
|
+
* every probe seam rejects with an `Error` per the lint rule
|
|
150
|
+
* `@typescript-eslint/prefer-promise-reject-errors`.
|
|
151
|
+
*
|
|
152
|
+
* Distinct from the shared {@link asError} in `utils/errors.ts`: the
|
|
153
|
+
* shared helper has no fallback parameter and uses `String(err)` as
|
|
154
|
+
* the no-Error fallback, which would render `"undefined"` for the
|
|
155
|
+
* `ctrl.abort()` (no-arg) case. This local variant takes a named
|
|
156
|
+
* fallback string that describes the abort source, useful when
|
|
157
|
+
* `AbortSignal.reason` is undefined.
|
|
158
|
+
*/
|
|
159
|
+
const asError = (reason, fallback) => {
|
|
160
|
+
if (reason instanceof Error) {
|
|
161
|
+
return reason;
|
|
162
|
+
}
|
|
163
|
+
/* c8 ignore next — `AbortSignal.reason` is an Error in every code
|
|
164
|
+
path the probes reach (timeout uses `ProbeTimeoutError`; outer
|
|
165
|
+
aborts are wrapped errors). Fallback exists for the unspecified
|
|
166
|
+
`ctrl.abort()` (no-arg) case. */
|
|
167
|
+
return new Error(fallback);
|
|
168
|
+
};
|
|
169
|
+
class ProbeTimeoutError extends Error {
|
|
170
|
+
constructor(probe, timeoutMs) {
|
|
171
|
+
super(`${probe} probe timed out after ${String(timeoutMs)}ms`);
|
|
172
|
+
this.name = 'ProbeTimeoutError';
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Builds a single-fire abort controller that fires when EITHER the
|
|
177
|
+
* per-probe deadline elapses OR the outer signal aborts. Each probe
|
|
178
|
+
* threads `abortCtx.signal` into its seam so a timeout cleanly
|
|
179
|
+
* tears down the in-flight socket / fetch handle.
|
|
180
|
+
*/
|
|
181
|
+
const createProbeAbortContext = (probe, timeoutMs, outerSignal) => {
|
|
182
|
+
const ctrl = new AbortController();
|
|
183
|
+
let timedOut = false;
|
|
184
|
+
const onOuterAbort = () => {
|
|
185
|
+
ctrl.abort(outerSignal?.reason);
|
|
186
|
+
};
|
|
187
|
+
const timer = setTimeout(() => {
|
|
188
|
+
timedOut = true;
|
|
189
|
+
ctrl.abort(new ProbeTimeoutError(probe, timeoutMs));
|
|
190
|
+
}, timeoutMs);
|
|
191
|
+
if (outerSignal !== undefined) {
|
|
192
|
+
if (outerSignal.aborted) {
|
|
193
|
+
ctrl.abort(outerSignal.reason);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
outerSignal.addEventListener('abort', onOuterAbort, { once: true });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
signal: ctrl.signal,
|
|
201
|
+
dispose: () => {
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
outerSignal?.removeEventListener('abort', onOuterAbort);
|
|
204
|
+
},
|
|
205
|
+
didTimeout: () => timedOut,
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
const ok = (probe, startMs, details) => ({
|
|
209
|
+
kind: 'ok',
|
|
210
|
+
probe,
|
|
211
|
+
elapsed_ms: Date.now() - startMs,
|
|
212
|
+
details,
|
|
213
|
+
});
|
|
214
|
+
const fail = (probe, startMs, reason, message, details) => ({
|
|
215
|
+
kind: 'fail',
|
|
216
|
+
probe,
|
|
217
|
+
elapsed_ms: Date.now() - startMs,
|
|
218
|
+
reason,
|
|
219
|
+
message,
|
|
220
|
+
details,
|
|
221
|
+
});
|
|
222
|
+
// Production-only path: binds to the OS resolver. Unit tests inject
|
|
223
|
+
// a `lookupImpl` seam; integration tests under `--no-probe` skip this
|
|
224
|
+
// path entirely. Real DNS-against-api.monday.com tests would be
|
|
225
|
+
// flaky in CI without a fixture resolver.
|
|
226
|
+
/* c8 ignore start */
|
|
227
|
+
const defaultDnsLookup = async (hostname) => {
|
|
228
|
+
const result = await dnsPromises.lookup(hostname, { all: false });
|
|
229
|
+
return { address: result.address, family: result.family };
|
|
230
|
+
};
|
|
231
|
+
/* c8 ignore stop */
|
|
232
|
+
/**
|
|
233
|
+
* Resolves `inputs.hostname` (default {@link DEFAULT_PROBE_HOSTNAME})
|
|
234
|
+
* via `node:dns/promises.lookup` (or the injected `lookupImpl` test
|
|
235
|
+
* seam). Maps `EAI_NONAME` / `ENOTFOUND` → `not_found`; `EAI_AGAIN`
|
|
236
|
+
* → `temporary_failure`; timeout → `timeout`; anything else →
|
|
237
|
+
* `lookup_failed`.
|
|
238
|
+
*/
|
|
239
|
+
export const runDnsProbe = async (inputs = {}) => {
|
|
240
|
+
const hostname = inputs.hostname ?? DEFAULT_PROBE_HOSTNAME;
|
|
241
|
+
const timeoutMs = inputs.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
242
|
+
// Production path goes through `defaultDnsLookup`; unit tests always
|
|
243
|
+
// inject `lookupImpl` for deterministic + offline coverage.
|
|
244
|
+
/* c8 ignore next */
|
|
245
|
+
const lookupImpl = inputs.lookupImpl ?? defaultDnsLookup;
|
|
246
|
+
const start = Date.now();
|
|
247
|
+
const abortCtx = createProbeAbortContext('dns', timeoutMs, inputs.signal);
|
|
248
|
+
try {
|
|
249
|
+
// `dns.promises.lookup` doesn't accept AbortSignal — race the
|
|
250
|
+
// lookup against the abort signal so the per-probe deadline still
|
|
251
|
+
// bounds the wall-clock cost. The leaked underlying lookup
|
|
252
|
+
// resolves into the void once the OS resolver finishes; not ideal
|
|
253
|
+
// but unavoidable without binding to a different resolver lib.
|
|
254
|
+
const result = await Promise.race([
|
|
255
|
+
lookupImpl(hostname),
|
|
256
|
+
new Promise((_, reject) => {
|
|
257
|
+
if (abortCtx.signal.aborted) {
|
|
258
|
+
reject(asError(abortCtx.signal.reason, 'dns probe aborted'));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
abortCtx.signal.addEventListener('abort', () => {
|
|
262
|
+
reject(asError(abortCtx.signal.reason, 'dns probe aborted'));
|
|
263
|
+
}, { once: true });
|
|
264
|
+
}),
|
|
265
|
+
]);
|
|
266
|
+
return ok('dns', start, {
|
|
267
|
+
address: result.address,
|
|
268
|
+
family: result.family,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
if (abortCtx.didTimeout()) {
|
|
273
|
+
return fail('dns', start, 'timeout', `dns lookup of ${hostname} timed out`, {
|
|
274
|
+
hostname,
|
|
275
|
+
timeout_ms: timeoutMs,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
const code = errorCode(err);
|
|
279
|
+
if (code === 'ENOTFOUND' || code === 'EAI_NONAME') {
|
|
280
|
+
return fail('dns', start, 'not_found', `host ${hostname} not found`, {
|
|
281
|
+
hostname,
|
|
282
|
+
code,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (code === 'EAI_AGAIN') {
|
|
286
|
+
return fail('dns', start, 'temporary_failure', `dns lookup temporarily failed`, {
|
|
287
|
+
hostname,
|
|
288
|
+
code,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
return fail('dns', start, 'lookup_failed', errorMessage(err), {
|
|
292
|
+
hostname,
|
|
293
|
+
...(code === undefined ? {} : { code }),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
abortCtx.dispose();
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
// Production-only path: binds a real TCP socket via `node:net`. Unit
|
|
301
|
+
// tests inject a `tcpConnectImpl` seam; integration tests run
|
|
302
|
+
// `--no-probe`. Real-socket test would require an ephemeral
|
|
303
|
+
// localhost listener + a TCP server, brittle vs. the seam-injected
|
|
304
|
+
// per-failure-mode matrix that already covers every reason
|
|
305
|
+
// discriminant.
|
|
306
|
+
/* c8 ignore start */
|
|
307
|
+
const defaultTcpConnect = ({ host, port, signal }) => new Promise((resolve, reject) => {
|
|
308
|
+
const socket = createConnection({ host, port });
|
|
309
|
+
const cleanup = () => {
|
|
310
|
+
socket.removeAllListeners();
|
|
311
|
+
signal.removeEventListener('abort', onAbort);
|
|
312
|
+
socket.destroy();
|
|
313
|
+
};
|
|
314
|
+
const onAbort = () => {
|
|
315
|
+
cleanup();
|
|
316
|
+
reject(asError(signal.reason, 'tcp connect aborted'));
|
|
317
|
+
};
|
|
318
|
+
if (signal.aborted) {
|
|
319
|
+
cleanup();
|
|
320
|
+
reject(asError(signal.reason, 'tcp connect aborted'));
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
324
|
+
socket.once('connect', () => {
|
|
325
|
+
cleanup();
|
|
326
|
+
resolve();
|
|
327
|
+
});
|
|
328
|
+
socket.once('error', (err) => {
|
|
329
|
+
cleanup();
|
|
330
|
+
reject(err);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
/* c8 ignore stop */
|
|
334
|
+
/**
|
|
335
|
+
* Opens a TCP connection to `inputs.hostname:inputs.port` (defaults
|
|
336
|
+
* {@link DEFAULT_PROBE_HOSTNAME} / {@link DEFAULT_PROBE_PORT}) via
|
|
337
|
+
* `node:net.createConnection` (or the injected seam). The socket is
|
|
338
|
+
* destroyed in every exit path; the per-probe deadline aborts the
|
|
339
|
+
* connection via the threaded signal.
|
|
340
|
+
*/
|
|
341
|
+
export const runTcpProbe = async (inputs = {}) => {
|
|
342
|
+
const hostname = inputs.hostname ?? DEFAULT_PROBE_HOSTNAME;
|
|
343
|
+
const port = inputs.port ?? DEFAULT_PROBE_PORT;
|
|
344
|
+
const timeoutMs = inputs.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
345
|
+
// Production path; unit tests inject `tcpConnectImpl`.
|
|
346
|
+
/* c8 ignore next */
|
|
347
|
+
const tcpConnectImpl = inputs.tcpConnectImpl ?? defaultTcpConnect;
|
|
348
|
+
const start = Date.now();
|
|
349
|
+
const abortCtx = createProbeAbortContext('tcp', timeoutMs, inputs.signal);
|
|
350
|
+
try {
|
|
351
|
+
await tcpConnectImpl({ host: hostname, port, signal: abortCtx.signal });
|
|
352
|
+
return ok('tcp', start, { host: hostname, port });
|
|
353
|
+
}
|
|
354
|
+
catch (err) {
|
|
355
|
+
if (abortCtx.didTimeout()) {
|
|
356
|
+
return fail('tcp', start, 'timeout', `tcp connect to ${hostname}:${String(port)} timed out`, { host: hostname, port, timeout_ms: timeoutMs });
|
|
357
|
+
}
|
|
358
|
+
const code = errorCode(err);
|
|
359
|
+
if (code === 'ECONNREFUSED') {
|
|
360
|
+
return fail('tcp', start, 'connection_refused', errorMessage(err), {
|
|
361
|
+
host: hostname,
|
|
362
|
+
port,
|
|
363
|
+
code,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
if (code === 'ECONNRESET') {
|
|
367
|
+
return fail('tcp', start, 'connection_reset', errorMessage(err), {
|
|
368
|
+
host: hostname,
|
|
369
|
+
port,
|
|
370
|
+
code,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
if (code === 'ETIMEDOUT') {
|
|
374
|
+
return fail('tcp', start, 'timeout', errorMessage(err), {
|
|
375
|
+
host: hostname,
|
|
376
|
+
port,
|
|
377
|
+
code,
|
|
378
|
+
timeout_ms: timeoutMs,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
return fail('tcp', start, 'connection_failed', errorMessage(err), {
|
|
382
|
+
host: hostname,
|
|
383
|
+
port,
|
|
384
|
+
...(code === undefined ? {} : { code }),
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
finally {
|
|
388
|
+
abortCtx.dispose();
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
// Production-only path: binds a real TLS socket via `node:tls`. Unit
|
|
392
|
+
// tests inject a `tlsConnectImpl` seam; integration tests under
|
|
393
|
+
// `--no-probe` skip this path. A real-cert test would require a
|
|
394
|
+
// disposable TLS server with a self-signed cert, brittle vs. the
|
|
395
|
+
// seam-injected per-failure-mode coverage (cert_expired,
|
|
396
|
+
// cert_untrusted, cert_name_mismatch, etc.).
|
|
397
|
+
/* c8 ignore start */
|
|
398
|
+
const defaultTlsConnect = ({ host, port, signal }) => new Promise((resolve, reject) => {
|
|
399
|
+
const socket = tlsConnect({
|
|
400
|
+
host,
|
|
401
|
+
port,
|
|
402
|
+
servername: host,
|
|
403
|
+
rejectUnauthorized: true,
|
|
404
|
+
});
|
|
405
|
+
const cleanup = () => {
|
|
406
|
+
socket.removeAllListeners();
|
|
407
|
+
signal.removeEventListener('abort', onAbort);
|
|
408
|
+
socket.destroy();
|
|
409
|
+
};
|
|
410
|
+
const onAbort = () => {
|
|
411
|
+
cleanup();
|
|
412
|
+
reject(asError(signal.reason, 'tls handshake aborted'));
|
|
413
|
+
};
|
|
414
|
+
if (signal.aborted) {
|
|
415
|
+
cleanup();
|
|
416
|
+
reject(asError(signal.reason, 'tls handshake aborted'));
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
420
|
+
socket.once('secureConnect', () => {
|
|
421
|
+
const cert = socket.getPeerCertificate();
|
|
422
|
+
cleanup();
|
|
423
|
+
// Node's `getPeerCertificate()` types `subject`/`issuer` as
|
|
424
|
+
// required objects, but the runtime can return an empty `{}`
|
|
425
|
+
// when the peer didn't send a cert (which `rejectUnauthorized`
|
|
426
|
+
// would reject anyway). Narrow defensively via typeof.
|
|
427
|
+
const subjectCn = cert.subject?.CN;
|
|
428
|
+
const issuerCn = cert.issuer?.CN;
|
|
429
|
+
const subject = typeof subjectCn === 'string' && subjectCn.length > 0 ? subjectCn : '<unknown>';
|
|
430
|
+
const issuer = typeof issuerCn === 'string' && issuerCn.length > 0 ? issuerCn : '<unknown>';
|
|
431
|
+
const validTo = typeof cert.valid_to === 'string' ? cert.valid_to : '<unknown>';
|
|
432
|
+
resolve({ subject, issuer, valid_to: validTo });
|
|
433
|
+
});
|
|
434
|
+
socket.once('error', (err) => {
|
|
435
|
+
cleanup();
|
|
436
|
+
reject(err);
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
/* c8 ignore stop */
|
|
440
|
+
/**
|
|
441
|
+
* Completes a TLS handshake against `inputs.hostname:inputs.port`
|
|
442
|
+
* with SNI set + cert validation on. Captures the peer cert summary
|
|
443
|
+
* (`subject`/`issuer`/`valid_to`) into `details` on success; maps
|
|
444
|
+
* `CERT_HAS_EXPIRED` → `cert_expired`, `UNABLE_TO_VERIFY_LEAF_SIGNATURE`
|
|
445
|
+
* → `cert_untrusted`, `ERR_TLS_CERT_ALTNAME_INVALID` →
|
|
446
|
+
* `cert_name_mismatch`, anything else → `tls_handshake_failed`.
|
|
447
|
+
*/
|
|
448
|
+
export const runTlsProbe = async (inputs = {}) => {
|
|
449
|
+
const hostname = inputs.hostname ?? DEFAULT_PROBE_HOSTNAME;
|
|
450
|
+
const port = inputs.port ?? DEFAULT_PROBE_PORT;
|
|
451
|
+
const timeoutMs = inputs.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
452
|
+
// Production path; unit tests inject `tlsConnectImpl`.
|
|
453
|
+
/* c8 ignore next */
|
|
454
|
+
const tlsConnectImpl = inputs.tlsConnectImpl ?? defaultTlsConnect;
|
|
455
|
+
const start = Date.now();
|
|
456
|
+
const abortCtx = createProbeAbortContext('tls', timeoutMs, inputs.signal);
|
|
457
|
+
try {
|
|
458
|
+
const cert = await tlsConnectImpl({
|
|
459
|
+
host: hostname,
|
|
460
|
+
port,
|
|
461
|
+
signal: abortCtx.signal,
|
|
462
|
+
});
|
|
463
|
+
return ok('tls', start, {
|
|
464
|
+
subject: cert.subject,
|
|
465
|
+
issuer: cert.issuer,
|
|
466
|
+
valid_to: cert.valid_to,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
catch (err) {
|
|
470
|
+
if (abortCtx.didTimeout()) {
|
|
471
|
+
return fail('tls', start, 'timeout', `tls handshake to ${hostname} timed out`, {
|
|
472
|
+
host: hostname,
|
|
473
|
+
port,
|
|
474
|
+
timeout_ms: timeoutMs,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
const code = errorCode(err);
|
|
478
|
+
if (code === 'CERT_HAS_EXPIRED') {
|
|
479
|
+
return fail('tls', start, 'cert_expired', errorMessage(err), {
|
|
480
|
+
host: hostname,
|
|
481
|
+
port,
|
|
482
|
+
code,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
if (code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
|
|
486
|
+
return fail('tls', start, 'cert_untrusted', errorMessage(err), {
|
|
487
|
+
host: hostname,
|
|
488
|
+
port,
|
|
489
|
+
code,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
if (code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
|
|
493
|
+
return fail('tls', start, 'cert_name_mismatch', errorMessage(err), {
|
|
494
|
+
host: hostname,
|
|
495
|
+
port,
|
|
496
|
+
code,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return fail('tls', start, 'tls_handshake_failed', errorMessage(err), {
|
|
500
|
+
host: hostname,
|
|
501
|
+
port,
|
|
502
|
+
...(code === undefined ? {} : { code }),
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
finally {
|
|
506
|
+
abortCtx.dispose();
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
/**
|
|
510
|
+
* Issues `query { me { id } }` against the supplied transport. Maps
|
|
511
|
+
* the empirical-probe-confirmed 401 envelope to `unauthorized`;
|
|
512
|
+
* 5xx HTTP status / transport failures (DNS recovery, TCP reset, TLS
|
|
513
|
+
* handshake error mid-flight) to `network_error` per cli-design
|
|
514
|
+
* §11.5.1's per-probe error-code table.
|
|
515
|
+
*
|
|
516
|
+
* The probe's contract is "can we authenticate?", not "can we
|
|
517
|
+
* complete a request budget-wise?". A `complexity_exceeded` from
|
|
518
|
+
* `me { id }` would mean the budget is pathological; the auth probe
|
|
519
|
+
* surfaces it as `auth_failed` rather than mapping into the §11.5.1
|
|
520
|
+
* table's two reasons, so the verb-level envelope reads cleanly.
|
|
521
|
+
*/
|
|
522
|
+
export const runAuthProbe = async (inputs) => {
|
|
523
|
+
const timeoutMs = inputs.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
524
|
+
const start = Date.now();
|
|
525
|
+
const abortCtx = createProbeAbortContext('auth', timeoutMs, inputs.signal);
|
|
526
|
+
try {
|
|
527
|
+
const response = await inputs.transport.request({
|
|
528
|
+
query: 'query MondayStatusAuth { me { id } }',
|
|
529
|
+
operationName: 'MondayStatusAuth',
|
|
530
|
+
signal: abortCtx.signal,
|
|
531
|
+
});
|
|
532
|
+
if (response.status === 401) {
|
|
533
|
+
return fail('auth', start, 'unauthorized', 'Monday returned 401', {
|
|
534
|
+
http_status: 401,
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
if (response.status >= 500) {
|
|
538
|
+
return fail('auth', start, 'network_error', `Monday returned HTTP ${String(response.status)}`, { http_status: response.status });
|
|
539
|
+
}
|
|
540
|
+
if (response.status !== 200) {
|
|
541
|
+
return fail('auth', start, 'network_error', `unexpected HTTP status ${String(response.status)} from auth probe`, { http_status: response.status });
|
|
542
|
+
}
|
|
543
|
+
const body = response.body;
|
|
544
|
+
if (typeof body !== 'object' || body === null) {
|
|
545
|
+
return fail('auth', start, 'network_error', 'auth probe response was not a JSON object', { http_status: response.status });
|
|
546
|
+
}
|
|
547
|
+
const bodyRecord = body;
|
|
548
|
+
const errorsField = bodyRecord.errors;
|
|
549
|
+
if (Array.isArray(errorsField) && errorsField.length > 0) {
|
|
550
|
+
// §11.5.1 unauthorized-mapping is "any errors[] entry carries
|
|
551
|
+
// an auth code", not "the first entry". Scan every entry so a
|
|
552
|
+
// mixed-error response (e.g., complexity warning followed by
|
|
553
|
+
// an auth-token expiration) still maps to `unauthorized` — the
|
|
554
|
+
// security-bearing reading wins over the first-error read.
|
|
555
|
+
// Codex M22 W2 ratified.
|
|
556
|
+
const extractCode = (entry) => {
|
|
557
|
+
if (typeof entry !== 'object' || entry === null)
|
|
558
|
+
return undefined;
|
|
559
|
+
const ext = entry.extensions;
|
|
560
|
+
if (typeof ext !== 'object' || ext === null)
|
|
561
|
+
return undefined;
|
|
562
|
+
const code = ext.code;
|
|
563
|
+
return typeof code === 'string' ? code : undefined;
|
|
564
|
+
};
|
|
565
|
+
const extractMessage = (entry) => {
|
|
566
|
+
if (typeof entry !== 'object' || entry === null)
|
|
567
|
+
return undefined;
|
|
568
|
+
const m = entry.message;
|
|
569
|
+
return typeof m === 'string' ? m : undefined;
|
|
570
|
+
};
|
|
571
|
+
const errorsList = errorsField;
|
|
572
|
+
const authEntry = errorsList.find((e) => {
|
|
573
|
+
const code = extractCode(e);
|
|
574
|
+
return code === 'NOT_AUTHENTICATED' || code === 'UNAUTHENTICATED';
|
|
575
|
+
});
|
|
576
|
+
if (authEntry !== undefined) {
|
|
577
|
+
const code = extractCode(authEntry);
|
|
578
|
+
const msg = extractMessage(authEntry) ?? 'auth probe rejected';
|
|
579
|
+
return fail('auth', start, 'unauthorized', msg, {
|
|
580
|
+
http_status: response.status,
|
|
581
|
+
monday_code: code,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
const first = errorsList[0];
|
|
585
|
+
const firstCode = extractCode(first);
|
|
586
|
+
const firstMsg = extractMessage(first) ?? 'auth probe rejected';
|
|
587
|
+
return fail('auth', start, 'auth_failed', firstMsg, {
|
|
588
|
+
http_status: response.status,
|
|
589
|
+
...(firstCode === undefined ? {} : { monday_code: firstCode }),
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
const data = bodyRecord.data;
|
|
593
|
+
/* c8 ignore next 9 — defensive: Monday's GraphQL surface always
|
|
594
|
+
returns `data: {...}` on a 200, and the errors[] branch above
|
|
595
|
+
handles the `data: null` case. This guard exists for hypothetical
|
|
596
|
+
transport shapes that 200-without-data; not reproducible from
|
|
597
|
+
a fixture cassette that returns a valid GraphQL envelope. */
|
|
598
|
+
if (typeof data !== 'object' || data === null) {
|
|
599
|
+
return fail('auth', start, 'network_error', 'auth probe response missing `data`', { http_status: response.status });
|
|
600
|
+
}
|
|
601
|
+
const me = data.me;
|
|
602
|
+
if (typeof me !== 'object' || me === null) {
|
|
603
|
+
return fail('auth', start, 'unauthorized', 'auth probe returned null `me`', {
|
|
604
|
+
http_status: response.status,
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
const meId = me.id;
|
|
608
|
+
if (typeof meId !== 'string' || meId.length === 0) {
|
|
609
|
+
return fail('auth', start, 'network_error', 'auth probe response had unexpected `me` shape', { http_status: response.status });
|
|
610
|
+
}
|
|
611
|
+
const details = { me_id: meId };
|
|
612
|
+
if (inputs.apiVersionHint !== undefined) {
|
|
613
|
+
details.api_version = inputs.apiVersionHint;
|
|
614
|
+
}
|
|
615
|
+
return ok('auth', start, details);
|
|
616
|
+
}
|
|
617
|
+
catch (err) {
|
|
618
|
+
if (abortCtx.didTimeout()) {
|
|
619
|
+
return fail('auth', start, 'timeout', 'auth probe timed out', {
|
|
620
|
+
timeout_ms: timeoutMs,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
if (err instanceof ApiError) {
|
|
624
|
+
if (err.code === 'unauthorized') {
|
|
625
|
+
return fail('auth', start, 'unauthorized', err.message, {
|
|
626
|
+
http_status: err.httpStatus ?? 401,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
return fail('auth', start, 'network_error', err.message, {
|
|
630
|
+
...(err.httpStatus === undefined ? {} : { http_status: err.httpStatus }),
|
|
631
|
+
error_code: err.code,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
return fail('auth', start, 'network_error', errorMessage(err), {});
|
|
635
|
+
}
|
|
636
|
+
finally {
|
|
637
|
+
abortCtx.dispose();
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
/**
|
|
641
|
+
* Verifies the local cache directory (`<HOME>/.monday-cli/`) exists,
|
|
642
|
+
* is mode `0700`, and is writable. Stat + access(W_OK) + probe-write
|
|
643
|
+
* (`<dir>/.probe-<rand>`) → delete dance — `access(W_OK)` is advisory
|
|
644
|
+
* on some Unix filesystems, so a probe-write confirms true writability.
|
|
645
|
+
*/
|
|
646
|
+
export const runCacheWritabilityProbe = async (inputs) => {
|
|
647
|
+
const home = inputs.home ?? inputs.env.HOME ?? homedir();
|
|
648
|
+
const cacheDir = join(home, CACHE_DIR_NAME);
|
|
649
|
+
const start = Date.now();
|
|
650
|
+
let stats;
|
|
651
|
+
try {
|
|
652
|
+
stats = await stat(cacheDir);
|
|
653
|
+
}
|
|
654
|
+
catch (err) {
|
|
655
|
+
if (isENOENT(err)) {
|
|
656
|
+
return fail('cache_writability', start, 'dir_missing', `cache directory ${cacheDir} does not exist`, {
|
|
657
|
+
path: cacheDir,
|
|
658
|
+
hint: 'run `monday auth login --profile <name>` or any cache-writing command to create it',
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
// Non-ENOENT stat failures (EACCES on the parent dir, etc.) aren't
|
|
662
|
+
// reproducible without root or chroot; ENOENT is the only failure
|
|
663
|
+
// mode tests can drive against a tmp dir.
|
|
664
|
+
/* c8 ignore start */
|
|
665
|
+
return fail('cache_writability', start, 'stat_failed', `cannot stat cache directory ${cacheDir}: ${errorMessage(err)}`, { path: cacheDir });
|
|
666
|
+
/* c8 ignore stop */
|
|
667
|
+
}
|
|
668
|
+
if (!stats.isDirectory()) {
|
|
669
|
+
return fail('cache_writability', start, 'not_a_directory', `cache path ${cacheDir} is not a directory`, { path: cacheDir });
|
|
670
|
+
}
|
|
671
|
+
if ((stats.mode & CACHE_DIR_REQUIRED_MODE_BITS) !== 0) {
|
|
672
|
+
return fail('cache_writability', start, 'mode_insecure', `cache directory ${cacheDir} has insecure permissions ${formatMode(stats.mode)}`, {
|
|
673
|
+
path: cacheDir,
|
|
674
|
+
mode: formatMode(stats.mode),
|
|
675
|
+
hint: `run \`chmod 700 ${cacheDir}\` to tighten`,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
try {
|
|
679
|
+
await access(cacheDir, fsConstants.W_OK);
|
|
680
|
+
}
|
|
681
|
+
catch (err) {
|
|
682
|
+
// access(W_OK) failure requires the dir to exist but be non-writable
|
|
683
|
+
// — needs root + ownership manipulation to reproduce against a
|
|
684
|
+
// tmp dir owned by the test user. Production-only.
|
|
685
|
+
/* c8 ignore start */
|
|
686
|
+
return fail('cache_writability', start, 'permission_denied', `cache directory ${cacheDir} is not writable: ${errorMessage(err)}`, { path: cacheDir, mode: formatMode(stats.mode) });
|
|
687
|
+
/* c8 ignore stop */
|
|
688
|
+
}
|
|
689
|
+
const probeFile = join(cacheDir, `.probe-${randomUUID()}`);
|
|
690
|
+
try {
|
|
691
|
+
await writeFile(probeFile, '', { mode: 0o600 });
|
|
692
|
+
}
|
|
693
|
+
catch (err) {
|
|
694
|
+
// writeFile failure when access(W_OK) succeeded is a TOCTOU race
|
|
695
|
+
// (between the access check and the write), filesystem-full, or
|
|
696
|
+
// similar runtime-rare condition. Production-only.
|
|
697
|
+
return fail('cache_writability', start, 'write_failed', `probe write to ${cacheDir} failed: ${errorMessage(err)}`, { path: cacheDir, mode: formatMode(stats.mode) });
|
|
698
|
+
/* c8 ignore stop */
|
|
699
|
+
}
|
|
700
|
+
// Best-effort cleanup; if unlink fails we still succeed because the
|
|
701
|
+
// write succeeded — a leaked probe file is harmless.
|
|
702
|
+
await unlink(probeFile).catch(() => undefined);
|
|
703
|
+
return ok('cache_writability', start, {
|
|
704
|
+
path: cacheDir,
|
|
705
|
+
mode: formatMode(stats.mode),
|
|
706
|
+
});
|
|
707
|
+
};
|
|
708
|
+
/**
|
|
709
|
+
* Folds {@link REDACTION_SELF_TEST_FIXTURE_TOKEN} into
|
|
710
|
+
* `inputs.runtimeSecrets`, runs a sample object (carrying the
|
|
711
|
+
* canary in `error.message`, `error.stack`, a nested
|
|
712
|
+
* `error.cause.message`, a URL, and a lowercase
|
|
713
|
+
* `authorization` header value, plus a mixed-content debug string)
|
|
714
|
+
* through `redact()` with `collectSecrets(env, runtimeSecrets)`, and
|
|
715
|
+
* asserts the canary is absent from every byte of the redacted
|
|
716
|
+
* output. Removes the canary from `runtimeSecrets` before returning
|
|
717
|
+
* so the caller's state is unchanged.
|
|
718
|
+
*
|
|
719
|
+
* Failure here indicates a regression in the redaction layer
|
|
720
|
+
* itself (a serious bug); the probe surfaces `canary_leaked` with
|
|
721
|
+
* `details.contexts_leaked` listing which §7.4.3 enumerated
|
|
722
|
+
* carrier paths still hold the canary.
|
|
723
|
+
*/
|
|
724
|
+
export const runRedactionSelfTest = (inputs) => Promise.resolve(runRedactionSelfTestSync(inputs));
|
|
725
|
+
const runRedactionSelfTestSync = (inputs) => {
|
|
726
|
+
const start = Date.now();
|
|
727
|
+
const canary = REDACTION_SELF_TEST_FIXTURE_TOKEN;
|
|
728
|
+
// Production path; the leak-path test injects a tampered redactor.
|
|
729
|
+
/* c8 ignore next */
|
|
730
|
+
const redactImpl = inputs.redactImpl ?? defaultRedactImpl;
|
|
731
|
+
inputs.runtimeSecrets.push(canary);
|
|
732
|
+
try {
|
|
733
|
+
const innerError = new Error(`inner failure carrying ${canary}`);
|
|
734
|
+
const outerError = new Error(`outer failure carrying ${canary}`);
|
|
735
|
+
outerError.cause = innerError;
|
|
736
|
+
const sample = {
|
|
737
|
+
error: {
|
|
738
|
+
message: outerError.message,
|
|
739
|
+
// `Error.stack` is always populated under V8; the fallback is a
|
|
740
|
+
// defensive guard for embedders with custom error constructors.
|
|
741
|
+
/* c8 ignore next */
|
|
742
|
+
stack: outerError.stack ?? '',
|
|
743
|
+
cause: {
|
|
744
|
+
message: innerError.message,
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
headers: {
|
|
748
|
+
authorization: canary,
|
|
749
|
+
},
|
|
750
|
+
url: `https://api.monday.com/v2?bogus=${canary}`,
|
|
751
|
+
debug: `auth=${canary} expired`,
|
|
752
|
+
};
|
|
753
|
+
const redacted = redactImpl(sample, {
|
|
754
|
+
secrets: collectSecrets(inputs.env, inputs.runtimeSecrets),
|
|
755
|
+
});
|
|
756
|
+
const serialised = JSON.stringify(redacted);
|
|
757
|
+
if (serialised.includes(canary)) {
|
|
758
|
+
const leakedContexts = [];
|
|
759
|
+
const redactedRecord = redacted;
|
|
760
|
+
const stringAt = (path) => {
|
|
761
|
+
let cur = redactedRecord;
|
|
762
|
+
for (const segment of path) {
|
|
763
|
+
/* c8 ignore next — defensive: paths into the fixture-built
|
|
764
|
+
sample object are always traversable when the redactor is
|
|
765
|
+
identity-mapped; non-object hops only happen when a
|
|
766
|
+
future redactor materially changes the shape. */
|
|
767
|
+
if (typeof cur !== 'object' || cur === null)
|
|
768
|
+
return undefined;
|
|
769
|
+
cur = cur[segment];
|
|
770
|
+
}
|
|
771
|
+
/* c8 ignore next — fixture leaves canary as a string at every
|
|
772
|
+
path; non-string terminals only appear via a rewriting
|
|
773
|
+
redactor. */
|
|
774
|
+
return typeof cur === 'string' ? cur : undefined;
|
|
775
|
+
};
|
|
776
|
+
const checks = [
|
|
777
|
+
{ key: 'error.message', path: ['error', 'message'] },
|
|
778
|
+
{ key: 'error.stack', path: ['error', 'stack'] },
|
|
779
|
+
{ key: 'error.cause.message', path: ['error', 'cause', 'message'] },
|
|
780
|
+
{ key: 'headers.authorization', path: ['headers', 'authorization'] },
|
|
781
|
+
{ key: 'url', path: ['url'] },
|
|
782
|
+
{ key: 'debug', path: ['debug'] },
|
|
783
|
+
];
|
|
784
|
+
for (const check of checks) {
|
|
785
|
+
const value = stringAt(check.path);
|
|
786
|
+
/* c8 ignore next — `value?.includes(canary)` short-circuits
|
|
787
|
+
on `value === undefined`; defensive false-arm. */
|
|
788
|
+
if (value?.includes(canary) === true) {
|
|
789
|
+
leakedContexts.push(check.key);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return fail('redaction_self_test', start, 'canary_leaked', 'redaction self-test canary leaked through redact()', {
|
|
793
|
+
fixture_count: REDACTION_LEAK_CONTEXT_KEYS.length,
|
|
794
|
+
contexts_leaked: leakedContexts,
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
return ok('redaction_self_test', start, {
|
|
798
|
+
fixture_count: REDACTION_LEAK_CONTEXT_KEYS.length,
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
finally {
|
|
802
|
+
const idx = inputs.runtimeSecrets.lastIndexOf(canary);
|
|
803
|
+
// Defensive: we just pushed the canary at the top of the try; the
|
|
804
|
+
// only way idx === -1 is if the test removed it mid-call (which
|
|
805
|
+
// it doesn't). Kept defensive so the splice is safe.
|
|
806
|
+
/* c8 ignore next */
|
|
807
|
+
if (idx !== -1) {
|
|
808
|
+
inputs.runtimeSecrets.splice(idx, 1);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
/**
|
|
813
|
+
* Summarises which of {@link ENV_VAR_PICKUP_KEYS} are set on
|
|
814
|
+
* `inputs.env`. **Values are NEVER included** — an env var's value
|
|
815
|
+
* is potentially a token. Wrapped in a Promise so callers can
|
|
816
|
+
* `Promise.allSettled` the full matrix without special-casing this
|
|
817
|
+
* step.
|
|
818
|
+
*/
|
|
819
|
+
export const summariseEnvVarPickup = (inputs) => {
|
|
820
|
+
const start = Date.now();
|
|
821
|
+
const set = {};
|
|
822
|
+
for (const key of ENV_VAR_PICKUP_KEYS) {
|
|
823
|
+
const value = inputs.env[key];
|
|
824
|
+
set[key] = value !== undefined && value.length > 0;
|
|
825
|
+
}
|
|
826
|
+
return Promise.resolve(ok('env_var_pickup', start, { set }));
|
|
827
|
+
};
|
|
828
|
+
/**
|
|
829
|
+
* Schema validating an individual probe result for the
|
|
830
|
+
* `monday status` envelope. Mirrors the runtime
|
|
831
|
+
* {@link ProbeResult} union so the envelope is parse-safe at every
|
|
832
|
+
* emission path (in particular `monday schema` introspection).
|
|
833
|
+
*/
|
|
834
|
+
export const probeResultSchema = z.discriminatedUnion('kind', [
|
|
835
|
+
z.object({
|
|
836
|
+
kind: z.literal('ok'),
|
|
837
|
+
probe: z.string().min(1),
|
|
838
|
+
elapsed_ms: z.number().nonnegative(),
|
|
839
|
+
details: z.record(z.string(), z.unknown()),
|
|
840
|
+
}),
|
|
841
|
+
z.object({
|
|
842
|
+
kind: z.literal('fail'),
|
|
843
|
+
probe: z.string().min(1),
|
|
844
|
+
elapsed_ms: z.number().nonnegative(),
|
|
845
|
+
reason: z.string().min(1),
|
|
846
|
+
message: z.string().min(1),
|
|
847
|
+
details: z.record(z.string(), z.unknown()),
|
|
848
|
+
}),
|
|
849
|
+
z.object({
|
|
850
|
+
kind: z.literal('skipped'),
|
|
851
|
+
probe: z.string().min(1),
|
|
852
|
+
reason: z.string().min(1),
|
|
853
|
+
}),
|
|
854
|
+
]);
|
|
855
|
+
/**
|
|
856
|
+
* Top-level `monday status` envelope-data schema. `probes` requires
|
|
857
|
+
* every {@link STATUS_PROBE_ORDER} entry to be populated (default
|
|
858
|
+
* run AND `--no-probe` run — `--no-probe` emits `ProbeSkipped` slots
|
|
859
|
+
* for the four network probes; local probes still produce real
|
|
860
|
+
* entries). Catchall allows additive future probes (per cli-design
|
|
861
|
+
* §6.1) WITHOUT a schema-version bump; missing-slot drift gets
|
|
862
|
+
* caught at parse time.
|
|
863
|
+
*/
|
|
864
|
+
export const statusOutputSchema = z
|
|
865
|
+
.object({
|
|
866
|
+
probes: z
|
|
867
|
+
.object({
|
|
868
|
+
dns: probeResultSchema,
|
|
869
|
+
tcp: probeResultSchema,
|
|
870
|
+
tls: probeResultSchema,
|
|
871
|
+
auth: probeResultSchema,
|
|
872
|
+
cache_writability: probeResultSchema,
|
|
873
|
+
redaction_self_test: probeResultSchema,
|
|
874
|
+
env_var_pickup: probeResultSchema,
|
|
875
|
+
})
|
|
876
|
+
.catchall(probeResultSchema),
|
|
877
|
+
overall: z.enum(['ok', 'degraded', 'down']),
|
|
878
|
+
api_version: z.string().min(1),
|
|
879
|
+
})
|
|
880
|
+
.strict();
|
|
881
|
+
//# sourceMappingURL=probes.js.map
|