Skip to content

Commit 71eda53

Browse files
committed
remote content source (experimental)
1 parent 75d8ad9 commit 71eda53

File tree

12 files changed

+225
-0
lines changed

12 files changed

+225
-0
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,15 @@ jobs:
6363
- run: yarn build
6464
- run: yarn start
6565
working-directory: examples/node-script-mdx
66+
67+
build-example-node-script-remote-content:
68+
strategy:
69+
matrix:
70+
node-version: [14, 16, 17, 18]
71+
os: [ubuntu-latest, windows-latest]
72+
runs-on: ${{ matrix.os }}
73+
steps:
74+
- uses: schickling-actions/checkout-and-install@main
75+
- run: yarn build
76+
- run: yarn start
77+
working-directory: examples/node-script-remote-content
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nextjs-repo
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { defineDocumentType } from 'contentlayer/source-files'
2+
import { spawn } from 'node:child_process'
3+
import { makeSource } from 'contentlayer/source-remote-files'
4+
5+
const Post = defineDocumentType(() => ({
6+
name: 'Post',
7+
filePathPattern: `docs/**/*.md`,
8+
fields: {
9+
title: {
10+
type: 'string',
11+
required: false,
12+
},
13+
description: {
14+
type: 'string',
15+
required: false,
16+
},
17+
},
18+
computedFields: {
19+
url: {
20+
type: 'string',
21+
resolve: (doc) => `/posts/${doc._raw.flattenedPath}`,
22+
},
23+
},
24+
}))
25+
26+
const syncContentFromGit = async (contentDir: string) => {
27+
const syncRun = async () => {
28+
const gitUrl = 'https://github.com/vercel/next.js.git'
29+
await runBashCommand(`
30+
if [ -d "${contentDir}" ];
31+
then
32+
cd "${contentDir}"; git pull;
33+
else
34+
git clone --depth 1 --single-branch ${gitUrl} ${contentDir};
35+
fi
36+
`)
37+
}
38+
39+
let wasCancelled = false
40+
let syncInterval
41+
42+
const syncLoop = async () => {
43+
console.log('Syncing content files from git')
44+
45+
await syncRun()
46+
47+
if (wasCancelled) return
48+
49+
syncInterval = setTimeout(syncLoop, 1000 * 60)
50+
}
51+
52+
// Block until the first sync is done
53+
await syncLoop()
54+
55+
return () => {
56+
wasCancelled = true
57+
clearTimeout(syncInterval)
58+
}
59+
}
60+
61+
const runBashCommand = (command: string) =>
62+
new Promise((resolve, reject) => {
63+
const child = spawn(command, [], { shell: true })
64+
65+
child.stdout.setEncoding('utf8')
66+
child.stdout.on('data', (data) => process.stdout.write(data))
67+
68+
child.stderr.setEncoding('utf8')
69+
child.stderr.on('data', (data) => process.stderr.write(data))
70+
71+
child.on('close', function (code) {
72+
if (code === 0) {
73+
resolve(void 0)
74+
} else {
75+
reject(new Error(`Command failed with exit code ${code}`))
76+
}
77+
})
78+
})
79+
80+
export default makeSource({
81+
syncFiles: syncContentFromGit,
82+
contentDirPath: 'nextjs-repo',
83+
contentDirInclude: ['docs'],
84+
documentTypes: [Post],
85+
disableImportAliasWarning: true,
86+
})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { allPosts } from './.contentlayer/generated/index.mjs'
2+
3+
const postUrls = allPosts.map(post => post.url)
4+
5+
console.log(`Found ${postUrls.length} posts:`);
6+
console.log(postUrls)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "node-script-remote-content-example",
3+
"private": true,
4+
"scripts": {
5+
"start": "contentlayer build && node --experimental-json-modules my-script.mjs"
6+
},
7+
"dependencies": {
8+
"contentlayer": "latest"
9+
}
10+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "@contentlayer/source-remote-files",
3+
"version": "0.2.10-dev.4",
4+
"type": "module",
5+
"exports": {
6+
".": {
7+
"import": "./dist/index.js"
8+
}
9+
},
10+
"types": "./dist/index.d.ts",
11+
"files": [
12+
"./dist/*.{js,ts,map}",
13+
"./dist/!(__test__)/**/*.{js,ts,map}",
14+
"./src",
15+
"./package.json"
16+
],
17+
"dependencies": {
18+
"@contentlayer/core": "workspace:*",
19+
"@contentlayer/source-files": "workspace:*",
20+
"@contentlayer/utils": "workspace:*"
21+
}
22+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import * as core from '@contentlayer/core'
2+
import * as SourceFiles from '@contentlayer/source-files'
3+
import { unknownToAbsolutePosixFilePath } from '@contentlayer/utils'
4+
import { M, OT, pipe, S, T } from '@contentlayer/utils/effect'
5+
6+
type CancelFn = () => void
7+
8+
type Args = SourceFiles.Args & {
9+
syncFiles: (
10+
/** Provided `contentDirPath` (as absolute file path) */
11+
contentDirPath: string,
12+
) => Promise<CancelFn>
13+
}
14+
15+
export const makeSource: core.MakeSourcePlugin<Args> = async (rawArgs) => {
16+
const {
17+
restArgs: { syncFiles, ...args },
18+
} = await core.processArgs(rawArgs)
19+
20+
const sourcePlugin = await SourceFiles.makeSource(rawArgs)
21+
22+
return {
23+
...sourcePlugin,
24+
fetchData: (fetchDataArgs) =>
25+
pipe(
26+
M.gen(function* ($) {
27+
const contentDirPath = yield* $(
28+
pipe(
29+
core.getCwd,
30+
T.map((cwd) => unknownToAbsolutePosixFilePath(args.contentDirPath, cwd)),
31+
),
32+
)
33+
34+
// TODO acutally cancel the syncing when the process is terminated
35+
const cancelRemoteSyncing = yield* $(
36+
pipe(
37+
T.tryPromiseOrDie(() => syncFiles(contentDirPath)),
38+
OT.withSpan('syncFiles'),
39+
),
40+
)
41+
42+
yield* $(M.finalizer(T.sync(() => cancelRemoteSyncing())))
43+
44+
return sourcePlugin.fetchData(fetchDataArgs)
45+
}),
46+
S.unwrapManaged,
47+
),
48+
}
49+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"extends": "../../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"module": "ES2020",
5+
"rootDir": "./src",
6+
"outDir": "./dist",
7+
"tsBuildInfoFile": "./dist/.tsbuildinfo.json"
8+
},
9+
"include": ["./src"],
10+
"references": [{ "path": "../source-files" }, { "path": "../utils" }, { "path": "../core" }]
11+
}

packages/contentlayer/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
"./source-files/schema": {
1515
"import": "./dist/source-files/schema/index.js"
1616
},
17+
"./source-remote-files": {
18+
"import": "./dist/source-remote-files/index.js"
19+
},
1720
"./client": {
1821
"import": "./dist/client/index.js"
1922
},
@@ -39,6 +42,9 @@
3942
"source-files/schema": [
4043
"./dist/source-files/schema"
4144
],
45+
"source-remote-files": [
46+
"./dist/source-remote-files"
47+
],
4248
"client": [
4349
"./dist/client"
4450
],
@@ -70,6 +76,7 @@
7076
"@contentlayer/client": "workspace:*",
7177
"@contentlayer/core": "workspace:*",
7278
"@contentlayer/source-files": "workspace:*",
79+
"@contentlayer/source-remote-files": "workspace:*",
7380
"@contentlayer/utils": "workspace:*"
7481
},
7582
"devDependencies": {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '@contentlayer/source-remote-files'

0 commit comments

Comments
 (0)