From fa873538d72090821570084fce6590ce03d3956b Mon Sep 17 00:00:00 2001 From: Greg Pabian <35925521+grzpab@users.noreply.github.com> Date: Fri, 22 Dec 2023 14:56:06 +0100 Subject: [PATCH] fix(parser): parser bugfixing --- readme-parser/parse.test.ts | 83 +++++++++++++++++----------- readme-parser/parse.ts | 106 +++++++++++++++++++++++------------- readme-parser/sync.ts | 50 +++++++++++------ 3 files changed, 154 insertions(+), 85 deletions(-) diff --git a/readme-parser/parse.test.ts b/readme-parser/parse.test.ts index 197bb142..1513c69a 100644 --- a/readme-parser/parse.test.ts +++ b/readme-parser/parse.test.ts @@ -8,7 +8,7 @@ const DATA = ` ## Description -This is an amazing codemod +This is an amazing codemod which does \`the thing\` ### WARNING @@ -40,6 +40,8 @@ http.get('/resource', (req, res, ctx) => { }); \`\`\` +### **engine.tsx** + ### Before \`\`\`ts @@ -81,7 +83,7 @@ Maybe more... ### Owner -[Intuita](https://github.com/intuita-inc) +[The Author](https://github.com/author) ### Links for more info @@ -96,44 +98,45 @@ describe('parse/yaml', function () { deepEqual(parseResult, { name: 'Do the thing', description: - 'This is an amazing codemod\n### WARNING\nThis codemod does the thing\n' + + 'This is an amazing codemod which does `the thing`\n\n### WARNING\n\nThis codemod does the thing\n' + 'Following the original msw [upgrade guide](https://mswjs.io/docs/migrations/1.x-to-2.x/#imports), ' + 'there are certain imports that changed their location and/or naming. This codemod will adjust your imports to the new location and naming.\n' + - '- `setupWorker` is now imported from `msw/browser`\n' + - '- `rest` from `msw` is now named `http`\n' + - '- `RestHandler` from `msw` is now named `HttpHandler`', + ' - `setupWorker` is now imported from `msw/browser`\n' + + ' - `rest` from `msw` is now named `http`\n' + + ' - `RestHandler` from `msw` is now named `HttpHandler`', examples: - '### tsconfig.json\n' + + '\n### `tsconfig.json`\n\n' + '### Before\n\n' + - '```ts\n' + + '```ts\n\n' + "http.get('/resource', (req, res, ctx) => {\n" + " return res(ctx.json({ firstName: 'John' }));\n" + - '});\n' + + '});\n\n' + '```\n\n' + '### After\n\n' + - '```ts\n' + + '```ts\n\n' + "http.get('/resource', (req, res, ctx) => {\n" + " return res(ctx.json({ firstName: 'John' }));\n" + - '});\n' + + '});\n\n' + '```\n\n' + + '### **engine.tsx**\n\n' + '### Before\n\n' + - '```ts\n' + + '```ts\n\n' + "http.get('/resource', (req, res, ctx) => {\n" + " return res(ctx.json({ firstName: 'John' }));\n" + - '});\n' + + '});\n\n' + '```\n\n' + '### After\n\n' + - '```ts\n' + + '```ts\n\n' + "http.get('/resource', (req, res, ctx) => {\n" + " return res(ctx.json({ firstName: 'John' }));\n" + - '});\n' + - '```\n', - applicability: 'MSW >= 1.0.0', + '});\n\n' + + '```', + applicability: '`MSW` >= 1.0.0', version: '1.0.0', changeMode: 'assistive', engine: 'ts-morph', timeSave: '5 minutes/occurrence\nMaybe more...', - owner: 'Intuita', + owner: 'The Author', links: 'https://example.com/,https://example1.com/', }); }); @@ -166,60 +169,73 @@ describe('parse/yaml', function () { created-on: ${date.toISOString()} f_long-description: >- ## Description - \n - This is an amazing codemod + + + This is an amazing codemod which does \`the thing\` + ### WARNING + This codemod does the thing Following the original msw [upgrade guide](https://mswjs.io/docs/migrations/1.x-to-2.x/#imports), there are certain imports that changed their location and/or naming. This codemod will adjust your imports to the new location and naming. - - \`setupWorker\` is now imported from \`msw/browser\` - - \`rest\` from \`msw\` is now named \`http\` - - \`RestHandler\` from \`msw\` is now named \`HttpHandler\` + - \`setupWorker\` is now imported from \`msw/browser\` + - \`rest\` from \`msw\` is now named \`http\` + - \`RestHandler\` from \`msw\` is now named \`HttpHandler\` - ### tsconfig.json + + ### \`tsconfig.json\` + ### Before \`\`\`ts + http.get('/resource', (req, res, ctx) => { return res(ctx.json({ firstName: 'John' })); }); + \`\`\` ### After \`\`\`ts + http.get('/resource', (req, res, ctx) => { return res(ctx.json({ firstName: 'John' })); }); + \`\`\` + ### **engine.tsx** + ### Before \`\`\`ts + http.get('/resource', (req, res, ctx) => { return res(ctx.json({ firstName: 'John' })); }); + \`\`\` ### After \`\`\`ts + http.get('/resource', (req, res, ctx) => { return res(ctx.json({ firstName: 'John' })); }); - \`\`\` + \`\`\` f_github-link: https://github.com/intuita-inc/codemod-registry/tree/main/codemods/msw/2/imports f_vs-code-link: vscode://intuita.intuita-vscode-extension/showCodemod?chd=${vscodeHashDigest} -f_codemod-studio-link: n/a f_cli-command: intuita msw/2/imports f_framework: cms/framework/msw.md -f_applicability-criteria: MSW >= 1.0.0 -f_verified-codemod: true -f_author: cms/authors/intuita.md +f_applicability-criteria: "\`MSW\` >= 1.0.0" +f_verified-codemod: false +f_author: cms/authors/the-author.md layout: "[automations].html" slug: msw-2-imports -title: Do the thing +title: Msw V2 - Do the thing f_slug-name: msw-2-imports f_codemod-engine: cms/codemod-engines/ts-morph.md f_change-mode-2: Assistive @@ -229,7 +245,12 @@ f_estimated-time-saving: >- tags: automations updated-on: ${date.toISOString()} published-on: ${date.toISOString()} -seo: n/a +seo: + title: Msw V2 - Do the thing | Intuita Automations + og:title: Msw V2 - Do the thing | Intuita Automations + twitter:title: Msw V2 - Do the thing | Intuita Automations + description: This is an amazing codemod which does \`the thing\` + twitter:card: This is an amazing codemod which does \`the thing\` `.trim(), ); }); diff --git a/readme-parser/parse.ts b/readme-parser/parse.ts index 4d2c0917..2f1ae41c 100644 --- a/readme-parser/parse.ts +++ b/readme-parser/parse.ts @@ -21,15 +21,22 @@ const noFirstLetterLowerCase = (str: string) => const capitalize = (str: string) => str[0] ? str[0].toUpperCase() + str.slice(1) : str; +// TODO: +// const getStyledValue = (node) => {}; + const getTextFromNode = ( node: RootContent | PhrasingContent | null, - style?: boolean, + style = false, ): string | null => { if (!node) { return null; } if ('value' in node) { + if (node.type === 'inlineCode') { + return `\`${node.value}\``; + } + return node.value; } @@ -37,16 +44,14 @@ const getTextFromNode = ( let textContent = ''; for (const child of node.children) { if (!style) { - textContent += getTextFromNode(child); + textContent += getTextFromNode(child, style); continue; } - if (child.type === 'listItem') { - textContent += `${getTextFromNode(child, style)}`; - } else if (child.type === 'inlineCode') { - textContent += `\`${getTextFromNode(child, style)}\``; + if (node.type === 'strong') { + textContent += `**${getTextFromNode(child, style)}**`; } else { - textContent += getTextFromNode(child); + textContent += getTextFromNode(child, style); } } @@ -143,11 +148,23 @@ const getTextByHeader = ( rc.type === 'heading' && rc.depth > heading.depth && idx === 0 && - (child.type === 'text' || child.type === 'inlineCode') + (child.type === 'text' || + child.type === 'inlineCode' || + child.type === 'strong') ) { - return `${'#'.repeat(rc.depth)} ${ - child.value - }${delimiter}`; + const conditionalDelimiter = delimiter.repeat( + isDescription ? 2 : 1, + ); + return `${conditionalDelimiter}${'#'.repeat( + rc.depth, + )} ${getTextFromNode( + child, + true, + )}${conditionalDelimiter}`; + } + + if (child.type === 'inlineCode') { + return `\`${child.value}\``; } if (child.type === 'text') { @@ -161,7 +178,7 @@ const getTextByHeader = ( if (child.type === 'listItem') { if (isDescription) { - return `- ${getTextFromNode( + return ` - ${getTextFromNode( child.children[0] ?? null, true, )}${delimiter}`; @@ -212,9 +229,7 @@ const getTextByHeader = ( if ('value' in rc) { if (rc.type === 'code') { - textParts.push( - `\n\`\`\`${rc.lang}\n${rc.value}\n\`\`\`${delimiter}\n`, - ); + textParts.push(`\n\`\`\`${rc.lang}\n\n${rc.value}\n\n\`\`\`\n`); } else { textParts.push(`${rc.value}${delimiter}`); } @@ -223,7 +238,7 @@ const getTextByHeader = ( // Trim last el to remove delimiter textParts[textParts.length - 1] = - textParts.at(-1)?.replace(/(\W)$/, '') ?? ''; + textParts.at(-1)?.replace(new RegExp(`${delimiter}$`), '') ?? ''; return textParts.join(''); }; @@ -363,11 +378,13 @@ export const convertToYaml = ( let slug: string | null = null; let framework: string | null = null; + let frameworkVersion: string | null = null; let cliCommand: string | null = null; let cleanPath: string | null = null; let codemodName: string | null = null; if (path) { - cleanPath = path.split('/').slice(0, -1).join('/'); + const splitPath = path.split('/'); + cleanPath = splitPath.slice(0, -1).join('/'); const parts = __dirname.split('/'); const pivot = parts.indexOf('readme-parser'); @@ -376,7 +393,8 @@ export const convertToYaml = ( cleanPath, ); - framework = path.split('/').at(1) ?? null; + framework = splitPath.at(1) ?? null; + frameworkVersion = splitPath.at(2) ?? null; try { const config = readFileSync( @@ -401,6 +419,18 @@ export const convertToYaml = ( .digest('base64url'); } + let titleWithVersion = title; + if (framework) { + if (frameworkVersion) { + titleWithVersion = `${framework} V${frameworkVersion} - ${title}`; + } else { + titleWithVersion = `${framework} - ${title}`; + } + } + titleWithVersion = capitalize(titleWithVersion); + + const shortDescription = description.split('\n').at(0); + const res = ` created-on: ${new Date().toISOString()} f_long-description: >- @@ -408,42 +438,42 @@ f_long-description: >- \n ${description.replace(/\n/g, '\n ')} \n - ${examples.replace(/\n/g, '\n ')} -f_github-link: ${ + ${examples.replace(/\n/g, '\n ')}${ path - ? `https://github.com/intuita-inc/codemod-registry/tree/main/${cleanPath}` - : 'n/a' - } -f_vs-code-link: ${ + ? `\nf_github-link: https://github.com/intuita-inc/codemod-registry/tree/main/${cleanPath}` + : '' + }${ vscodeHashDigest - ? `vscode://intuita.intuita-vscode-extension/showCodemod?chd=${vscodeHashDigest}` - : 'n/a' - } -f_codemod-studio-link: n/a -f_cli-command: ${cliCommand ?? 'n/a'} -f_framework: ${framework ? `cms/framework/${framework}.md` : 'n/a'} -f_applicability-criteria: ${applicability} + ? `\nf_vs-code-link: vscode://intuita.intuita-vscode-extension/showCodemod?chd=${vscodeHashDigest}` + : '' + }${cliCommand ? `\nf_cli-command: ${cliCommand}` : ''}${ + framework ? `\nf_framework: cms/framework/${framework}.md` : '' + } +f_applicability-criteria: "${applicability}" f_verified-codemod: ${owner === 'Intuita' ? 'true' : 'false'} f_author: ${ owner === 'Intuita' ? 'cms/authors/intuita.md' - : `cms/authors/${codemodName?.split('/')?.[0] ?? ''}.md` + : `cms/authors/${owner?.toLowerCase().replace(/ /g, '-') ?? ''}.md` } -layout: "[automations].html" -slug: ${slug ?? 'n/a'} -title: ${title} -f_slug-name: ${slug ?? 'n/a'} +layout: "[automations].html"${slug ? `\nslug: ${slug}` : ''} +title: ${capitalize(titleWithVersion)}${slug ? `\nf_slug-name: ${slug}` : ''} f_codemod-engine: cms/codemod-engines/${engine}.md f_change-mode-2: ${capitalize(changeMode)} f_estimated-time-saving: ${ timeSave.includes('\n') ? `>-\n ${timeSave.replace(/\n/, '\n ')}` - : timeSave + : `"${timeSave}"` } tags: automations updated-on: ${new Date().toISOString()} published-on: ${new Date().toISOString()} -seo: n/a +seo: + title: ${titleWithVersion} | Intuita Automations + og:title: ${titleWithVersion} | Intuita Automations + twitter:title: ${titleWithVersion} | Intuita Automations + description: ${shortDescription} + twitter:card: ${shortDescription} `.trim(); return res; diff --git a/readme-parser/sync.ts b/readme-parser/sync.ts index 5585423a..0bcdcf59 100644 --- a/readme-parser/sync.ts +++ b/readme-parser/sync.ts @@ -14,12 +14,12 @@ const findKeyLineRange = (yaml: string, key: string) => { let startFound = false; for (const [index, line] of splitYaml.entries()) { - if (startFound && line.match(/^\w+:\s/)) { + if (startFound && new RegExp(`^[A-Za-z0-9_-]+:\\s`).test(line)) { fieldEndLine = index; break; } - if (line.match(`^${key}:\\s`)) { + if (new RegExp(`^${key}:\\s`).test(line)) { fieldStartLine = index; startFound = true; } @@ -47,9 +47,9 @@ export const sync = async () => { await git.addConfig('user.name', 'Intuita Team', false, 'local'); await git.fetch(['website', 'master']); - await git.fetch(['origin', 'main']); + await git.fetch(['origin', 'main', '--depth=2']); - const diff = await git.diff(['--name-only', 'origin/main']); + const diff = await git.diff(['--name-only', 'origin/main~1']); const readmesChanged = diff .split('\n') .filter((path) => path.match(/^codemods\/.*README\.md$/)); @@ -67,6 +67,7 @@ export const sync = async () => { let websiteFile: string | null; let oldFile: string | null; + let newFile: string | null; try { websiteFile = await git.catFile([ '-p', @@ -77,14 +78,27 @@ export const sync = async () => { } try { - oldFile = await git.catFile(['-p', `origin/main:${path}`]); + oldFile = await git.catFile(['-p', `origin/main~1:${path}`]); } catch (err) { oldFile = null; } - // Always exists - const newFile = await git.catFile(['-p', `HEAD:${path}`]); - const newReadmeYamlContent = convertToYaml(parse(newFile), path); + try { + newFile = await git.catFile(['-p', `origin/main:${path}`]); + } catch (err) { + newFile = null; + } + + if (!newFile) { + console.error(`File was deleted in HEAD: ${path}`); + continue; + } + + const parsedNewFile = parse(newFile); + const newFileShortDescription = parsedNewFile.description + .split('\n') + .at(0); + const newReadmeYamlContent = convertToYaml(parsedNewFile, path); // If !websiteFile, we just add the file // If websiteFile is present, but oldFile is not, this means that @@ -92,7 +106,8 @@ export const sync = async () => { // which technically should not be possible. // In that case we just update the entire file with the new one anyways. if (!websiteFile || !oldFile) { - staged[websitePath] = `---\n${newReadmeYamlContent}\n---`; + staged[websitePath] = + `---\n${newReadmeYamlContent}\n---\n${newFileShortDescription}`; continue; } @@ -156,7 +171,12 @@ export const sync = async () => { continue; } + // Also update the updated-on field + changedKeys.push('updated-on'); + // Yaml to be updated on each iteration serving as a target to make replacements in let updatedYaml = websiteYamlContent; + const newFileLines = newReadmeYamlContent.split('\n'); + for (const key of changedKeys) { const websiteRange = findKeyLineRange(updatedYaml, key); if (!websiteRange) { @@ -175,22 +195,20 @@ export const sync = async () => { const [websiteStartIndex, websiteEndIndex] = websiteRange; const [newFileStartIndex, newFileEndIndex] = newFileRange; - const websiteLines = websiteYamlContent.split('\n'); - const newFileLines = newReadmeYamlContent.split('\n'); + // Use the latest version of yaml that's being updated + const websiteLines = updatedYaml.split('\n'); updatedYaml = [ - '---', ...websiteLines.slice(0, websiteStartIndex), ...newFileLines.slice(newFileStartIndex, newFileEndIndex), ...websiteLines.slice(websiteEndIndex), - '---', ].join('\n'); } const websiteLeftoverDescription = websiteContentSplit.at(2)?.trim(); - if (websiteLeftoverDescription) { - updatedYaml += `\n${websiteLeftoverDescription}`; - } + updatedYaml = `---\n${updatedYaml}\n---\n${ + websiteLeftoverDescription ?? newFileShortDescription + }`; staged[websitePath] = updatedYaml; }