Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for monorepos based on npm/Yarn Workspaces #1567

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,39 @@ will treat each file contained within it as an entry point.
typedoc package1/index.ts package2/index.ts
```

### Monorepos / Workspaces

If your codebase is comprised of one or more npm packages, you can pass the paths to these
packages and TypeDoc will attempt to determine entry points from your `package.json`'s `main`
property (or its default value `index.js`).
If any of the packages given are the root of an [npm Workspace](https://docs.npmjs.com/cli/v7/using-npm/workspaces)
or a [Yarn Workspace](https://classic.yarnpkg.com/en/docs/workspaces/) TypeDoc will find all
the `workpsaces` defined in the `package.json`.
This mode requires sourcemaps in your JS entry points, in order to find the TS entry points.
Supports wildcard paths in the same fashion as those found in npm or Yarn workspaces.

#### Single npm module

```text
typedoc --packages .
```

#### Monorepo with npm/Yarn workspace at the root

```text
typedoc --packages .
```

#### Monorepo with manually specified sub-packages to document

This can be useful if you do not want all your workspaces to be processed.
Accepts the same paths as would go in the `package.json`'s workspaces

```text
# Note the single quotes prevent shell widcard expansion, allowing typedoc to do the expansion
typedoc --packages a-package --packages 'some-more-packages/*' --packages 'some-other-packages/*'
```

### Arguments

For a complete list of the command line arguments run `typedoc --help` or visit
Expand All @@ -47,6 +80,9 @@ For a complete list of the command line arguments run `typedoc --help` or visit
- `--options`<br>
Specify a json option file that should be loaded. If not specified TypeDoc
will look for 'typedoc.json' in the current directory.
- `--packages <path/to/package/>`<br>
Specify one or more sub packages, or the root of a monorepo with workspaces.
Supports wildcard paths in the same fashion as those found in npm or Yarn workspaces.
- `--tsconfig <path/to/tsconfig.json>`<br>
Specify a typescript config file that should be loaded. If not
specified TypeDoc will look for 'tsconfig.json' in the current directory.
Expand Down
7 changes: 5 additions & 2 deletions bin/typedoc
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ async function run(app) {
return ExitCodes.OptionError;
}

if (app.options.getValue("entryPoints").length === 0) {
app.logger.error("No entry points provided");
if (
app.options.getValue("entryPoints").length === 0 &&
app.options.getValue("packages").length === 0
) {
app.logger.error("No entry points or packages provided");
return ExitCodes.NoEntryPoints;
}

Expand Down
1 change: 1 addition & 0 deletions examples/basic/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
docs
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running typedoc on the examples folder locally, I needed to gitignore this folder.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"dependencies": {
"colors": "^1.4.0",
"fs-extra": "^9.1.0",
"glob": "^7.1.6",
"handlebars": "^4.7.7",
"lodash": "^4.17.21",
"lunr": "^2.3.9",
Expand All @@ -37,6 +38,7 @@
},
"devDependencies": {
"@types/fs-extra": "^9.0.11",
"@types/glob": "^7.1.3",
"@types/lodash": "^4.14.168",
"@types/lunr": "^2.3.3",
"@types/marked": "^2.0.2",
Expand Down Expand Up @@ -65,7 +67,7 @@
],
"scripts": {
"pretest": "node scripts/copy_test_files.js",
"test": "nyc --reporter=html --reporter=text-summary mocha --timeout=10000 'dist/test/**/*.test.js'",
"test": "nyc --reporter=html --reporter=text-summary mocha --timeout=10000 'dist/test/**/*.test.js' --exclude 'dist/test/packages/**'",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the /packages/ folder contains a non-trivial amount of sample code we need to avoid accidentally picking up tests in there

"prerebuild_specs": "npm run pretest",
"rebuild_specs": "node scripts/rebuild_specs.js",
"build": "tsc --project .",
Expand Down
129 changes: 125 additions & 4 deletions scripts/copy_test_files.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,128 @@

const fs = require("fs-extra");
const { join } = require("path");
const { spawn } = require("child_process");

function promiseFromChildProcess(childProcess) {
return new Promise(function (resolve, reject) {
childProcess.on("error", function (error) {
reject(
new Error(
childProcess.spawnargs.join(" ") + " : " + error.message
)
);
});
childProcess.on("exit", function (code) {
if (code !== 0) {
reject(
new Error(
childProcess.spawnargs.join(" ") +
" : exited with code " +
code
)
);
} else {
resolve();
}
});
});
}

const isWindows = process.platform === "win32";
const npmCommand = isWindows ? "npm.cmd" : "npm";

function ensureNpmVersion() {
return Promise.resolve().then(() => {
const npmProc = spawn(npmCommand, ["--version"], {
stdio: ["ignore", "pipe", "inherit"],
});
let npmVersion = "";
npmProc.stdout.on("data", (data) => {
npmVersion += data;
});
return promiseFromChildProcess(npmProc).then(() => {
npmVersion = npmVersion.trim();
let firstDot = npmVersion.indexOf(".");
const npmMajorVer = parseInt(
npmVersion.slice(0, npmVersion.indexOf("."))
);
if (npmMajorVer < 7) {
throw new Error(
"npm version must be at least 7, version installed is " +
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Installing the monorepo's deps (and gluing the inter-package deps together) requires either lerna, or yarn, or npm>7. I opted for the npm approach as I figured it's the most "vanilla" despite having been around for the least amount of time. This could run in to issues under some of your CI configs however so let's see if anything breaks and decide what to do.

npmVersion
);
}
});
});
}

function prepareMonorepoFolder() {
return Promise.resolve()
.then(() => {
return promiseFromChildProcess(
spawn(
"git",
["clone", "https://github.com/efokschaner/ts-monorepo.git"],
{
cwd: join(__dirname, "../dist/test/packages"),
stdio: "inherit",
}
)
);
})
.then(() => {
return promiseFromChildProcess(
spawn(
"git",
["checkout", "73bdd4c6458ad4cc3de35498e65d55a1a44a8499"],
{
cwd: join(
__dirname,
"../dist/test/packages/ts-monorepo"
),
stdio: "inherit",
}
)
);
})
.then(() => {
return promiseFromChildProcess(
spawn(npmCommand, ["install"], {
cwd: join(__dirname, "../dist/test/packages/ts-monorepo"),
stdio: "inherit",
})
);
})
.then(() => {
return promiseFromChildProcess(
spawn(npmCommand, ["run", "build"], {
cwd: join(__dirname, "../dist/test/packages/ts-monorepo"),
stdio: "inherit",
})
);
});
}

function prepareSinglePackageExample() {
return Promise.resolve().then(() => {
return promiseFromChildProcess(
spawn(npmCommand, ["run", "build"], {
cwd: join(
__dirname,
"../dist/test/packages/typedoc-single-package-example"
),
stdio: "inherit",
})
);
});
}

const copy = [
"test/converter",
"test/converter2",
"test/renderer",
"test/module",
"test/packages",
"test/utils/options/readers/data",
];

Expand All @@ -20,7 +136,12 @@ const copies = copy.map((dir) => {
.then(() => fs.copy(source, target));
});

Promise.all(copies).catch((reason) => {
console.error(reason);
process.exit(1);
});
Promise.all(copies)
.then(ensureNpmVersion)
.then(() =>
Promise.all([prepareMonorepoFolder(), prepareSinglePackageExample()])
)
.catch((reason) => {
console.error(reason);
process.exit(1);
});
8 changes: 4 additions & 4 deletions scripts/rebuild_specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ function rebuildConverterTests(dirs) {

for (const fullPath of dirs) {
console.log(fullPath);
const src = app.expandInputFiles([fullPath]);

for (const [file, before, after] of conversions) {
const out = path.join(fullPath, `${file}.json`);
if (fs.existsSync(out)) {
TypeDoc.resetReflectionID();
before();
const result = app.converter.convert(src, program);
const result = app.converter.convert(
app.getEntrypointsForPaths([fullPath])
);
const serialized = app.serializer.toObject(result);

const data = JSON.stringify(serialized, null, " ")
Expand Down Expand Up @@ -104,7 +104,7 @@ async function rebuildRendererTest() {
externalPattern: ["**/node_modules/**"],
});

app.options.setValue("entryPoints", app.expandInputFiles([src]));
app.options.setValue("entryPoints", [src]);
const project = app.convert();
await app.generateDocs(project, out);
await app.generateJson(project, path.join(out, "specs.json"));
Expand Down