diff --git a/code/core/src/telemetry/anonymous-id.test.ts b/code/core/src/telemetry/anonymous-id.test.ts index 6606f43cabc2..8277ca547e85 100644 --- a/code/core/src/telemetry/anonymous-id.test.ts +++ b/code/core/src/telemetry/anonymous-id.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { normalizeGitUrl } from './anonymous-id'; +import { normalizeGitUrl, unhashedProjectId } from './anonymous-id'; describe('normalizeGitUrl', () => { it('trims off https://', () => { @@ -69,6 +69,12 @@ describe('normalizeGitUrl', () => { ); }); + it('adds .git if missing', () => { + expect(normalizeGitUrl('https://github.com/storybookjs/storybook')).toEqual( + 'github.com/storybookjs/storybook.git' + ); + }); + it('trims off #hash', () => { expect(normalizeGitUrl('https://github.com/storybookjs/storybook.git#next')).toEqual( 'github.com/storybookjs/storybook.git' @@ -85,3 +91,17 @@ describe('normalizeGitUrl', () => { ); }); }); + +describe('unhashedProjectId', () => { + it('does not touch unix paths', () => { + expect( + unhashedProjectId('https://github.com/storybookjs/storybook.git\n', 'path/to/storybook') + ).toBe('github.com/storybookjs/storybook.gitpath/to/storybook'); + }); + + it('normalizes windows paths', () => { + expect( + unhashedProjectId('https://github.com/storybookjs/storybook.git\n', 'path\\to\\storybook') + ).toBe('github.com/storybookjs/storybook.gitpath/to/storybook'); + }); +}); diff --git a/code/core/src/telemetry/anonymous-id.ts b/code/core/src/telemetry/anonymous-id.ts index c97071c0d127..1d23d0a493ae 100644 --- a/code/core/src/telemetry/anonymous-id.ts +++ b/code/core/src/telemetry/anonymous-id.ts @@ -3,6 +3,7 @@ import { relative } from 'node:path'; import { getProjectRoot } from '@storybook/core/common'; import { execSync } from 'child_process'; +import slash from 'slash'; import { oneWayHash } from './one-way-hash'; @@ -16,7 +17,18 @@ export function normalizeGitUrl(rawUrl: string) { // Now strip off scheme const urlWithoutScheme = urlWithoutUser.replace(/^.*\/\//, ''); - return urlWithoutScheme.replace(':', '/'); + // Ensure the URL ends in `.git` + const urlWithExtension = urlWithoutScheme.endsWith('.git') + ? urlWithoutScheme + : `${urlWithoutScheme}.git`; + + return urlWithExtension.replace(':', '/'); +} + +// we use a combination of remoteUrl and working directory +// to separate multiple storybooks from the same project (e.g. monorepo) +export function unhashedProjectId(remoteUrl: string, projectRootPath: string) { + return `${normalizeGitUrl(remoteUrl)}${slash(projectRootPath)}`; } let anonymousProjectId: string; @@ -25,7 +37,6 @@ export const getAnonymousProjectId = () => { return anonymousProjectId; } - let unhashedProjectId; try { const projectRoot = getProjectRoot(); @@ -36,11 +47,7 @@ export const getAnonymousProjectId = () => { stdio: `pipe`, }); - // we use a combination of remoteUrl and working directory - // to separate multiple storybooks from the same project (e.g. monorepo) - unhashedProjectId = `${normalizeGitUrl(String(originBuffer))}${projectRootPath}`; - - anonymousProjectId = oneWayHash(unhashedProjectId); + anonymousProjectId = oneWayHash(unhashedProjectId(String(originBuffer), projectRootPath)); } catch (_) { // }