diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 4bb7a021..8c9c6072 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -49,7 +49,7 @@ jobs: - name: Setup .npmrc file to publish to npm uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: '26.2.0' + node-version: '26.4.0' registry-url: 'https://registry.npmjs.org' package-manager-cache: false - name: Enable corepack diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index 19cfa50d..00000000 --- a/.tool-versions +++ /dev/null @@ -1,2 +0,0 @@ -nodejs 26.2.0 - diff --git a/mise.toml b/mise.toml new file mode 100644 index 00000000..8ab0b32b --- /dev/null +++ b/mise.toml @@ -0,0 +1,6 @@ +[tools] +node = "26.4.0" + +[tools."npm:corepack"] +version = "0.35.0" +postinstall = "corepack enable" diff --git a/src/utils/check-pyroscope-config.ts b/src/utils/check-pyroscope-config.ts index 7945f5c2..9e689c2e 100644 --- a/src/utils/check-pyroscope-config.ts +++ b/src/utils/check-pyroscope-config.ts @@ -4,6 +4,9 @@ import { PyroscopeWallConfig, } from '../pyroscope-config.js'; +const INVALID_LABEL_CHARACTERS = ['{', '}', ',', '='] as const; +const INVALID_LABEL_CHARACTERS_MESSAGE = "'{', '}', ',', or '=' characters"; + export function checkPyroscopeConfig( config: unknown ): asserts config is PyroscopeConfig { @@ -15,8 +18,12 @@ export function checkPyroscopeConfig( if (!hasValidApplicationName(config)) { errors.push('Expecting a config with string appName'); + } else { + validateApplicationName(config, errors); } + validateTags(config, errors); + if (!hasValidAuthToken(config)) { errors.push('Expecting a config with string auth token'); } @@ -51,6 +58,64 @@ function hasValidApplicationName( ); } +function validateApplicationName( + config: Record, + errors: string[] +): void { + const appName: string | undefined = (config as Partial) + .appName; + + if (appName === undefined) { + return; + } + + if (hasInvalidLabelCharacters(appName)) { + errors.push(`appName must not contain ${INVALID_LABEL_CHARACTERS_MESSAGE}`); + } +} + +function validateTags( + config: Record, + errors: string[] +): void { + const tags: unknown = (config as Partial).tags; + + if (tags === undefined) { + return; + } + + if (!isObject(tags)) { + errors.push('Expecting a config with object tags'); + return; + } + + for (const [tagKey, tagValue] of Object.entries(tags)) { + if (hasInvalidLabelCharacters(tagKey)) { + errors.push( + `tag key "${tagKey}" must not contain ${INVALID_LABEL_CHARACTERS_MESSAGE}` + ); + } + + if (typeof tagValue === 'string' && hasInvalidLabelCharacters(tagValue)) { + errors.push( + `tag value for "${tagKey}" must not contain ${INVALID_LABEL_CHARACTERS_MESSAGE}` + ); + } else if (typeof tagValue !== 'number' && typeof tagValue !== 'string') { + errors.push(`tag value for "${tagKey}" must be a string or number`); + } + } +} + +function hasInvalidLabelCharacters(value: string): boolean { + for (const character of INVALID_LABEL_CHARACTERS) { + if (value.includes(character)) { + return true; + } + } + + return false; +} + function hasValidAuthToken(config: Record): boolean { return ( (config as Partial).authToken === undefined ||