-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
17ff0b1
commit 150d78f
Showing
1 changed file
with
287 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import { Client, SchemaTypes } from '@datocms/cli/lib/cma-client-node'; | ||
import { render, type StructuredTextDocument } from 'datocms-structured-text-to-plain-text'; | ||
import { isLink, type Link } from 'datocms-structured-text-utils'; | ||
import { isLink, type ItemLink, type NodeWithMeta } from 'datocms-structured-text-utils'; | ||
import { cloneDeep, isEqual, omit } from 'lodash-es'; | ||
import { match } from 'path-to-regexp'; | ||
import { visit } from 'unist-util-visit'; | ||
|
||
|
@@ -17,6 +18,7 @@ const knownPaths = [ | |
'/contact/thanks', | ||
'/customer-stories', | ||
'/customers', | ||
'/changelog', | ||
'/docs/community-tutorials', | ||
'/docs/content-delivery-api/filtering-records', | ||
'/docs/content-delivery-api/filtering-uploads', | ||
|
@@ -385,7 +387,7 @@ const pathToResourceConfig = [ | |
field: 'slug', | ||
}, | ||
{ | ||
pattern: '/marketplace/plugins/i/:packageName', | ||
pattern: '/marketplace/plugins/i/*packageName', | ||
urlVariable: 'packageName', | ||
model: 'plugin', | ||
field: 'package_name', | ||
|
@@ -434,19 +436,240 @@ const pathToResourceConfig = [ | |
}, | ||
]; | ||
|
||
const redirects = Object.entries({ | ||
'/marketplace/plugins/i/datocms-plugin-yoast-seo': | ||
'/marketplace/plugins/i/datocms-plugin-seo-readability-analysis', | ||
'/docs/introduction/custom-assets-domain': '/marketplace/enterprise/aws-s3', | ||
'/search': '/docs/site-search', | ||
'/docs/search': '/docs/site-search', | ||
'/about': '/company/about', | ||
'/brand-assets': '/company/brand-assets', | ||
'/docs/building-plugins/*splat': '/docs/legacy-plugins/*splat', | ||
'/docs/guides/custom-assets-domain/*splat': '/marketplace/enterprise/*splat', | ||
'/docs/guides/single-sign-on': '/marketplace/enterprise', | ||
'/docs/guides/single-sign-on/configure-sso-with-okta': '/marketplace/enterprise/okta-sso', | ||
'/docs/guides/single-sign-on/configure-sso-with-onelogin': '/marketplace/enterprise/onelogin-sso', | ||
'/docs/deployments/travis': '/marketplace/hosting/travis-ci', | ||
'/docs/deployments/*splat': '/marketplace/hosting/*splat', | ||
'/plugins/i/@stackbitdatocms-plugin-typed-list': | ||
'/marketplace/plugins/i/@stackbit/datocms-plugin-typed-list', | ||
'/plugins/i/@ecologicdatocms-plugin-multiselect': | ||
'/marketplace/plugins/i/@ecologic/datocms-plugin-multiselect', | ||
'/plugins/i/@ecologicdatocms-plugin-ordered-tag-editor': | ||
'/marketplace/plugins/i/@ecologic/datocms-plugin-ordered-tag-editor', | ||
'/content-management-api': '/docs/content-management-api', | ||
'/plugins': '/marketplace/plugins', | ||
'/plugins/i/*splat': '/marketplace/plugins/i/*splat', | ||
'/docs/plugins/entry-point': '/docs/building-plugins/entry-point', | ||
'/docs/plugins/creating-a-new-plugin': '/docs/building-plugins/creating-a-new-plugin', | ||
'/docs/plugins/publishing': '/docs/building-plugins/publishing', | ||
'/docs/guides/offline-backups': '/docs/import-and-export/export-data', | ||
'/docs/general-concepts/transfer': '/docs/plans-pricing-and-billing/transfer', | ||
'/docs/content-management-api/js-client': '/docs/content-management-api/using-the-nodejs-clients', | ||
'/docs/security': '/security', | ||
'/docs/guides/private-videos': '/docs/content-modelling/external-video-field', | ||
'/docs/content-delivery-api/overview': '/docs/content-delivery-api', | ||
'/docs/content-delivery-api/endpoint': '/docs/content-delivery-api/api-endpoints', | ||
'/docs/content-delivery-api/complexity_limiting': '/docs/content-delivery-api/complexity', | ||
'/docs/content-delivery-api/first-request': '/docs/content-delivery-api/your-first-request', | ||
'/docs/content-delivery-api/querying': '/docs/content-delivery-api/how-to-fetch-records', | ||
'/docs/content-delivery-api/filtering': '/docs/content-delivery-api/filtering-records', | ||
'/docs/content-delivery-api/ordering': '/docs/content-delivery-api/ordering-records', | ||
'/docs/content-delivery-api/modular-content': '/docs/content-delivery-api/modular-content-fields', | ||
'/docs/content-delivery-api/uploads': '/docs/content-delivery-api/images-and-videos', | ||
'/docs/content-delivery-api/seo': '/docs/content-delivery-api/seo-and-favicon', | ||
'/blog/2': '/blog/p/2', | ||
'/changelog/2': '/product-updates/p/2', | ||
'/changelog/3': '/product-updates/p/3', | ||
'/changelog/4': '/product-updates/p/4', | ||
'/changelog/5': '/product-updates/p/5', | ||
'/changelog/6': '/product-updates/p/5', | ||
'/changelog/*splat': '/product-updates/*splat', | ||
'/docs/general-concepts/pricing': '/docs/plans-pricing-and-billing', | ||
'/docs/guides/installing-site-search/*splat': '/docs/site-search/*splat', | ||
'/docs/guides/*splat': '/docs/*splat', | ||
'/docs/static-generators/gatsbyjs/*splat': '/docs/gatsby/*splat', | ||
'/docs/static-generators/*splat': '/docs/*splat', | ||
'/docs/single-page-apps/*splat': '/docs/*splat', | ||
'/marketplace/hosting/zeit': '/marketplace/hosting/vercel', | ||
'/cms/react': '/cms/react-cms', | ||
'/cms/hugo': '/cms/hugo-cms', | ||
'/cms/nextjs': '/cms/nextjs-cms', | ||
'/cms/nuxt-js': '/cms/nuxtjs-cms', | ||
'/cms/jekyll': '/cms/jekyll-cms', | ||
'/cms/middleman': '/cms/middleman-cms', | ||
'/cms/vue': '/cms/vue-js-cms', | ||
'/cms/gatsbyjs': '/cms/gatsbyjs-cms', | ||
'/features/video-streaming-encoding': '/features/video-api', | ||
'/features/graphql-content-api': '/features/headless-cms-graphql', | ||
'/features/multi-language': '/features/headless-cms-multi-language', | ||
'/features/workflows': '/features/workflow-cms', | ||
'/features/real-time': '/features/real-time-api', | ||
'/features/structured-text': '/features/structured-content-cms', | ||
'/team/digital-marketers': '/team/cms-digital-marketing', | ||
'/blog/what-is-an-headless-cms': '/blog/what-is-a-headless-cms', | ||
'/team/developers': '/team/best-cms-for-developers', | ||
'/enterprise': '/enterprise-headless-cms', | ||
'/marketplace/starters/nextjs-blog': '/marketplace/starters/nextjs-template-blog', | ||
'/blog/offer-responsive-progressive-lqip-images-in-2020': | ||
'/blog/best-way-for-handling-react-images', | ||
'/blog/static-ecommerce-website-snipcart-gatsbyjs-datocms': '/blog/gatsby-ecommerce-tutorial', | ||
'/cms/gatsbyjs-cms': '/cms/gatsby-cms', | ||
'/legal/security': '/security', | ||
'/legal/privacy': '/legal/privacy-policy', | ||
'/docs/pro-tips/manage-draft-published-state-by-locale': | ||
'/docs/general-concepts/localization#locale-based-publishing', | ||
'/marketplace/starters/next-js-multilingual-blog-per-local-publishing': | ||
'/docs/general-concepts/localization#locale-based-publishing', | ||
'/pricing/compare': '/pricing', | ||
'/docs/content-management-api/rate-limits': '/docs/content-management-api/technical-limits', | ||
'/docs/content-delivery-api/rate-limiting': '/docs/content-delivery-api/technical-limits', | ||
'/partners/locale': '/tech-partners/crowdin', | ||
'/partners/imgix': '/tech-partners/imgix', | ||
'/blog/introducing-visual-editing-for-vercel-and-datocms-enterprise-customers': | ||
'/blog/introducing-content-link-for-vercel-and-datocms-enterprise-customers', | ||
'/blog/july-update-visual-editing-and-per-locale-publishing': | ||
'/blog/july-update-content-link-and-per-locale-publishing', | ||
'/docs/visual-editing': '/docs/content-link/how-to-use-content-link', | ||
'/docs/visual-editing/how-to-use-visual-editing': '/docs/content-link/how-to-use-content-link', | ||
'/partners/harvey-cameron/showcase/jacuzzi ': '/partners', | ||
'/docs/plugins/install': '/docs/general-concepts/plugins', | ||
'/blog/live-preview-changes-on-gatsby-preview': '/blog', | ||
'/docs/localizing-images': '/docs/content-delivery-api/images-and-videos', | ||
'/cda-explorer': '/docs/content-delivery-api', | ||
'/docs/content-management-api/using-the-ruby-client': '/docs/content-management-api', | ||
'/use-cases': '/', | ||
'/docs/cdn-settings/advanced-asset-settings': '/docs/asset-api/asset-cdn-settings', | ||
'/docs/general-concepts/videos': '/docs/asset-api/videos', | ||
'/docs/general-concepts/images': '/docs/asset-api/images', | ||
'/docs/project-starters-and-templates': '/docs/general-concepts/project-starters-and-templates', | ||
'/docs/project-starters-and-templates/clone-project-button': | ||
'/docs/general-concepts/project-starters-and-templates#generate-a-clone-project-button', | ||
'/docs/project-starters-and-templates/project-starter-button': | ||
'/docs/general-concepts/project-starters-and-templates#generate-a-project-starter-button', | ||
'/blog/headless-cms-unconventional-use-cases': '/customer-stories/trip-to-japan', | ||
'/docs/plugin-sdk': '/docs/plugin-sdk/introduction', | ||
'/docs/general-concepts/environments': '/docs/general-concepts/primary-and-sandbox-environments', | ||
'/docs/react/*splat': '/docs/next-js/*splat', | ||
'/marketplace/plugins/i/datocms-plugin-gatsby-cloud': '/', | ||
'/docs/content-management-api/resources/sso-token': | ||
'/docs/content-management-api/resources/sso-settings/generate_token', | ||
'/docs/next-js/setting-up-next-js-preview-mode': | ||
'/docs/legacy-next-js-documentation/setting-up-next-js-preview-mode', | ||
'/docs/plugin-sdk/execute-code-on-boot': '/docs/plugin-sdk/event-hooks', | ||
'/partners/harvey-cameron': '/partners', | ||
'/docs/vue/display-videos': '/docs/nuxt', | ||
'/docs/vue/loading-responsive-progressive-images-from-datocms': '/docs/nuxt', | ||
'/docs/vue': '/docs/nuxt', | ||
'/docs/vue/*splat': '/docs/nuxt/*splat', | ||
'/docs/sveltekit/getting-started-with-sveltekit-and-datocms': '/docs/svelte', | ||
'/partners/dev-kitchen': '/partners', | ||
'/docs/plugin-sdk/sdk/field-extensions ': '/docs/plugin-sdk/field-extensions', | ||
'/cms/hugo-cms': '/', | ||
'/marketplace/starters/next-13-company-landing-page-demo': | ||
'/marketplace/starters/next-js-starter-kit', | ||
'/docs/agency-partner-program/benefits': '/partner-program', | ||
'/docs/site-search/excluding-text': | ||
'/docs/site-search/how-the-crawling-works#excluding-content-from-indexing', | ||
'/docs/building-plugins/creating-a-new-plugin': '/docs/plugin-sdk/introduction', | ||
'/docs/building-plugins/sdk-reference': '/docs/plugin-sdk/introduction', | ||
'/docs/building-plugins/entry-point': '/docs/plugin-sdk/introduction', | ||
'/docs/gatsby': '/docs', | ||
'/docs/other-ssgs': '/docs', | ||
'/docs/guides/building-plugins/sdk-reference': '/docs/plugin-sdk/build-your-first-plugin', | ||
'/docs/react': '/docs', | ||
'/docs/guides/building-plugins/creating-a-new-plugin': '/docs/plugin-sdk/build-your-first-plugin', | ||
'/docs/hugo': '/docs', | ||
'/docs/building-plugins': '/docs/plugin-sdk/build-your-first-plugin', | ||
'/docs/building-plugins/using-the-generator': '/docs/plugin-sdk/build-your-first-plugin', | ||
'/docs/content-delivery-api/docs/general-concepts/roles-and-permission-system': | ||
'/docs/general-concepts/roles-and-permission-system', | ||
'/docs/plugin-sdk/render%E2%80%8BUpload%E2%80%8BSidebarPanel': '/docs/plugin-sdk/sidebar-panels', | ||
'/docs/plugin-sdk/sdk/field-extensions': '/docs/plugin-sdk/field-extensions', | ||
'/docs/building-plugins/install': | ||
'/docs/plugin-sdk/build-your-first-plugin#install-your-plugin-in-the-datocms-web-app', | ||
'/docs/building-plugins/publishing': '/docs/plugin-sdk/publishing-to-marketplace', | ||
'/docs/guides': '/docs', | ||
'/docs/react/managing-images': '/docs', | ||
'/marketplace/plugins/i/[email protected]:voorhoede/datocms-plugin-editor-help.git': | ||
'/marketplace/plugins', | ||
'/marketplace/plugins/i/license': '/marketplace/plugins', | ||
'/docs/gatsby/gatsby-cloud': '/docs', | ||
'/docs/single-page-apps/react': '/docs', | ||
'/docs/static-generators/gatsbyjs': '/docs', | ||
'/marketplace/plugins/i/contributing.md': '/docs/plugin-sdk/publishing-to-marketplace', | ||
'/marketplace/starters/middleman-portfolio': '/marketplace/starters', | ||
'/docs/react/structured-text-fields': '/docs', | ||
'/marketplace/plugins/i/LICENSE.txt': '/docs/plugin-sdk/publishing-to-marketplace', | ||
'/docs/middleman': '/docs', | ||
'/docs/guides/localizing-images': '/docs/asset-api/images', | ||
'/blog/introducing-inherited-roles-for-greater-modularity-in-role-management': | ||
'/blog/introducing-inherited-roles', | ||
'/marketplace/plugins/i/datocms-plugin-kontainer': '/marketplace/plugins', | ||
'/docs/vue/rendering-structured-text-fields': '/docs/nuxt/rendering-structured-text-fields', | ||
'/product-updates/undefined': '/product-updates', | ||
'/docs/content-management-api/resources/item/instances/docs/content-management-api/resources/item/instances': | ||
'/docs/content-management-api', | ||
'/marketplace/starters/next-js-multilingual-blog': '/marketplace/starters', | ||
'/docs/guides/installing-site-search/widget': '/docs/general-concepts/site-search', | ||
'/docs/pro-tips/create-a-select-with-a-single-line-string-field': '/docs', | ||
'/docs/guides/building-plugins': '/docs/plugin-sdk/build-your-first-plugin', | ||
'/partners/tecnica': '/partners', | ||
'/docs/hugo/fields': '/docs', | ||
'/docs/content-modelling/data-migration/docs/content-modelling/data-migration': | ||
'/docs/content-modelling/data-migration', | ||
'/partners/harvey-cameron/showcase/jacuzzi': '/partners', | ||
'/docs/plugin-sdk/field-extension': '/docs/plugin-sdk/field-extensions', | ||
'/docs/jekyll/fields': '/docs', | ||
'/docs/static-generators/middleman/image-manipulation': '/docs', | ||
'/docs/gatsby/examples': '/docs', | ||
'/docs/guides/custom-assets-domain/google-cloud-storage': | ||
'/marketplace/enterprise/google-cloud-storage', | ||
'/use-cases/docs': '/use-cases/headless-cms-knowledge-management', | ||
'/features/headless-cms-multi-languag': '/features/headless-cms-multi-language', | ||
'/docs/guides/custom-assets-domain': '/docs', | ||
'/docs/deployments/zeit': '/docs', | ||
'/docs/content-management-api/resources/upload/updatebut': | ||
'/docs/content-management-api/resources/upload', | ||
'/foo': '/docs', | ||
}).map(([pattern, replacement]) => ({ | ||
matcher: match(pattern, { decode: decodeURIComponent }), | ||
replacer: (params: Partial<Record<string, string | string[]>>) => | ||
replacement.replace('*splat', params?.splat ? (params.splat as string[]).join('/') : '*SPLAT'), | ||
})); | ||
|
||
async function fetchItemIdForPathname( | ||
client: Client, | ||
pathname: string, | ||
): Promise<[string, string] | 'ITEM_NOT_FOUND' | 'INVALID_ROUTE'> { | ||
): Promise<[string, string] | 'ITEM_NOT_FOUND' | 'INVALID_ROUTE' | 'MULTIPLE_ITEMS'> { | ||
for (const config of pathToResourceConfig) { | ||
const matcher = match(config.pattern, { decode: decodeURIComponent }); | ||
const result = matcher(pathname); | ||
|
||
if (result) { | ||
const fieldValue = result.params[config.urlVariable]; | ||
const items = await client.items.list({ | ||
filter: { type: config.model, fields: { [config.field]: { eq: fieldValue } } }, | ||
let { data: items } = await client.items.rawList({ | ||
filter: { | ||
type: config.model, | ||
fields: { | ||
[config.field]: { eq: Array.isArray(fieldValue) ? fieldValue.join('/') : fieldValue }, | ||
}, | ||
}, | ||
}); | ||
if (config.model === 'doc_page') { | ||
const docGroupSlug = result.params.docGroupSlug as string; | ||
const newItems: SchemaTypes.Item[] = []; | ||
for (const item of items) { | ||
const docGroups = await client.items.references(item, { version: 'published' }); | ||
if (docGroups.find((group) => group.slug === docGroupSlug)) { | ||
newItems.push(item); | ||
} | ||
} | ||
items = newItems; | ||
} | ||
if (items.length > 1) { | ||
return 'MULTIPLE_ITEMS'; | ||
} | ||
const firstItem = items[0]; | ||
if (!firstItem) { | ||
return 'ITEM_NOT_FOUND'; | ||
|
@@ -470,7 +693,7 @@ export default async function (client: Client) { | |
|
||
async function analyzeItem( | ||
item: SchemaTypes.Item, | ||
cb: (path: string, url: URL, node: Link) => Promise<void>, | ||
cb: (path: string, url: URL, node: NodeWithMeta) => Promise<void>, | ||
path: string[] = [], | ||
) { | ||
const itemType = itemTypes.find((it) => it.id === item.relationships.item_type.data.id)!; | ||
|
@@ -499,6 +722,20 @@ export default async function (client: Client) { | |
try { | ||
const url = new URL(node.url, 'https://www.datocms.com/'); | ||
if (url.hostname === 'www.datocms.com') { | ||
if (url.pathname.endsWith('/')) { | ||
// console.log('TRAILING_SLASH', url.pathname); | ||
url.pathname = url.pathname.slice(0, -1); | ||
} | ||
|
||
for (const redirect of redirects) { | ||
const result = redirect.matcher(url.pathname); | ||
if (result) { | ||
const newPathname = redirect.replacer(result.params); | ||
// console.log('REDIRECT', url.pathname, '->', newPathname); | ||
url.pathname = newPathname; | ||
} | ||
} | ||
|
||
promises.push(cb(path.join('.'), url, node)); | ||
} | ||
} catch { | ||
|
@@ -537,32 +774,65 @@ export default async function (client: Client) { | |
filter: { type: model.id }, | ||
nested: true, | ||
})) { | ||
await analyzeItem(record, async (path, url, link) => { | ||
const oldRecord = cloneDeep(record); | ||
|
||
await analyzeItem(record, async (path, url, node) => { | ||
const result = await fetchItemIdForPathname(client, url.pathname); | ||
if (Array.isArray(result)) { | ||
const [modelApiKey, itemId] = result; | ||
const linkedModel = itemTypes.find((it) => it.attributes.api_key === modelApiKey)!; | ||
|
||
const itemLinkNode = node as ItemLink; | ||
itemLinkNode.type = 'itemLink'; | ||
itemLinkNode.item = itemId; | ||
|
||
const meta = itemLinkNode.meta || []; | ||
if (url.hash) { | ||
itemLinkNode.meta?.push({ id: 'hash', value: url.hash }); | ||
} | ||
if (url.search) { | ||
itemLinkNode.meta?.push({ id: 'search', value: url.search }); | ||
} | ||
if (meta.length > 0) { | ||
itemLinkNode.meta = meta; | ||
} | ||
delete (itemLinkNode as any).url; | ||
|
||
console.log( | ||
'[FOUND]', | ||
`https://datocms.admin.datocms.com/editor/item_types/${model.id}/items/${record.id}`, | ||
path, | ||
url.toString(), | ||
`"${render(link)}"`, | ||
`"${render(node)}"`, | ||
`https://datocms.admin.datocms.com/editor/item_types/${linkedModel.id}/items/${itemId}`, | ||
JSON.stringify(itemLinkNode), | ||
); | ||
} else if ( | ||
(result === 'INVALID_ROUTE' && !knownPaths.includes(url.pathname)) || | ||
result === 'ITEM_NOT_FOUND' | ||
!knownPaths.includes(url.pathname) && | ||
(result === 'INVALID_ROUTE' || result === 'ITEM_NOT_FOUND') | ||
) { | ||
console.log( | ||
`[${result}]`, | ||
`https://datocms.admin.datocms.com/editor/item_types/${model.id}/items/${record.id}`, | ||
path, | ||
url.toString(), | ||
`"${render(link)}"`, | ||
); | ||
// console.log( | ||
// `[${result}]`, | ||
// `https://datocms.admin.datocms.com/editor/item_types/${model.id}/items/${record.id}`, | ||
// path, | ||
// url.toString(), | ||
// `"${render(node)}"`, | ||
// ); | ||
} else if (result === 'MULTIPLE_ITEMS') { | ||
// console.log( | ||
// `[${result}]`, | ||
// `https://datocms.admin.datocms.com/editor/item_types/${model.id}/items/${record.id}`, | ||
// path, | ||
// url.toString(), | ||
// `"${render(node)}"`, | ||
// ); | ||
} | ||
}); | ||
|
||
if (!isEqual(oldRecord, record)) { | ||
await client.items.rawUpdate(record.id, { data: omit(record, 'relationships') }); | ||
console.log('Updated'); | ||
} | ||
} | ||
} | ||
|
||
|