From 9a44b835126c51a3ce199325479c0cdd22013be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rge=20N=C3=A6ss?= Date: Mon, 14 Aug 2023 13:16:44 +0200 Subject: [PATCH] chore(docs): build up a list of mutations for docs information, keep version info --- package.json | 2 + scripts/docReport.ts | 232 +++++++++++++++++++++++++++++++++++-------- scripts/types.ts | 2 +- yarn.lock | 18 ++++ 4 files changed, 212 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index a9e3331e7d3..5fc8e68b0e1 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "@babel/preset-env": "^7.19.1", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", + "@bjoerge/mutiny": "^0.0.1", "@optimize-lodash/rollup-plugin": "^4.0.1", "@playwright/test": "^1.37.0", "@sanity/client": "^6.4.9", @@ -132,6 +133,7 @@ "prettier-plugin-packagejson": "^2.4.5", "pretty-quick": "^3.1.3", "rimraf": "^3.0.2", + "rxjs": "^7.8.1", "semver": "^7.3.5", "turbo": "^1.10.12", "typescript": "^5.1.6", diff --git a/scripts/docReport.ts b/scripts/docReport.ts index 44a597b458a..eabeab0f874 100644 --- a/scripts/docReport.ts +++ b/scripts/docReport.ts @@ -1,27 +1,108 @@ import path from 'path' import fs from 'fs' -import {cwd} from 'process' -import ts from 'typescript' +import ts, {JSDoc, JSDocComment, SyntaxKind} from 'typescript' +import {createClient} from '@sanity/client' +import { + at, + createIfNotExists, + type Mutation, + patch, + SanityEncoder, + set, + setIfMissing, + upsert, +} from '@bjoerge/mutiny' +import {deburr} from 'lodash' +import {filter, map, mergeMap, of, tap} from 'rxjs' +import readPackages from './utils/readPackages' +import {PackageManifest} from './types' +import {readEnv} from 'sanity-perf-tests/config/envVars' const ALLOWED_TAGS = ['public', 'alpha', 'beta', 'internal', 'experimental', 'deprecated'] - +interface Package { + path: string + dirname: string + manifest: PackageManifest +} function getTags(node: ts.Node) { const tags = ts.getJSDocTags(node).map((tag) => tag.tagName.getText()) return tags.filter((tag) => ALLOWED_TAGS.includes(tag)) } -function getDocReport(exportPath: string) { +export function sanityIdify(input: string) { + return deburr(input) + .replace(/[^a-zA-Z0-9_-]+/g, '_') + .replace(/^-/, '') +} + +type SyntaxType = 'function' | 'class' | 'interface' | 'variable' | 'typeAlias' | 'enum' | 'module' + +function getComment(comment: string | ts.NodeArray) { + // eslint-disable-next-line no-nested-ternary + return typeof comment === 'string' ? [comment] : comment.flatMap((c) => c.text) +} + +function getCommentFromJSDoc(t: ts.JSDoc) { + return t.comment ? getComment(t.comment) : [] +} + +function getName(node: ts.Node) { + if (node.kind === ts.SyntaxKind.VariableStatement) { + return (node as ts.VariableStatement).declarationList.declarations[0].name.getText() + } + return ( + node as ts.FunctionDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration + ).name?.getText() +} + +function getNodeType(node: ts.Node): SyntaxType { + if (node.kind === ts.SyntaxKind.VariableStatement) { + return 'variable' + } + if (node.kind === ts.SyntaxKind.InterfaceDeclaration) { + return 'interface' + } + if (node.kind === ts.SyntaxKind.FunctionDeclaration) { + return 'function' + } + if (node.kind === ts.SyntaxKind.ClassDeclaration) { + return 'class' + } + if (node.kind === ts.SyntaxKind.TypeAliasDeclaration) { + return 'typeAlias' + } + if (node.kind === ts.SyntaxKind.EnumDeclaration) { + return 'enum' + } + if (node.kind === ts.SyntaxKind.ModuleDeclaration) { + return 'module' + } + throw new Error(`Unsupported syntax kind for node: ${ts.SyntaxKind[node.kind]}`) +} + +type ExportedSymbol = { + name: string + type: SyntaxType + tags: string[] + comment?: string | undefined +} + +type ResolvedExport = { + exportName: string + normalized: string + dtsExport: string + dtsFilePath: string +} +function getExportedSymbols(exp: ResolvedExport): ExportedSymbol[] { + const exportedSymbols: ExportedSymbol[] = [] // Read the .d.ts file const sourceFile = ts.createSourceFile( - exportPath, - fs.readFileSync(path.resolve(cwd(), `packages/sanity/lib/exports/${exportPath}`)).toString(), + exp.dtsExport!, + fs.readFileSync(exp.dtsFilePath!).toString('utf-8'), // from tsconfig.settings.json ts.ScriptTarget.ES2017, - true + true, ) - - const exportNames: {name?: string; tags: string[]}[] = [] - sourceFile.forEachChild((node) => { // Get all the export items that are named or default export const exportedItem = ts @@ -29,45 +110,114 @@ function getDocReport(exportPath: string) { ?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) if (exportedItem) { - let name - - if (node.kind === ts.SyntaxKind.VariableStatement) { - name = (node as ts.VariableStatement).declarationList.declarations[0].name.getText() - } else { - name = ( - node as ts.FunctionDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration - ).name?.getText() - } + const comment = ts + .getJSDocCommentsAndTags(node) + .filter((t): t is JSDoc => t.kind === SyntaxKind.JSDoc) + .flatMap((t) => getCommentFromJSDoc(t)) + const name = getName(node) + if (!name) { + return + } const tags = getTags(node) + const type = getNodeType(node) - exportNames.push({name, tags}) + exportedSymbols.push({ + name, + type, + comment: comment.length > 0 ? comment.join('\n') : undefined, + tags, + }) } }) - - return exportNames + return exportedSymbols } -// todo: make it work for different packages -fs.readdir(path.resolve(cwd(), 'packages/sanity/lib/exports'), (err, files) => { - if (err) { - console.error(err) - return - } - - const result = files - .map((file) => { - if (file.endsWith('.d.ts')) { - return { - packageName: `sanity${file === 'index.d.ts' ? '' : `/${file.replace('.d.ts', '')}`}`, - properties: getDocReport(file), +function getResolvedExports(pkg: Package): ResolvedExport[] { + const manifest = pkg.manifest + return Object.entries(manifest.exports || {}).flatMap(([exportName, exportDefinition]) => { + return exportDefinition.types + ? { + exportName, + normalized: path.join(manifest.name, exportName), + dtsExport: exportDefinition.types, + dtsFilePath: exportDefinition.types && path.join(pkg.dirname, exportDefinition.types), } - } + : [] + }) +} - return undefined - }) - .filter(Boolean) +function getPackageMutations(pkg: Package): Mutation[] { + const exports = getResolvedExports(pkg) + return exports.flatMap((exp) => { + const exportsDocId = `package-exports-${sanityIdify(exp.normalized)}` + const symbols = getExportedSymbols(exp) + return [ + createIfNotExists({ + _id: exportsDocId, + _type: 'packageExports', + }), + patch(exportsDocId, [ + at('name', set(exp.exportName)), + at('normalized', set(exp.normalized)), + at('package', set(pkg.manifest.name)), + ]), + ...symbols.flatMap((symbol) => { + const symbolDocumentId = `symbol-${sanityIdify(symbol.name)}` + return [ + createIfNotExists({ + _id: symbolDocumentId, + _type: 'exportSymbol', + }), + patch(symbolDocumentId, [ + at('exportedBy', setIfMissing({_type: 'ref', _ref: exportsDocId})), + ]), + patch(symbolDocumentId, [ + at('name', set(symbol.name)), + at('versions', setIfMissing([])), + at( + 'versions', + upsert( + [ + { + _key: pkg.manifest.version, + version: pkg.manifest.version, + type: symbol.type, + comment: symbol.comment, + tags: symbol.tags, + updatedAt: new Date().toISOString(), + }, + ], + 'before', + 0, + ), + ), + ]), + ] + }), + ] + }) +} - // TODO: Store this in studio - console.log(result) +const studioMetricsClient = createClient({ + projectId: 'c1zuxvqn', + dataset: 'production', + token: readEnv('PERF_TEST_METRICS_TOKEN'), + apiVersion: '2023-02-03', + useCdn: false, }) + +of(readPackages()) + .pipe( + tap((packages) => console.log(`Updating docs for ${packages.length} packages`)), + mergeMap((packages) => packages), + map((pkg) => { + return {pkg, mutations: getPackageMutations(pkg)} + }), + filter(({mutations}) => mutations.length > 0), + mergeMap(({pkg, mutations}) => { + console.log(`Submitting ${mutations.length} mutations for ${pkg.manifest.name}`) + return studioMetricsClient.observable.transaction(SanityEncoder.encode(mutations)).commit() + }, 2), + ) + .subscribe() diff --git a/scripts/types.ts b/scripts/types.ts index 58b0a1ea8d2..c38d08b4b82 100644 --- a/scripts/types.ts +++ b/scripts/types.ts @@ -2,7 +2,7 @@ export interface PackageManifest { name: string private?: boolean version: string - exports?: Record> + exports?: Record> dependencies?: Record devDependencies?: Record engines?: Record diff --git a/yarn.lock b/yarn.lock index f1e3e9afb1a..cc62202b7fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1187,6 +1187,14 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@bjoerge/mutiny@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@bjoerge/mutiny/-/mutiny-0.0.1.tgz#02e050da524bf730a04f1287ed812d6e9ac2a4ec" + integrity sha512-xL8qsQovS1McDa+pDIJLZSo+sAyRiC5oe/k+T569oK97ka8Weu6XrzR5ypCtMN20TIduw9VV00nc3hv3E9gQmg== + dependencies: + diff-match-patch "^1.0.5" + hotscript "^1.0.13" + "@chevrotain/cst-dts-gen@10.5.0": version "10.5.0" resolved "https://registry.yarnpkg.com/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz#922ebd8cc59d97241bb01b1b17561a5c1ae0124e" @@ -8239,6 +8247,11 @@ didyoumean@^1.2.2: resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== +diff-match-patch@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37" + integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw== + diff-sequences@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" @@ -10643,6 +10656,11 @@ hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: dependencies: lru-cache "^6.0.0" +hotscript@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/hotscript/-/hotscript-1.0.13.tgz#6eb5de757e9b33444ffc22555e98dbc17fa31fb4" + integrity sha512-C++tTF1GqkGYecL+2S1wJTfoH6APGAsbb7PAWQ3iVIwgG/EFseAfEVOKFgAFq4yK3+6j1EjUD4UQ9dRJHX/sSQ== + hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"