Compare commits

...

1 commit

Author SHA1 Message Date
Fred K. Schott
a3ea759c6c telemetry audit 2023-09-15 15:44:21 -07:00

View file

@ -2,24 +2,6 @@ import type { AstroUserConfig } from '../@types/astro.js';
const EVENT_SESSION = 'ASTRO_CLI_SESSION_STARTED'; const EVENT_SESSION = 'ASTRO_CLI_SESSION_STARTED';
interface ConfigInfo {
markdownPlugins: string[];
adapter: string | null;
integrations: string[];
trailingSlash: undefined | 'always' | 'never' | 'ignore';
build:
| undefined
| {
format: undefined | 'file' | 'directory';
};
markdown:
| undefined
| {
drafts: undefined | boolean;
syntaxHighlight: undefined | 'shiki' | 'prism' | false;
};
}
interface EventPayload { interface EventPayload {
cliCommand: string; cliCommand: string;
config?: ConfigInfo; config?: ConfigInfo;
@ -28,42 +10,90 @@ interface EventPayload {
optionalIntegrations?: number; optionalIntegrations?: number;
} }
const multiLevelKeys = new Set([ // Utility Types
'build', type ConfigInfoShape<T> = Record<keyof T, string | boolean | string[] | undefined>;
'markdown', type ConfigInfoNested<T> = Record<keyof T, string | boolean | string[] | undefined | object>;
'markdown.shikiConfig', type AssertKeysEqual<X extends ConfigInfoShape<Y>, Y extends Record<keyof X, any>> = never;
'server', type AssertKeysEqualDeep<X extends ConfigInfoNested<Y>, Y extends Record<keyof X, any>> = never;
'vite',
'vite.resolve',
'vite.css',
'vite.json',
'vite.server',
'vite.server.fs',
'vite.build',
'vite.preview',
'vite.optimizeDeps',
'vite.ssr',
'vite.worker',
]);
function configKeys(obj: Record<string, any> | undefined, parentKey: string): string[] {
if (!obj) {
return [];
}
return Object.entries(obj) // Type Assertions!
.map(([key, value]) => { // This will throw if createAnonymousConfigInfo() does not match the AstroUserConfig interface.
if (typeof value === 'object' && !Array.isArray(value)) { // To fix: Analyze the error and update createAnonymousConfigInfo() below.
const localKey = parentKey ? parentKey + '.' + key : key; type ConfigInfo = ReturnType<typeof createAnonymousConfigInfo>;
if (multiLevelKeys.has(localKey)) { // eslint-disable-next-line @typescript-eslint/no-unused-vars
let keys = configKeys(value, localKey).map((subkey) => key + '.' + subkey); type Assertions = [
keys.unshift(key); AssertKeysEqualDeep<ConfigInfo, Required<AstroUserConfig>>,
return keys; AssertKeysEqual<ConfigInfo['build'], Required<NonNullable<AstroUserConfig['build']>>>,
} AssertKeysEqual<ConfigInfo['image'], Required<NonNullable<AstroUserConfig['image']>>>,
} AssertKeysEqual<ConfigInfo['markdown'], Required<NonNullable<AstroUserConfig['markdown']>>>,
AssertKeysEqual<
ConfigInfo['experimental'],
Required<NonNullable<AstroUserConfig['experimental']>>
>,
AssertKeysEqual<ConfigInfo['legacy'], Required<NonNullable<AstroUserConfig['legacy']>>>,
];
return key; /**
}) * This function creates an anonymous "config info" object from the user's config.
.flat(1); * All values are sanitized to preserve anonymity.
* Complex values should be cast to simple booleans/strings where possible.
* `undefined` means that the value is not tracked.
* This verbose implementation helps keep the implemetation up-to-date.
*/
function createAnonymousConfigInfo(userConfig?: AstroUserConfig) {
return {
adapter: userConfig?.adapter?.name ?? undefined,
build: {
format: userConfig?.build?.format,
client: undefined,
server: undefined,
assets: undefined,
assetsPrefix: undefined,
serverEntry: undefined,
redirects: undefined,
inlineStylesheets: undefined,
excludeMiddleware: undefined,
split: undefined,
},
base: undefined,
cacheDir: undefined,
compressHTML: undefined,
image: {
endpoint: undefined,
service: undefined,
domains: undefined,
remotePatterns: undefined,
},
integrations: userConfig?.integrations
?.flat()
.map((i) => i && i.name)
.filter((i) => i && i.startsWith('@astrojs/')) as string[],
markdown: {
drafts: undefined,
shikiConfig: undefined,
syntaxHighlight: userConfig?.markdown?.syntaxHighlight,
remarkPlugins: undefined,
remarkRehype: undefined,
gfm: undefined,
smartypants: undefined,
rehypePlugins: undefined,
},
outDir: undefined,
output: userConfig?.output,
publicDir: undefined,
redirects: userConfig?.redirects,
root: undefined,
scopedStyleStrategy: userConfig?.scopedStyleStrategy,
server: undefined,
site: undefined,
srcDir: undefined,
trailingSlash: userConfig?.trailingSlash,
vite: undefined,
experimental: {
optimizeHoistedScript: userConfig?.experimental?.optimizeHoistedScript,
},
legacy: {},
};
} }
export function eventCliSession( export function eventCliSession(
@ -71,44 +101,12 @@ export function eventCliSession(
userConfig?: AstroUserConfig, userConfig?: AstroUserConfig,
flags?: Record<string, any> flags?: Record<string, any>
): { eventName: string; payload: EventPayload }[] { ): { eventName: string; payload: EventPayload }[] {
// Filter out falsy integrations
const configValues = userConfig
? {
markdownPlugins: [
...(userConfig?.markdown?.remarkPlugins?.map((p) =>
typeof p === 'string' ? p : typeof p
) ?? []),
...(userConfig?.markdown?.rehypePlugins?.map((p) =>
typeof p === 'string' ? p : typeof p
) ?? []),
] as string[],
adapter: userConfig?.adapter?.name ?? null,
integrations: (userConfig?.integrations ?? [])
.filter(Boolean)
.flat()
.map((i: any) => i?.name),
trailingSlash: userConfig?.trailingSlash,
build: userConfig?.build
? {
format: userConfig?.build?.format,
}
: undefined,
markdown: userConfig?.markdown
? {
drafts: userConfig.markdown?.drafts,
syntaxHighlight: userConfig.markdown?.syntaxHighlight,
}
: undefined,
}
: undefined;
// Filter out yargs default `_` flag which is the cli command // Filter out yargs default `_` flag which is the cli command
const cliFlags = flags ? Object.keys(flags).filter((name) => name != '_') : undefined; const cliFlags = flags ? Object.keys(flags).filter((name) => name != '_') : undefined;
const payload: EventPayload = { const payload: EventPayload = {
cliCommand, cliCommand,
configKeys: userConfig ? configKeys(userConfig, '') : undefined, config: createAnonymousConfigInfo(userConfig),
config: configValues,
flags: cliFlags, flags: cliFlags,
}; };
return [{ eventName: EVENT_SESSION, payload }]; return [{ eventName: EVENT_SESSION, payload }];