Skip to content

Commit f1d4598

Browse files
authored
feat: docs-viewer (#9774)
* yay docs viewer :eyeroll: * add readme * stub out command to rebuild * updates * make work
1 parent 9b4950e commit f1d4598

11 files changed

+436
-1
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,6 @@ benchmarks/results/*.json
5656
!.vscode/
5757
.idea/
5858
*.iml
59+
60+
# Ignore the cloned repos for docs viewer
61+
docs-viewer/projects

docs-viewer/README.md

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Internal Docs Viewer
2+
3+
This package provides a script for quickly setting up the various repositories
4+
needed to preview the API docs defined in the source-code of this project and
5+
linking them together properly.
6+
7+
The scripts can be run from the project root or from within this directory.
8+
9+
### `bun preview-api-docs`
10+
11+
This will update the various repositories to their latest commit (or clone the
12+
repo if needed) in the `docs-viewer/projects` directory. This directory is
13+
git-ignored.
14+
15+
It will then generate necessary symlinks, run the docs build script, and start
16+
the api-docs viewer app.
17+
18+
Once the app is running, changes to the docs do not automatically rebuild.
19+
The command `rebuild-api-docs` will update the docs data for the running app.
20+
21+
### `bun rebuild-api-docs`
22+
23+
This will rebuild the api-docs data consumed by the api-docs viewer application.
24+
25+
This must be run manually after any changes to api documentation for them to be
26+
available to a running instance of the api docs application. If the app is not
27+
currently running, this command is unneeded as `preview-api-docs` will also do
28+
an initial build of the docs.
29+

docs-viewer/package.json

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "@warp-drive/internal-docs-viewer",
3+
"version": "5.4.0-alpha.142",
4+
"description": "API Docs Viewer for the WarpDrive Project Monorepo | Unpublished",
5+
"private": true,
6+
"type": "module",
7+
"files": [
8+
"src"
9+
],
10+
"bin": {
11+
"preview-api-docs": "./src/preview-docs.ts",
12+
"rebuild-api-docs": "./src/rebuild-docs.ts"
13+
},
14+
"dependencies": {
15+
"@types/bun": "^1.2.4",
16+
"typescript": "^5.8.2",
17+
"chalk": "5.4.1",
18+
"debug": "4.4.0",
19+
"@pnpm/find-workspace-dir": "1000.1.0",
20+
"@types/debug": "4.1.12"
21+
},
22+
"engines": {
23+
"node": ">= 18.20.7"
24+
},
25+
"volta": {
26+
"extends": "../package.json"
27+
}
28+
}

docs-viewer/projects/.gitkeep

Whitespace-only changes.

docs-viewer/src/-utils.ts

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { findWorkspaceDir } from '@pnpm/find-workspace-dir';
2+
import path from 'path';
3+
import chalk from 'chalk';
4+
import fs from 'fs';
5+
6+
const workspaceRoot = (await findWorkspaceDir(process.cwd())) as string;
7+
8+
if (!workspaceRoot) {
9+
throw new Error('Could not find workspace root');
10+
}
11+
12+
const docsViewerRoot = path.join(workspaceRoot, 'docs-viewer');
13+
const projectRoot = path.join(docsViewerRoot, './projects');
14+
15+
export { workspaceRoot, docsViewerRoot, projectRoot };
16+
17+
export function log(message: string) {
18+
console.log(chalk.grey(`[docs-viewer]\t${message}`));
19+
}
20+
21+
export async function getCurrentVersion(tool: string) {
22+
const proc = Bun.spawn([tool, '--version'], {
23+
env: process.env,
24+
stdio: ['inherit', 'pipe', 'inherit'],
25+
});
26+
await proc.exited;
27+
const version = await new Response(proc.stdout).text();
28+
return version.trim().replace('v', '');
29+
}
30+
31+
export function determinePackageManager(dir: string) {
32+
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) {
33+
return 'pnpm';
34+
}
35+
if (fs.existsSync(path.join(dir, 'package-lock.json'))) {
36+
return 'npm';
37+
}
38+
if (fs.existsSync(path.join(dir, 'yarn.lock'))) {
39+
return 'yarn';
40+
}
41+
42+
return 'npm';
43+
}
44+
45+
export async function generateDocs() {
46+
const currentVersion = require(path.join(workspaceRoot, 'package.json')).version;
47+
const absoluteVersion = currentVersion.split('-')[0];
48+
const command = ['bun', 'gen', '--project', 'ember-data', '--version', absoluteVersion];
49+
const proc = Bun.spawn(command, {
50+
cwd: path.join(projectRoot, 'ember-jsonapi-docs'),
51+
env: Object.assign({}, process.env, { COREPACK_INTEGRITY_KEYS: 0 }),
52+
stdio: ['inherit', 'inherit', 'inherit'],
53+
});
54+
await proc.exited;
55+
56+
const command2 = ['bun', 'fix:files'];
57+
const proc2 = Bun.spawn(command2, {
58+
cwd: path.join(projectRoot, 'ember-api-docs-data'),
59+
env: process.env,
60+
stdio: ['inherit', 'inherit', 'inherit'],
61+
});
62+
await proc2.exited;
63+
}
64+
65+
export function repoDetails(gitUrl: string) {
66+
const repoPath = gitUrl.replace('.git', '').replace('git@github.com:', '');
67+
const [org, name] = repoPath.split('/');
68+
const installPathFromRoot = path.join('./projects', name);
69+
const location = path.join(docsViewerRoot, installPathFromRoot);
70+
71+
return {
72+
org,
73+
name,
74+
repoPath,
75+
gitUrl,
76+
installPathFromRoot,
77+
location,
78+
relativePath: path.relative(__dirname, path.join(docsViewerRoot, installPathFromRoot)),
79+
};
80+
}
81+
82+
export async function installDeps(packageManager: 'pnpm' | 'npm' | 'yarn', details: ReturnType<typeof repoDetails>) {
83+
const proc = Bun.spawn(
84+
[packageManager, 'install', packageManager === 'pnpm' ? '--ignore-workspace' : ''].filter(Boolean),
85+
{
86+
cwd: details.location,
87+
env: process.env,
88+
stdio: ['inherit', 'inherit', 'inherit'],
89+
}
90+
);
91+
await proc.exited;
92+
}
93+
94+
export async function maybeMakePNPMInstallable(details: ReturnType<typeof repoDetails>) {
95+
// get the version to use from package.json
96+
const packageJson = require(path.join(details.location, 'package.json'));
97+
98+
const nodeVersion = await getCurrentVersion('node');
99+
const pnpmVersion = await getCurrentVersion('pnpm');
100+
101+
if (
102+
!packageJson.volta ||
103+
packageJson.volta.node !== nodeVersion ||
104+
packageJson.volta.pnpm !== pnpmVersion ||
105+
packageJson.packageManager ||
106+
packageJson.engines?.node !== nodeVersion
107+
) {
108+
delete packageJson.packageManager;
109+
packageJson.volta = {
110+
node: nodeVersion,
111+
pnpm: pnpmVersion,
112+
};
113+
packageJson.engines = packageJson.engines || {};
114+
packageJson.engines.node = nodeVersion;
115+
116+
// if this is ember-api-docs we need to also force it to use dart-sass
117+
if (packageJson.name === 'ember-api-docs') {
118+
packageJson.pnpm = packageJson.pnpm || {};
119+
packageJson.pnpm.overrides = packageJson.pnpm.overrides || {};
120+
packageJson.pnpm.overrides['node-sass'] = 'npm:sass@^1.86.0';
121+
}
122+
123+
fs.writeFileSync(path.join(details.location, 'package.json'), JSON.stringify(packageJson, null, 2));
124+
125+
// run install to pickup the lockfile change
126+
await installDeps('pnpm', details);
127+
128+
const proc = Bun.spawn(['git', 'commit', '-am', '"ensure volta works as expected"'], {
129+
cwd: details.location,
130+
env: process.env,
131+
stdio: ['inherit', 'inherit', 'inherit'],
132+
});
133+
await proc.exited;
134+
}
135+
}

docs-viewer/src/preview-docs.ts

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#! /usr/bin/env bun
2+
/**
3+
* Sets up the docs viewer app
4+
*/
5+
import chalk from 'chalk';
6+
import path from 'path';
7+
import fs from 'fs';
8+
import {
9+
determinePackageManager,
10+
docsViewerRoot,
11+
generateDocs,
12+
installDeps,
13+
log,
14+
maybeMakePNPMInstallable,
15+
projectRoot,
16+
repoDetails,
17+
workspaceRoot,
18+
} from './-utils.ts';
19+
20+
const EMBER_API_DOCS_REPO = `git@github.com:ember-learn/ember-api-docs.git`;
21+
const EMBER_JSONAPI_DOCS_REPO = `git@github.com:ember-learn/ember-jsonapi-docs.git`;
22+
const EMBER_API_DOCS_DATA_REPO = `git@github.com:ember-learn/ember-api-docs-data.git`;
23+
24+
async function getOrUpdateRepo(gitUrl: string) {
25+
const details = repoDetails(gitUrl);
26+
27+
let updated = true;
28+
if (fs.existsSync(details.location)) {
29+
updated = await getLatest(details);
30+
} else {
31+
await cloneRepo(details);
32+
}
33+
34+
if (!updated) {
35+
return;
36+
}
37+
38+
// install dependencies
39+
const packageManager = determinePackageManager(details.location);
40+
if (packageManager === 'pnpm') {
41+
// some of the repositories use pnpm but do not have volta configured
42+
// and have not had their engines/packageManager field updated in a while.
43+
// this lets us still install them with pnpm if that is the case
44+
await maybeMakePNPMInstallable(details);
45+
}
46+
47+
log(`Installing dependencies in ${chalk.green(details.installPathFromRoot)} using ${chalk.yellow(packageManager)}`);
48+
await installDeps(packageManager, details);
49+
}
50+
51+
async function getSHA(details: ReturnType<typeof repoDetails>, reference: string) {
52+
log(`Getting commit sha for ${reference} for ${chalk.green(details.repoPath)}`);
53+
const shaProc1 = Bun.spawn(['git', 'rev-parse', reference], {
54+
cwd: details.location,
55+
});
56+
await shaProc1.exited;
57+
return (await new Response(shaProc1.stdout).text()).trim();
58+
}
59+
60+
/**
61+
* Updates the repo by fetching only the latest commit on the main branch
62+
* and resetting the local repo to that commit
63+
*
64+
* Returns true if the repo was updated, false if it was already up to date
65+
*/
66+
async function getLatest(details: ReturnType<typeof repoDetails>): Promise<boolean> {
67+
const currentSha = await getSHA(details, 'HEAD^1');
68+
69+
log(`Updating ${chalk.green(details.repoPath)} in ${chalk.green(details.installPathFromRoot)} to latest`);
70+
const mainBranch = details.name === 'ember-jsonapi-docs' ? 'master' : 'main';
71+
const proc = Bun.spawn(['git', 'fetch', 'origin', mainBranch, '--depth=1'], {
72+
cwd: details.location,
73+
});
74+
await proc.exited;
75+
76+
// if the commit sha has not changed, we do not need to reset
77+
const newSha = await getSHA(details, `origin/${mainBranch}`);
78+
79+
if (currentSha === newSha) {
80+
log(`${chalk.green(details.repoPath)} is already up to date`);
81+
return false;
82+
} else {
83+
log(`Resetting ${chalk.green(details.repoPath)} from ${chalk.red(currentSha)} to ${chalk.green(newSha)}`);
84+
}
85+
86+
const proc2 = Bun.spawn(['git', 'reset', '--hard', `origin/${mainBranch}`], {
87+
cwd: details.location,
88+
});
89+
await proc2.exited;
90+
91+
return true;
92+
}
93+
94+
/**
95+
* Clones the repo, fetching only the latest commit
96+
*/
97+
async function cloneRepo(details: ReturnType<typeof repoDetails>) {
98+
const relativePath = path.join('./projects', details.name);
99+
log(`Cloning ${chalk.green(details.repoPath)} to ${chalk.green(relativePath)}`);
100+
const proc = Bun.spawn(['git', 'clone', details.gitUrl, relativePath, '--depth=1'], {
101+
cwd: docsViewerRoot,
102+
});
103+
await proc.exited;
104+
}
105+
106+
async function main() {
107+
log('Setting up the docs viewer');
108+
log(`\tCurrent working directory: ${chalk.green(process.cwd())}`);
109+
110+
// clone repos
111+
//////////////
112+
//
113+
await getOrUpdateRepo(EMBER_API_DOCS_DATA_REPO);
114+
await getOrUpdateRepo(EMBER_JSONAPI_DOCS_REPO);
115+
await getOrUpdateRepo(EMBER_API_DOCS_REPO);
116+
117+
// symlink our own project root into projects as 'data'
118+
/////////////////////////////////////////////////////////////
119+
//
120+
const emberDataLocation = path.join(projectRoot, 'data');
121+
if (!fs.existsSync(emberDataLocation)) {
122+
log(`Symlinking ${chalk.green('ember-data')} to ${chalk.green('./projects/data')}`);
123+
fs.symlinkSync(workspaceRoot, emberDataLocation);
124+
}
125+
126+
// symlink `ember-api-docs-data` into `ember-api-docs`
127+
////////////////////////////////////////////////////
128+
//
129+
const emberApiDocsData = path.join(projectRoot, 'ember-api-docs-data');
130+
const symLinkLocation = path.join(projectRoot, 'ember-api-docs/ember-api-docs-data');
131+
if (!fs.existsSync(symLinkLocation)) {
132+
log(`Symlinking ${chalk.green('ember-api-docs-data')} to ${chalk.green('ember-api-docs')}`);
133+
fs.symlinkSync(emberApiDocsData, symLinkLocation);
134+
}
135+
136+
log('Docs viewer setup complete');
137+
log('Generating the current docs in ember-jsonapi-docs (these will be inserted into ember-api-docs-data)');
138+
139+
// generate the docs
140+
////////////////////
141+
//
142+
await generateDocs();
143+
144+
log('Docs generated. Run `bun regenerate-docs` to update the docs with any changes');
145+
log('Starting the docs viewer');
146+
147+
// start the docs viewer
148+
////////////////////////
149+
//
150+
const proc2 = Bun.spawn(['pnpm', 'start'], {
151+
cwd: path.join(projectRoot, 'ember-api-docs'),
152+
env: process.env,
153+
stdio: ['inherit', 'inherit', 'inherit'],
154+
});
155+
await proc2.exited;
156+
}
157+
158+
main();

docs-viewer/src/rebuild-docs.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#! /usr/bin/env bun
2+
3+
/**
4+
* Rebuilds the data used by the docs viewer app
5+
*/
6+
7+
import { generateDocs } from './-utils.ts';
8+
9+
async function main() {
10+
await generateDocs();
11+
}
12+
13+
main();

0 commit comments

Comments
 (0)