Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@gasket/plugin-docs

Package Overview
Dependencies
Maintainers
8
Versions
237
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@gasket/plugin-docs - npm Package Compare versions

Comparing version
7.5.3
to
8.0.0-next.1
+0
-4
lib/index.js

@@ -1,2 +0,1 @@

import create from './create.js';
import configure from './configure.js';

@@ -7,3 +6,2 @@ import commands from './commands.js';

import webpackConfig from './webpack-config.js';
import prompt from './prompt.js';

@@ -20,6 +18,4 @@ import packageJson from '../package.json' with { type: 'json' };

configure,
create,
commands,
metadata,
prompt,
docsSetup,

@@ -26,0 +22,0 @@ webpackConfig

@@ -5,4 +5,3 @@ import type { Plugin, Gasket } from '@gasket/core';

ModuleData,
PluginData,
PresetData
PluginData
} from '@gasket/plugin-metadata';

@@ -133,4 +132,2 @@

plugins: ModuleDocsConfig[];
/** List of presets */
presets: ModuleDocsConfig[];
/** Root directory */

@@ -164,3 +161,2 @@ root: string;

* - metadata for modules not processed with plugins
* - metadata for presets
*/

@@ -253,20 +249,2 @@ export function buildDocsConfigSet(

/**
* Function to add documentation configuration for a preset.
*/
export function addPreset(
/** Metadata for the preset. */
presetData: PresetData,
/** Initial documentation setup. */
docsSetup?: DocsSetup
): Promise<void>;
/**
* Function to add documentation configuration for multiple presets.
*/
export function addPresets(
/** Metadata for multiple presets. */
presetsDatas: PresetData[]
): Promise<void>;
/**
* Function to add documentation configuration for a module.

@@ -273,0 +251,0 @@ */

@@ -66,3 +66,2 @@ /// <reference types="@gasket/plugin-logger" />

* - metadata for modules not processed with plugins
* - metadata for presets
* @type {import('../internal.d.ts').buildDocsConfigSet}

@@ -97,3 +96,2 @@ */

await builder.addModules(metadata.modules);
await builder.addPresets(metadata.presets);

@@ -100,0 +98,0 @@ return builder.getConfigSet();

+1
-1

@@ -84,3 +84,3 @@ import { readFile, writeFile, copyFile, stat } from 'fs/promises';

// Flatten the moduleDocsConfigs then generate
const flattened = ['plugins', 'presets', 'modules'].reduce(
const flattened = ['plugins', 'modules'].reduce(
(acc, type) => acc.concat(docsConfigSet[type]),

@@ -87,0 +87,0 @@ [docsConfigSet.app]

@@ -78,3 +78,2 @@ import path from 'node:path';

this._plugins = [];
this._presets = [];
this._modules = [];

@@ -206,3 +205,2 @@ this._transforms = [];

this._modules
.concat(this._presets)
.concat(this._plugins)

@@ -297,36 +295,2 @@ .forEach((moduleDoc) => {

/**
* Add DocsConfig to the set for a preset
* @type {import('../internal.d.ts').addPreset}
*/
async addPreset(presetData, docsSetup) {
if (this._presets.find((p) => p.metadata === presetData.metadata)) return;
// If docsSetup is passed, stick with it. Otherwise, look up a docsSetup
// defined by preset. Or, see if gasket.docsSetup in package.json. Finally,
// fall back to defaults.
docsSetup =
docsSetup ||
// @ts-expect-error
(presetData.module && presetData.module.docsSetup) ||
getDocsSetupFromPkg(presetData) ||
docsSetupDefault;
const { name } = presetData;
const targetRoot = path.join(this._docsRoot, 'presets', ...name.split('/'));
const docConfig = await this._buildDocsConfig(presetData, docsSetup, {
targetRoot
});
this._presets.push(docConfig);
}
/**
* Add DocsConfig to the set for multiple presets if not already added
* @type {import('../internal.d.ts').addPresets}
*/
async addPresets(presetDatas) {
await Promise.all(presetDatas.map((p) => this.addPreset(p)));
}
/**
* Add DocsConfig to the set for a module

@@ -384,3 +348,2 @@ * @type {import('../internal.d.ts').addModule}

plugins: sortModules(this._plugins),
presets: sortModules(this._presets),
modules: sortModules(this._modules),

@@ -387,0 +350,0 @@ root: this._root,

@@ -123,3 +123,2 @@ /* eslint-disable max-statements */

addSection('Presets', 'All configured presets', docsConfigSet.presets);
addSection('Templates', 'All configured templates', docsConfigSet.templates);

@@ -126,0 +125,0 @@ addSection('Plugins', 'All configured plugins', docsConfigSet.plugins);

@@ -48,3 +48,3 @@ /** @typedef {import('../internal.d.ts').ModuleDocsConfig} ModuleDocsConfig */

* - gasket scope > user scope > no scope > app plugins
* - presets > plugins > packages
* - plugins > packages
* @param {string} name - Package name

@@ -70,6 +70,5 @@ * @returns {number} weight

//
const isPreset = /preset/;
const isPlugin = /plugin/;
weight += (isPreset.test(name) && 20) || (isPlugin.test(name) && 10) || 0;
weight += (isPlugin.test(name) && 10) || 0;

@@ -76,0 +75,0 @@ return weight;

@@ -54,4 +54,4 @@ import path from 'path';

const { targetRoot } = docsConfig;
const { modules, presets, plugins } = docsConfigSet;
const allModuleDocConfigs = modules.concat(plugins).concat(presets);
const { modules, plugins } = docsConfigSet;
const allModuleDocConfigs = modules.concat(plugins);

@@ -58,0 +58,0 @@ const tx = makeLinkTransform((link) => {

{
"name": "@gasket/plugin-docs",
"version": "7.5.3",
"version": "8.0.0-next.1",
"description": "Centralize doc files from plugins and modules",
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib",
"cjs",
"EXAMPLES.md"

@@ -16,4 +14,3 @@ ],

"types": "./lib/index.d.ts",
"import": "./lib/index.js",
"require": "./cjs/index.js"
"import": "./lib/index.js"
},

@@ -41,3 +38,3 @@ "./package.json": "./package.json"

"rimraf": "^6.1.2",
"@gasket/plugin-command": "^7.6.3"
"@gasket/plugin-command": "^8.0.0-next.1"
},

@@ -47,12 +44,9 @@ "devDependencies": {

"vitest": "^3.2.0",
"@gasket/cjs": "^7.1.1",
"@gasket/core": "^7.7.2",
"@gasket/plugin-git": "^7.4.8",
"@gasket/plugin-logger": "^7.4.0",
"@gasket/plugin-metadata": "^7.5.8",
"@gasket/plugin-webpack": "^7.4.0",
"create-gasket-app": "^7.4.18"
"@gasket/core": "^8.0.0-next.1",
"@gasket/plugin-logger": "^8.0.0-next.1",
"@gasket/plugin-webpack": "^8.0.0-next.1",
"@gasket/plugin-metadata": "^8.0.0-next.1",
"create-gasket-app": "^8.0.0-next.1"
},
"scripts": {
"build": "gasket-cjs",
"lint": "eslint .",

@@ -59,0 +53,0 @@ "lint:fix": "pnpm run lint --fix",

# @gasket/plugin-docs
The plugin enables the **docs** command, which centralizes doc files for the
app's plugins, presets, and supporting modules.
app's plugins and supporting modules.

@@ -39,3 +39,3 @@ ## Installation

allows app developers to generate documentation for their Gasket projects. Only
those presets and plugins that are configured for a project, will be used to
those plugins that are configured for a project, will be used to
determine what documentation is available.

@@ -201,21 +201,2 @@

## Usage
### Presets
Presets can also set up custom docs. This is done by defining a `docsSetup`
property object on the module, which will be used to establish the `docsConfig`
for the preset.
```js
// gasket-preset-example.js
export default {
name: 'gasket-preset-example',
docsSetup: {
link: 'OTHER.md#go-here',
files: ['more-docs/**/*.*'],
}
}
```
## How it works

@@ -222,0 +203,0 @@

/// <reference types="@gasket/core" />
/// <reference types="@gasket/plugin-command" />
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, /**
* Get the docs command
* @type {import('@gasket/core').HookHandler<'commands'>}
*/ "default", {
enumerable: true,
get: function() {
return commands;
}
});
const _buildconfigset = /*#__PURE__*/ _interop_require_default(require("./utils/build-config-set.cjs"));
const _collatefiles = /*#__PURE__*/ _interop_require_default(require("./utils/collate-files.cjs"));
const _generateindex = /*#__PURE__*/ _interop_require_default(require("./utils/generate-index.cjs"));
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
function commands(gasket) {
return {
id: 'docs',
description: 'Generate docs for the app',
options: [
{
name: 'no-view',
description: 'View the docs after generating',
type: 'boolean'
}
],
action: async function({ view }) {
const docsConfigSet = await (0, _buildconfigset.default)(gasket);
await (0, _collatefiles.default)(docsConfigSet);
const generated = await gasket.exec('docsGenerate', docsConfigSet);
const docs = Array.isArray(generated) ? generated.flat().filter(Boolean) : [
generated
];
const guides = docs.filter((gen)=>gen && !gen.name.includes('template'));
const templates = docs.filter((gen)=>gen && gen.name.includes('template'));
// Ensure guides array exists before unshifting
if (!docsConfigSet.guides) {
docsConfigSet.guides = [];
}
docsConfigSet.guides.unshift(...guides);
// Ensure templates array exists before unshifting
if (!docsConfigSet.templates) {
docsConfigSet.templates = [];
}
docsConfigSet.templates.unshift(...templates);
await (0, _generateindex.default)(docsConfigSet);
if (view) {
await gasket.exec('docsView', docsConfigSet);
}
}
};
}
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, /**
* Configure lifecycle to set up SW config with defaults
* @type {import('@gasket/core').HookHandler<'configure'>}
*/ "default", {
enumerable: true,
get: function() {
return configure;
}
});
const _lodashdefaultsdeep = /*#__PURE__*/ _interop_require_default(require("lodash.defaultsdeep"));
const _constants = require("./utils/constants.cjs");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
function configure(gasket, baseConfig) {
const userConfig = baseConfig?.docs || {};
const docs = (0, _lodashdefaultsdeep.default)({}, userConfig, _constants.DEFAULT_CONFIG);
return {
...baseConfig,
docs
};
}
/// <reference types="create-gasket-app"/>
/// <reference types="@gasket/plugin-git" />
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, /** @type {import('@gasket/core').HookHandler<'create'>} */ "default", {
enumerable: true,
get: function() {
return create;
}
});
const _constants = require("./utils/constants.cjs");
const _packagejson = /*#__PURE__*/ _interop_require_default(require("../package.json"));
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
const { name, version, devDependencies } = _packagejson.default;
function create(gasket, { pkg, gasketConfig, gitignore, typescript, useDocs, readme }) {
if (!useDocs) return;
gitignore?.add(_constants.DEFAULT_CONFIG.outputDir, 'documentation');
gasketConfig.addCommand('docs', {
dynamicPlugins: [
`${name}`,
'@gasket/plugin-metadata'
]
});
pkg.add('devDependencies', {
[name]: `^${version}`,
'@gasket/plugin-metadata': devDependencies['@gasket/plugin-metadata']
});
const docsScript = typescript ? 'tsx gasket.ts docs' : 'node gasket.js docs';
pkg.add('scripts', {
docs: docsScript
});
readme.subHeading('Documentation').content('Generated docs will be placed in the `.docs` directory. To generate markdown documentation for the API, run:').codeBlock('{{{packageManager}}} run docs', 'bash');
}
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, /**
* Specify what files to copy and transform
* @type {import('@gasket/core').HookHandler<'docsSetup'>}
*/ "default", {
enumerable: true,
get: function() {
return docsSetup;
}
});
const _transforms = require("./utils/transforms.cjs");
function docsSetup() {
return {
link: 'README.md',
files: [
'README.md',
'docs/**/*',
'LICENSE.md'
],
transforms: [
_transforms.txGasketPackageLinks,
_transforms.txGasketUrlLinks,
_transforms.txAbsoluteLinks
]
};
}
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return _default;
}
});
const _create = /*#__PURE__*/ _interop_require_default(require("./create.cjs"));
const _configure = /*#__PURE__*/ _interop_require_default(require("./configure.cjs"));
const _commands = /*#__PURE__*/ _interop_require_default(require("./commands.cjs"));
const _metadata = /*#__PURE__*/ _interop_require_default(require("./metadata.cjs"));
const _docssetup = /*#__PURE__*/ _interop_require_default(require("./docs-setup.cjs"));
const _webpackconfig = /*#__PURE__*/ _interop_require_default(require("./webpack-config.cjs"));
const _prompt = /*#__PURE__*/ _interop_require_default(require("./prompt.cjs"));
const _packagejson = /*#__PURE__*/ _interop_require_default(require("../package.json"));
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
const { name, version, description } = _packagejson.default;
/** @type {import('@gasket/core').Plugin} */ const plugin = {
name,
version,
description,
hooks: {
configure: _configure.default,
create: _create.default,
commands: _commands.default,
metadata: _metadata.default,
prompt: _prompt.default,
docsSetup: _docssetup.default,
webpackConfig: _webpackconfig.default
}
};
const _default = plugin;
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, /**
* Attach additional metadata to pluginData
* @type {import('@gasket/core').HookHandler<'metadata'>}
*/ "default", {
enumerable: true,
get: function() {
return metadata;
}
});
const _constants = require("./utils/constants.cjs");
function metadata(gasket, meta) {
const { outputDir } = gasket.config.docs || _constants.DEFAULT_CONFIG;
return {
...meta,
commands: [
{
name: 'docs',
description: 'Generate docs for the app',
link: 'README.md#commands'
}
],
lifecycles: [
{
name: 'docsSetup',
description: 'Set up what docs are captured and how to transform them',
link: 'README.md#docsSetup',
command: 'docs',
method: 'execApply'
},
{
method: 'exec',
name: 'docsView',
description: 'View the collated documentation',
link: 'README.md#docsView',
command: 'docs',
after: 'docsSetup'
},
{
method: 'exec',
name: 'docsGenerate',
description: 'Generate graphs for display in documation',
link: 'README.md#docsGenerate',
command: 'docs',
after: 'docsSetup'
}
],
structures: [
{
name: outputDir + '/',
description: 'Output of the docs command',
link: 'README.md#options'
}
],
configurations: [
{
name: 'docs',
link: 'README.md#configuration',
description: 'Docs config object',
type: 'object'
},
{
name: 'docs.outputDir',
link: 'README.md#configuration',
description: 'Output directory for generated docs',
type: 'string',
default: '.docs'
}
]
};
}
{}
/** @type {import('@gasket/core').HookHandler<'prompt'>} */ Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return promptHook;
}
});
async function promptHook(gasket, context, { prompt }) {
if ('useDocs' in context) return context;
const { useDocs } = await prompt([
{
name: 'useDocs',
message: 'Do you want to use generated documentation?',
type: 'confirm'
}
]);
return Object.assign({}, context, {
useDocs
});
}
/// <reference types="@gasket/plugin-logger" />
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return _default;
}
});
const _configsetbuilder = /*#__PURE__*/ _interop_require_default(require("./config-set-builder.cjs"));
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
const defaults = _configsetbuilder.default.docsSetupDefault;
/** @typedef {import('@gasket/plugin-metadata').PluginData} PluginData */ /**
* Searches for the pluginData from metadata for a given plugin.
* If the plugin does not have a name, a unique match by hooks is attempted,
* otherwise a console warning is issued.
* @type {import('../internal.d.ts').findPluginData}
*/ function findPluginData(plugin, pluginsDatas, logger) {
const { name } = plugin;
// If the plugin does not have a name, try to find a unique hooks match
if (!name) {
const expectedHooks = Object.keys(plugin.hooks);
const results = pluginsDatas.filter((pluginData)=>{
const actual = Object.keys(pluginData.hooks);
return expectedHooks.length === actual.length && actual.every((k)=>expectedHooks.includes(k));
});
if (!results.length) {
logger.error(`Plugin missing name. Unable to find pluginData with hooks: ${JSON.stringify(expectedHooks)}`);
} else if (results.length > 1) {
logger.error(`Plugin missing name. More than one pluginData with hooks: ${JSON.stringify(expectedHooks)}`);
} else {
logger.info(`Determined plugin with missing name to be: ${results[0].name}`);
return results[0];
}
} else {
const results = pluginsDatas.find((p)=>p.name === name);
if (!results) {
logger.error(`Unable to find pluginData for: ${name}`);
}
return results;
}
}
/**
* Processes metadata and docsSetup hooks to assemble the set of docs configs
*
* Order of operations for building docsConfig:
* - docsSetup hooked plugins
* - metadata or docsSetup lifecycle file for app
* - metadata for plugins without docsSetup hook
* - metadata for modules not processed with plugins
* - metadata for presets
* @type {import('../internal.d.ts').buildDocsConfigSet}
*/ async function buildDocsConfigSet(gasket) {
const { logger } = gasket;
const metadata = await gasket.actions.getMetadata();
const { app: appData } = metadata;
const builder = new _configsetbuilder.default(gasket);
await gasket.execApply('docsSetup', async (plugin, handler)=>{
// If this is a lifecycle file, use it to modify the app-level docConfig
if (!plugin) {
const docsSetup = await handler({
defaults
});
return await builder.addApp(appData, docsSetup);
}
const pluginData = buildDocsConfigSet.findPluginData(plugin, metadata.plugins, logger);
if (pluginData) {
const docsSetup = await handler({
defaults
});
await builder.addPlugin(pluginData, docsSetup);
}
});
await builder.addApp(appData);
await builder.addPlugins(metadata.plugins);
await builder.addModules(metadata.modules);
await builder.addPresets(metadata.presets);
return builder.getConfigSet();
}
buildDocsConfigSet.findPluginData = findPluginData;
const _default = buildDocsConfigSet;
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return _default;
}
});
const _promises = require("fs/promises");
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
const _mkdirp = /*#__PURE__*/ _interop_require_default(require("mkdirp"));
const _rimraf = require("rimraf");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
/**
* Checks if a path is a file.
* @param {string} filePath - The path to check.
* @returns {Promise<boolean>} - True if it's a file, false otherwise.
*/ async function isFile(filePath) {
try {
const stats = await (0, _promises.stat)(filePath);
return stats.isFile();
} catch (err) {
console.error(`Error checking file type for: ${filePath}`, err);
return false;
}
}
/**
* Copies configured files for a module to the target output dir and applies any
* transforms.
* @param {import('../internal.d.ts').ModuleDocsConfig} moduleDocsConfig -
* @param {import('../internal.d.ts').DocsConfigSet} docsConfigSet - Configurations for
* collating docs
* @returns {Promise<void>} promise
* @private
*/ async function processModule(moduleDocsConfig, docsConfigSet) {
const { transforms: gTransforms = [] } = docsConfigSet;
const { sourceRoot, targetRoot, files, transforms = [] } = moduleDocsConfig;
const allTransforms = transforms.concat(gTransforms);
await Promise.all(files.map(async (filename)=>{
const source = _path.default.join(sourceRoot, filename);
const target = _path.default.join(targetRoot, filename);
// Check if the source is a file
const isSourceFile = await isFile(source);
if (!isSourceFile) {
console.warn(`Skipping non-file: ${source}`);
return;
}
await (0, _mkdirp.default)(_path.default.dirname(target));
// Process all files which meet transform tests (expects UTF-8 text files)
if (allTransforms.some((tx)=>tx.test.test(source))) {
let content = await (0, _promises.readFile)(source, 'utf8');
content = allTransforms.reduce((acc, tx)=>{
if (tx.test.test(source)) {
return tx.handler(acc, {
filename,
docsConfig: moduleDocsConfig,
docsConfigSet
});
}
return acc;
}, content);
return await (0, _promises.writeFile)(target, content);
}
// If file does not need transformation, just copy it
await (0, _promises.copyFile)(source, target);
}));
}
/**
* Collect and combine doc files in proper order.
* @param {import('../internal.d.ts').DocsConfigSet} docsConfigSet - Configurations for
* collating docs
* @returns {Promise<void>} promise
*/ async function collateFiles(docsConfigSet) {
const { docsRoot } = docsConfigSet;
await (0, _mkdirp.default)(docsRoot);
await (0, _rimraf.rimraf)(_path.default.join(docsRoot, '*'));
// Flatten the moduleDocsConfigs then generate
const flattened = [
'plugins',
'presets',
'modules'
].reduce((acc, type)=>acc.concat(docsConfigSet[type]), [
docsConfigSet.app
]);
await Promise.all(flattened.map((docsConfig)=>collateFiles.processModule(docsConfig, docsConfigSet)));
}
collateFiles.processModule = processModule;
const _default = collateFiles;
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return _default;
}
});
const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
const _lodashdefaultsdeep = /*#__PURE__*/ _interop_require_default(require("lodash.defaultsdeep"));
const _sorts = require("./sorts.cjs");
const _glob = require("glob");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
const isAppPlugin = /^\/.+\/plugins\//;
const isUrl = /^(https?:)?\/\//;
const isGasketScope = /^@gasket/;
const detailDocsHelpers = {
guides: {
sort: _sorts.sortGuides
},
commands: {
sort: _sorts.sortCommands
},
actions: {
sort: _sorts.sortActions
},
structures: {
sort: _sorts.sortStructures
},
lifecycles: {
sort: _sorts.sortLifecycles
},
configurations: {
sort: _sorts.sortConfigurations
}
};
/**
* Expected DetailDocsConfig types
* @type {string[]}
*/ const detailDocsTypes = Object.keys(detailDocsHelpers);
/**
* Defaults for when a docsSetup is not declared.
* @type {{link: string, files: Array}}
*/ const docsSetupDefault = Object.freeze({
link: 'README.md',
files: [
'docs/**/*.*',
'LICENSE.md'
]
});
/**
* Returns a filename with the hash removed
* @param {string} link - Filename that may have hash
* @returns {string} filename
*/ const noHash = (link)=>link && link.split('#')[0];
const getDocsSetupFromPkg = (moduleData)=>moduleData.package && moduleData.package.gasket && moduleData.package.gasket.docsSetup;
/**
* Util class for constructing the DocsConfigSet
*
* type {Class}
*/ class DocsConfigSetBuilder {
/**
* Look up all doc files for a module
* @type {import('../internal.d.ts')._findAllFiles}
*/ async _findAllFiles(moduleData, docsSetup, link, sourceRoot) {
if (!sourceRoot) return [];
const fileSet = new Set([]);
const tryAdd = (maybeFile)=>{
if (Boolean(maybeFile) && !isUrl.test(maybeFile) && typeof maybeFile === 'string') {
fileSet.add(noHash(maybeFile));
}
};
// Add file for main doc link if not a url
tryAdd(link);
// Add files for detail meta types docs
detailDocsTypes.forEach((metaType)=>{
const { metadata = {} } = moduleData;
if (metaType in metadata) {
metadata[metaType].forEach((o)=>{
tryAdd(o.link);
});
}
});
let { files = [] } = docsSetup;
files = Array.isArray(files) ? files : [
files
];
(await Promise.all(files.map(async (g)=>await (0, _glob.glob)(g, {
cwd: sourceRoot
})))).reduce((acc, cur)=>acc.concat(cur), []).forEach((file)=>tryAdd(file));
return Array.from(fileSet);
}
/**
* Divides global and local transforms from a docsSetup. Global transforms are
* added to the top-level set. Local transforms will be added to the module's
* docConfig.
* @type {import('../internal.d.ts')._segregateTransforms}
*/ _segregateTransforms(transforms) {
return transforms.reduce((acc, tx)=>{
if (tx.global) {
this._transforms.push(tx);
} else {
acc.push(tx);
}
return acc;
}, []);
}
/**
* Constructs the DocsConfig for a module based on its info and docsSetup
* @type {import('../internal.d.ts')._buildDocsConfig}
*/ async _buildDocsConfig(moduleData, docsSetup = {}, overrides = {}) {
const { name, version, // fallback to docsSetup or package.json content
link = docsSetup.link || moduleData.package && moduleData.package.homepage, description = docsSetup.description || moduleData.package && moduleData.package.description } = moduleData;
const { sourceRoot = moduleData.metadata.path } = overrides;
// Get only the local transforms
const transforms = this._segregateTransforms(docsSetup.transforms || []);
const files = await this._findAllFiles(moduleData, docsSetup, link, sourceRoot);
return {
...docsSetup,
name,
version,
description,
// safety-check: if link wants to be a file but was not found
link: !files.includes(noHash(link)) && !isUrl.test(link) ? null : link,
sourceRoot,
...overrides,
transforms,
files,
metadata: moduleData.metadata
};
}
/**
* Flattens all detail types from plugins' metadata. Add a from property with
* name of parent plugin.
* @type {import('../internal.d.ts')._flattenDetails}
*/ _flattenDetails(type) {
const arr = [];
this._modules.concat(this._presets).concat(this._plugins).forEach((moduleDoc)=>{
const { sourceRoot, targetRoot, name: from } = moduleDoc;
arr.push(...(moduleDoc.metadata[type] || []).map((docsSetup)=>({
...docsSetup,
from,
sourceRoot,
targetRoot
})));
});
return detailDocsHelpers[type].sort(arr);
}
/**
* Adds additional docsSetup for modules, merging duplicates with a first in
* wins approach. When a module is then add to be configured, a docSetup will
* be looked up from what's been added by plugins here.
* @type {import('../internal.d.ts')._addModuleDocsSetup}
*/ _addModuleDocsSetup(moduleDocsSetup) {
(0, _lodashdefaultsdeep.default)(this._moduleDocsSetups, moduleDocsSetup);
}
/**
* Add DocsConfig to the set for the App
* @type {import('../internal.d.ts').addApp}
*/ async addApp(moduleData, docsSetup) {
// If docsSetup is passed, stick with it. Or, see if gasket.docsSetup in
// package.json. Finally, fall back to defaults.
docsSetup = docsSetup || getDocsSetupFromPkg(moduleData) || docsSetupDefault;
if (!this._app) {
const targetRoot = _nodepath.default.join(this._docsRoot, 'app');
this._app = await this._buildDocsConfig(moduleData, docsSetup, {
targetRoot
});
}
}
/**
* Add DocsConfig to the set for a plugin
* @type {import('../internal.d.ts').addPlugin}
*/ async addPlugin(pluginData, docsSetup) {
if (this._plugins.find((p)=>p.metadata === pluginData.metadata)) return;
// If docsSetup is passed, stick with it. Or, see if gasket.docsSetup in
// package.json. Finally, fall back to defaults.
docsSetup = docsSetup || getDocsSetupFromPkg(pluginData) || docsSetupDefault;
let name = pluginData.name;
let { path: sourceRoot } = pluginData.metadata;
let targetRoot = _nodepath.default.join(this._docsRoot, 'plugins', ...name.split('/'));
if (isAppPlugin.test(pluginData.name)) {
name = _nodepath.default.relative(this._root, name);
sourceRoot = _nodepath.default.join(this._root);
targetRoot = _nodepath.default.join(this._docsRoot, 'app');
}
const { modules, ...setup } = docsSetup;
const docConfig = await this._buildDocsConfig(pluginData, setup, {
targetRoot,
name,
sourceRoot
});
this._plugins.push(docConfig);
if (modules) {
this._addModuleDocsSetup(modules);
}
}
/**
* Add DocsConfig to the set for multiple plugins if not already added
* @type {import('../internal.d.ts').addPlugins}
*/ async addPlugins(pluginDatas) {
await Promise.all(pluginDatas.map((p)=>this.addPlugin(p)));
}
/**
* Add DocsConfig to the set for a preset
* @type {import('../internal.d.ts').addPreset}
*/ async addPreset(presetData, docsSetup) {
if (this._presets.find((p)=>p.metadata === presetData.metadata)) return;
// If docsSetup is passed, stick with it. Otherwise, look up a docsSetup
// defined by preset. Or, see if gasket.docsSetup in package.json. Finally,
// fall back to defaults.
docsSetup = docsSetup || // @ts-expect-error
presetData.module && presetData.module.docsSetup || getDocsSetupFromPkg(presetData) || docsSetupDefault;
const { name } = presetData;
const targetRoot = _nodepath.default.join(this._docsRoot, 'presets', ...name.split('/'));
const docConfig = await this._buildDocsConfig(presetData, docsSetup, {
targetRoot
});
this._presets.push(docConfig);
}
/**
* Add DocsConfig to the set for multiple presets if not already added
* @type {import('../internal.d.ts').addPresets}
*/ async addPresets(presetDatas) {
await Promise.all(presetDatas.map((p)=>this.addPreset(p)));
}
/**
* Add DocsConfig to the set for a module
* @type {import('../internal.d.ts').addModule}
*/ async addModule(moduleData, docsSetup) {
if (this._modules.find((p)=>p.metadata === moduleData.metadata)) return;
// If docsSetup is passed, stick with it. Otherwise, look up a docsSetup
// added by plugins. Or, see if gasket.docsSetup in package.json. Finally,
// if this is a @gasket module fall back defaults.
docsSetup = docsSetup || this._moduleDocsSetups[moduleData.name] || getDocsSetupFromPkg(moduleData) || (isGasketScope.test(moduleData.name) ? docsSetupDefault : {});
const { name } = moduleData;
const targetRoot = _nodepath.default.join(this._docsRoot, 'modules', ...name.split('/'));
const docConfig = await this._buildDocsConfig(moduleData, docsSetup, {
targetRoot
});
this._modules.push(docConfig);
}
/**
* Add DocsConfig to the set for multiple modules if not already added
* @type {import('../internal.d.ts').addModules}
*/ async addModules(moduleDatas) {
await Promise.all(moduleDatas.map((p)=>this.addModule(p)));
}
/**
* Picks out properties to return as the config set
* @type {import('../internal.d.ts').getConfigSet}
*/ getConfigSet() {
const detailDocsConfigs = detailDocsTypes.reduce((acc, metaType)=>({
...acc,
[metaType]: this._flattenDetails(metaType)
}), {});
// sortByName
// - project, scoped, non-scoped
// structures dirs, files x hidden, non-hidden
return {
app: this._app,
plugins: (0, _sorts.sortModules)(this._plugins),
presets: (0, _sorts.sortModules)(this._presets),
modules: (0, _sorts.sortModules)(this._modules),
root: this._root,
docsRoot: this._docsRoot,
transforms: this._transforms,
...detailDocsConfigs
};
}
/**
* @param {import("@gasket/core").Gasket} gasket - Gasket API
*/ constructor(gasket){
this._plugins = [];
this._presets = [];
this._modules = [];
this._transforms = [];
const { root, docs: { outputDir } } = gasket.config;
this._root = root;
this._docsRoot = _nodepath.default.join(root, outputDir);
this._moduleDocsSetups = {};
}
}
DocsConfigSetBuilder.docsSetupDefault = docsSetupDefault;
const _default = DocsConfigSetBuilder;
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "DEFAULT_CONFIG", {
enumerable: true,
get: function() {
return DEFAULT_CONFIG;
}
});
const DEFAULT_CONFIG = {
outputDir: '.docs'
};
/* eslint-disable max-statements */ Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return _default;
}
});
const _promises = require("fs/promises");
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
const _markdowntable = /*#__PURE__*/ _interop_require_default(require("markdown-table"));
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
const isUrl = /^(https?:)?\/\//;
/**
* Generates the index README.md
* @param {import('../internal.d.ts').DocsConfigSet} docsConfigSet - Docs generation
* configs
* @returns {string} filename
*/ function generateContent(docsConfigSet) {
const { app: appDocs, docsRoot } = docsConfigSet;
const refMap = new Map();
let content = '';
let idx = 0;
/**
* Ensure that references are unique
* @param {string | number} ref - Reference to check
* @returns {string | number} unique reference
*/ function uniqueRef(ref) {
return refMap.has(ref) ? ++idx : ref;
}
const addLine = (text = '')=>{
content += text + '\n';
};
const addContent = (text = '')=>{
addLine(text);
addLine();
};
const addTable = (elems)=>{
addContent((0, _markdowntable.default)(elems));
};
const formatLink = (link, targetRoot)=>isUrl.test(link) ? link : _path.default.relative(docsRoot, _path.default.join(targetRoot, link));
addContent('<!-- generated by `gasket docs` -->');
addContent('# App');
addContent(`[${appDocs.name}] — ${appDocs.description}`);
refMap.set(appDocs.name, formatLink(appDocs.link, appDocs.targetRoot));
const addSection = (sectionTitle, sectionDesc, docs, { includeVersion = true, additionalHeaders = [], linkFallbacks = false } = {})=>{
if (!docs || !docs.length) return;
addContent(`## ${sectionTitle}`);
addContent(sectionDesc);
const headers = includeVersion ? [
'Name',
'Version',
'Description'
].concat(additionalHeaders) : [
'Name',
'Description'
].concat(additionalHeaders);
addTable([
headers,
...docs.map((moduleDoc)=>{
const additionalHeaderValues = additionalHeaders.map((h)=>moduleDoc[h.toLowerCase()]);
const { name, description, link, version, targetRoot, deprecated } = moduleDoc;
let itemName = deprecated ? `${name} (deprecated)` : name;
if (link || linkFallbacks) {
const ref = uniqueRef(itemName);
itemName = ref === name ? `[${itemName}]` : `[${itemName}][${ref}]`;
refMap.set(ref, formatLink(link || 'README.md', targetRoot));
}
return [
itemName,
...includeVersion ? [
version,
description,
...additionalHeaderValues
] : [
description,
...additionalHeaderValues
]
];
})
]);
};
addSection('Guides', 'Help and explanations docs', docsConfigSet.guides, {
includeVersion: false
});
addSection('Commands', 'Available commands', docsConfigSet.commands, {
includeVersion: false
});
addSection('Actions', 'Available actions', docsConfigSet.actions, {
includeVersion: false
});
addSection('Lifecycles', 'Available lifecycles', docsConfigSet.lifecycles, {
includeVersion: false
});
addSection('Structures', 'Available structure', docsConfigSet.structures, {
includeVersion: false
});
addSection('Presets', 'All configured presets', docsConfigSet.presets);
addSection('Templates', 'All configured templates', docsConfigSet.templates);
addSection('Plugins', 'All configured plugins', docsConfigSet.plugins);
addSection('Modules', 'Dependencies and supporting modules', docsConfigSet.modules);
addSection('Configurations', 'Available configuration options in the `gasket.js`', docsConfigSet.configurations, {
includeVersion: false,
additionalHeaders: [
'Type',
'Default'
],
linkFallbacks: true
});
addContent('<!-- LINKS -->');
refMap.forEach(function(value, key) {
addLine('[' + key + ']:' + value);
});
return content;
}
/**
* Generates the index README.md
* @param {import('../internal.d.ts').DocsConfigSet} docsConfigSet - Docs generation
* configs
* @returns {Promise<string>} filename
*/ async function generateIndex(docsConfigSet) {
const { docsRoot } = docsConfigSet;
const target = _path.default.join(docsRoot, 'README.md');
const content = await generateIndex.generateContent(docsConfigSet);
await (0, _promises.writeFile)(target, content);
return target;
}
generateIndex.generateContent = generateContent;
const _default = generateIndex;
/** @typedef {import('../internal.d.ts').ModuleDocsConfig} ModuleDocsConfig */ /**
* Sort an array of names alphabetically.
* @param {string} a - Name
* @param {string} b - Name
* @returns {number} comparison
*/ Object.defineProperty(exports, "__esModule", {
value: true
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
sortActions: function() {
return sortActions;
},
sortCommands: function() {
return sortCommands;
},
sortConfigurations: function() {
return sortConfigurations;
},
sortGuides: function() {
return sortGuides;
},
sortLifecycles: function() {
return sortLifecycles;
},
sortModules: function() {
return sortModules;
},
sortStructures: function() {
return sortStructures;
}
});
function alphaCompare(a, b) {
if (a === b) return 0;
return a > b ? 1 : -1;
}
/**
* Makes a sort compare function that looks up a names weight
* @param {Function} getWeight - Weight lookup callback
* @returns {Function} compare
*/ function makeWeightedCompare(getWeight) {
return function weightedCompare(a, b) {
const aWeight = getWeight(a);
const bWeight = getWeight(b);
if (aWeight !== bWeight) {
return aWeight > bWeight ? -1 : 1;
}
return alphaCompare(a, b);
};
}
/**
* Returns a function to sort an array of objects by a given key and compare
* function
* @param {string} key - Property to sort by
* @param {Function} compare - Sort function
* @returns {function(Array):Array} sorter
*/ function sortByKey(key, compare) {
return function sorter(arr) {
return arr.sort((a, b)=>compare(a[key], b[key]));
};
}
/**
* Determine the weight of a package name by scope, followed by type.
* - gasket scope > user scope > no scope > app plugins
* - presets > plugins > packages
* @param {string} name - Package name
* @returns {number} weight
*/ function getModuleWeight(name) {
//
// weight from scope
//
const isGasketScope = /^@gasket/;
const isUserScope = /^@/;
const isAppPlugin = /^plugins\//;
let weight = isGasketScope.test(name) && 300 || isUserScope.test(name) && 200 || !isAppPlugin.test(name) && 100 || 0;
//
// weight from type
//
const isPreset = /preset/;
const isPlugin = /plugin/;
weight += isPreset.test(name) && 20 || isPlugin.test(name) && 10 || 0;
return weight;
}
/**
* Sort an array of modules by name
* @type {function(ModuleDocsConfig[]): ModuleDocsConfig[]}
*/ const sortModules = sortByKey('name', makeWeightedCompare(getModuleWeight));
/**
* Use the same module weight by name, yet cli is always first
* @param {string} name - Package name
* @returns {number} weight
*/ function getGuideWeight(name) {
const isCli = /^@gasket\/cli/;
return isCli.test(name) && 1000 || getModuleWeight(name);
}
/**
* Sort an array of guides by the module their from
* @type {function(ModuleDocsConfig[]): ModuleDocsConfig[]}
*/ const sortGuides = sortByKey('from', makeWeightedCompare(getGuideWeight));
/**
* Determine the weight of a structure name by its type.
* - hidden dirs > dirs > hidden files > files
* @param {string} name - Structure name
* @returns {number} weight
*/ function getStructureWeight(name) {
const isDir = /\/$/;
const isHidden = /^\./;
return isDir.test(name) && isHidden.test(name) && 1000 || isDir.test(name) && 100 || isHidden.test(name) && 10 || 0;
}
/**
* Sort an array of structures by name
* @type {function(import('../internal.d.ts').DetailDocsConfig[]):
* import('../internal.d.ts').DetailDocsConfig[]}
*/ const sortStructures = sortByKey('name', makeWeightedCompare(getStructureWeight));
/**
* Sort an array of commands by name
* @type {function(import('../internal.d.ts').DetailDocsConfig[]):
* import('../internal.d.ts').DetailDocsConfig[]}
*/ const sortCommands = sortByKey('name', alphaCompare);
/**
* Sort an array of actions by name
* @type {function(import('../internal.d.ts').DetailDocsConfig[]):
* import('../internal.d.ts').DetailDocsConfig[]}
*/ const sortActions = sortByKey('name', alphaCompare);
/**
* Sort an array of lifeycles by name
* @type {function(import('../internal.d.ts').DetailDocsConfig[]):
* import('../internal.d.ts').DetailDocsConfig[]}
*/ // TODO (kinetifex): eventually sort by parent and order when doing graphing
// work
const sortLifecycles = sortByKey('name', alphaCompare);
/**
* Sort an array of configurations by name
* @type {function(import('../internal.d.ts').DetailDocsConfig[]):
* import('../internal.d.ts').DetailDocsConfig[]}
*/ const sortConfigurations = sortByKey('name', alphaCompare);
Object.defineProperty(exports, "__esModule", {
value: true
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
makeLinkTransform: function() {
return makeLinkTransform;
},
txAbsoluteLinks: function() {
return txAbsoluteLinks;
},
txGasketPackageLinks: function() {
return txGasketPackageLinks;
},
txGasketUrlLinks: function() {
return txGasketUrlLinks;
}
});
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
const isGasketScope = /^@gasket/;
const isMarkdown = /\.md$/;
const matchLink = /(]\s?:\s?|]\()([^)\s]+)(\)|\s?)/g;
const matchUrlLink = /^https:\/\/github.com\/godaddy\/gasket\/tree\/[^/]+\/packages\/(gasket-[^/]+)(\/.+)/;
/**
* Creates transform to modify links in markdown files
* @type {import('../internal.d.ts').LinkTransform}
*/ const makeLinkTransform = (callback)=>(content)=>{
return content.replace(matchLink, (match, p1, p2, p3)=>{
return [
p1,
callback(p2) || p2,
p3
].join('');
});
};
/**
* Updates gasket monorepo /packages/* links to be URLs to repo.
* @type {import('../internal.d.ts').DocsTransform}
*/ const txGasketPackageLinks = {
global: true,
test: /(node_modules\/@gasket|packages\/gasket-.+)\/.+\.md$/,
handler: function txGasketPackageLinks(content, { docsConfig }) {
// safety check that this is a @gasket scoped package.
if (!isGasketScope.test(docsConfig.name)) return content;
const tx = makeLinkTransform((link)=>{
if (/^\/packages\/gasket-.+/.test(link)) {
return [
'https://github.com/godaddy/gasket/tree/main',
link
].join('');
}
});
return tx(content);
}
};
/**
* Updates all gasket URL links to be relative to the collated docs if the
* target package has a docsConfig.
* @type {import('../internal.d.ts').DocsTransform}
*/ const txGasketUrlLinks = {
global: true,
test: isMarkdown,
/** @type {import('../internal.d.ts').DocsTransformHandler} */ handler: function txGasketUrlLinks(content, { filename, docsConfig, docsConfigSet }) {
const { targetRoot } = docsConfig;
const { modules, presets, plugins } = docsConfigSet;
const allModuleDocConfigs = modules.concat(plugins).concat(presets);
const tx = makeLinkTransform((link)=>{
return link.replace(matchUrlLink, (match, pkgMatch, fileMatch)=>{
const moduleName = pkgMatch.replace('gasket-', '@gasket/');
const tgtDocConfig = allModuleDocConfigs.find(function(m) {
if (typeof m === 'object') {
return m.name === moduleName;
}
});
if (tgtDocConfig) {
const dirWithLinkRef = _path.default.dirname(_path.default.join(targetRoot, filename));
const targetRootConfig = typeof tgtDocConfig === 'object' ? tgtDocConfig.targetRoot : tgtDocConfig;
const filePathOfLink = _path.default.join(targetRootConfig, fileMatch);
return _path.default.relative(dirWithLinkRef, filePathOfLink);
}
return match;
});
});
return tx(content);
}
};
/**
* Updates any absolute links in collated packages to be relative to the
* markdown file itself.
* @type {import('../internal.d.ts').DocsTransform}
*/ const txAbsoluteLinks = {
global: true,
test: isMarkdown,
handler: function txAbsoluteLinks(content, { filename, docsConfig }) {
const { targetRoot } = docsConfig;
const dirname = _path.default.dirname(_path.default.join(targetRoot, filename));
const tx = makeLinkTransform((link)=>{
return link.replace(/(^\/.+)(#.+)/, (match, p1, p2)=>{
const linkTarget = _path.default.join(targetRoot, p1);
const relLink = _path.default.relative(dirname, linkTarget);
return [
relLink,
p2
].join('');
});
});
return tx(content);
}
};
/// <reference types="@gasket/plugin-webpack" />
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, /** @type {import('@gasket/core').HookHandler<'webpackConfig'>} */ "default", {
enumerable: true,
get: function() {
return webpackConfigHook;
}
});
const _packagejson = /*#__PURE__*/ _interop_require_default(require("../package.json"));
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
const { name } = _packagejson.default;
function webpackConfigHook(gasket, webpackConfig) {
webpackConfig.resolve.alias[name] = false;
return webpackConfig;
}
/// <reference types="create-gasket-app"/>
/// <reference types="@gasket/plugin-git" />
import { DEFAULT_CONFIG } from './utils/constants.js';
import packageJson from '../package.json' with { type: 'json' };
const { name, version, devDependencies } = packageJson;
/** @type {import('@gasket/core').HookHandler<'create'>} */
export default function create(gasket, {
pkg,
gasketConfig,
gitignore,
typescript,
useDocs,
readme
}) {
if (!useDocs) return;
gitignore?.add(DEFAULT_CONFIG.outputDir, 'documentation');
gasketConfig.addCommand('docs', {
dynamicPlugins: [
`${name}`,
'@gasket/plugin-metadata'
]
});
pkg.add('devDependencies', {
[name]: `^${version}`,
'@gasket/plugin-metadata': devDependencies['@gasket/plugin-metadata']
});
const docsScript = typescript
? 'tsx gasket.ts docs'
: 'node gasket.js docs';
pkg.add('scripts', {
docs: docsScript
});
readme.subHeading('Documentation')
.content('Generated docs will be placed in the `.docs` directory. To generate markdown documentation for the API, run:')
.codeBlock('{{{packageManager}}} run docs', 'bash');
}
/** @type {import('@gasket/core').HookHandler<'prompt'>} */
export default async function promptHook(gasket, context, { prompt }) {
if ('useDocs' in context) return context;
const { useDocs } = await prompt([
{
name: 'useDocs',
message: 'Do you want to use generated documentation?',
type: 'confirm'
}
]);
return Object.assign({}, context, { useDocs });
}