lerna
Version:
Lerna is a fast, modern build system for managing and publishing multiple JavaScript/TypeScript packages from the same repository
450 lines (425 loc) • 14.3 kB
JavaScript
import {
Command,
ValidationError,
execSync,
npmConf,
slash
} from "./chunk-WC2B4V4E.js";
// libs/commands/create/src/index.ts
import dedent from "dedent";
import fs3 from "fs-extra";
import npa from "npm-package-arg";
import os from "os";
import pacote from "pacote";
import path3 from "path";
import { URL } from "url";
import initPackageJson from "init-package-json";
// libs/commands/create/src/lib/builtin-npmrc.ts
import fs from "fs-extra";
import path from "path";
function builtinNpmrc() {
let resolvedPath = "";
try {
resolvedPath = path.resolve(
fs.realpathSync(path.join(path.dirname(process.execPath), "npm")),
"../../npmrc"
);
} catch (err) {
}
return resolvedPath;
}
// libs/commands/create/src/lib/cat-file.ts
import fs2 from "fs-extra";
import path2 from "path";
function catFile(baseDir, fileName, content, opts = "utf8") {
return fs2.writeFile(path2.join(baseDir, fileName), `${content}
`, opts);
}
// libs/commands/create/src/index.ts
var LERNA_MODULE_DATA = path3.join(import.meta.dirname, "lib/lerna-module-data.cjs");
var DEFAULT_DESCRIPTION = [
"Now I\u2019m the model of a modern major general",
"The venerated Virginian veteran whose men are all",
"Lining up, to put me up on a pedestal",
"Writin\u2019 letters to relatives",
"Embellishin\u2019 my elegance and eloquence",
"But the elephant is in the room",
"The truth is in ya face when ya hear the British cannons go",
"BOOM"
].join(" / ");
function factory(argv) {
return new CreateCommand(argv);
}
var CreateCommand = class extends Command {
initialize() {
this._pkgByName = new Map(
Object.values(this.projectGraph.nodes).filter((n) => !!n.package).map((n) => [n.package.name, n])
);
const {
bin,
description = DEFAULT_DESCRIPTION,
esModule,
keywords,
license,
loc: pkgLocation,
name: pkgName,
yes
} = this.options;
const { name, scope } = npa(pkgName);
if (!name && pkgName.includes("/")) {
throw new ValidationError(
"ENOPKGNAME",
"Invalid package name. Use the <loc> positional to specify package directory.\nSee https://github.com/lerna/lerna/tree/main/libs/commands/create#usage for details."
);
}
this.dirName = scope ? name.split("/").pop() : name;
this.pkgName = name;
this.pkgsDir = this._getPackagesDir(pkgLocation);
this.camelName = this.dirName.replace(/[-_]+(.)?/g, (_, ch) => ch ? ch.toUpperCase() : "");
this.outDir = esModule ? "dist" : "lib";
this.targetDir = path3.resolve(this.pkgsDir, this.dirName);
this.binDir = path3.join(this.targetDir, "bin");
this.binFileName = bin === true ? this.dirName : bin;
this.libDir = path3.join(this.targetDir, esModule ? "src" : "lib");
this.libFileName = `${this.dirName}.js`;
this.testDir = path3.join(this.targetDir, "__tests__");
this.testFileName = `${this.dirName}.test.js`;
this.conf = npmConf({
description,
esModule,
keywords,
scope,
yes
});
this.conf.addFile(builtinNpmrc(), "builtin");
this.conf.set("init-main", `${this.outDir}/${this.libFileName}`);
if (esModule) {
this.conf.set("init-es-module", `${this.outDir}/${this.dirName}.module.js`);
}
if (!this.project.isIndependent()) {
this.conf.set("init-version", this.project.version);
}
if (this.conf.get("init-author-name") === "") {
this.conf.set("init-author-name", this.gitConfig("user.name"));
}
if (this.conf.get("init-author-email") === "") {
this.conf.set("init-author-email", this.gitConfig("user.email"));
}
if (license) {
this.conf.set("init-license", license);
}
if (this.options.private) {
this.conf.set("private", true);
}
if (this.options.loglevel === "silent") {
this.conf.set("silent", true);
}
if (this.binFileName) {
this.conf.set("bin", {
[this.binFileName]: `bin/${this.binFileName}`
});
}
this.conf.set("directories", {
lib: this.outDir,
test: "__tests__"
});
this.setFiles();
this.setHomepage();
this.setPublishConfig();
this.setRepository();
return Promise.resolve(this.setDependencies());
}
_getPackagesDir(pkgLocation) {
const packageParentDirs = this.project.packageParentDirs;
if (!pkgLocation) {
return packageParentDirs[0];
}
const normalizedPkgLocation = path3.resolve(this.project.rootPath, path3.normalize(pkgLocation)).toLowerCase();
const packageParentDirsLower = packageParentDirs.map((p) => p.toLowerCase());
const matchingPathIndex = packageParentDirsLower.findIndex((p) => p.indexOf(normalizedPkgLocation) > -1);
if (matchingPathIndex > -1) {
return packageParentDirs[matchingPathIndex];
}
throw new ValidationError(
"ENOPKGDIR",
`Location "${pkgLocation}" is not configured as a workspace directory.`
);
}
async execute() {
await fs3.mkdirp(this.libDir);
await fs3.mkdirp(this.testDir);
await Promise.all([this.writeReadme(), this.writeLibFile(), this.writeTestFile()]);
if (this.binFileName) {
await fs3.mkdirp(this.binDir);
await Promise.all([this.writeBinFile(), this.writeCliFile(), this.writeCliTest()]);
}
const data = await initPackageJson(this.targetDir, LERNA_MODULE_DATA, this.conf);
if (this.options.esModule) {
this.logger.notice(
"\u2714",
dedent`
Ensure '${path3.relative(".", this.pkgsDir)}/*/${this.outDir}' has been added to ./.gitignore
Ensure rollup or babel build scripts are in the root
`
);
}
this.logger.success(
"create",
`New package ${data.name} created at ./${path3.relative(".", this.targetDir)}`
);
}
gitConfig(prop) {
return execSync("git", ["config", "--get", prop], this.execOpts);
}
collectExternalVersions() {
const extVersions = /* @__PURE__ */ new Map();
const localNames = new Set(
Object.values(this.projectGraph.nodes).filter((n) => n.package).map((n) => n.package.name)
);
for (const node of Object.values(this.projectGraph.nodes)) {
if (!node.package) continue;
for (const depField of [
"dependencies",
"devDependencies",
"optionalDependencies",
"peerDependencies"
]) {
const deps = node.package.get(depField);
if (!deps) continue;
for (const [name, version] of Object.entries(deps)) {
if (!localNames.has(name)) {
extVersions.set(name, version);
}
}
}
}
return extVersions;
}
hasLocalRelativeFileSpec() {
for (const deps of Object.values(this.projectGraph.localPackageDependencies || {})) {
for (const dep of deps) {
if (dep.targetResolvedNpaResult?.type === "directory") {
return true;
}
}
}
}
resolveRelative(depNode) {
const relPath = path3.relative(this.targetDir, depNode.package.location);
const spec = npa.resolve(depNode.package.name, relPath, this.targetDir);
return slash(spec.saveSpec);
}
setDependencies() {
const inputs = new Set((this.options.dependencies || []).sort());
if (this.options.bin) {
inputs.add("yargs");
}
if (!inputs.size) {
return;
}
const exts = this.collectExternalVersions();
const localRelative = this.hasLocalRelativeFileSpec();
const savePrefix = this.conf.get("save-exact") ? "" : this.conf.get("save-prefix");
const pacoteOpts = this.conf.snapshot;
const decideVersion = (spec) => {
if (this._pkgByName.has(spec.name)) {
const depNode = this._pkgByName.get(spec.name);
if (localRelative) {
return this.resolveRelative(depNode);
}
return `${savePrefix}${depNode.package.version}`;
}
if (spec.type === "tag" && spec.fetchSpec === "latest" || spec.type === "range" && spec.fetchSpec === "*") {
if (exts.has(spec.name)) {
return exts.get(spec.name);
}
return pacote.manifest(spec, pacoteOpts).then((pkg) => `${savePrefix}${pkg.version}`);
}
if (spec.type === "git") {
throw new ValidationError("EGIT", "Do not use git dependencies");
}
return spec.rawSpec;
};
let chain = Promise.resolve();
chain = chain.then(async () => {
const dependencies = {};
for (const input of inputs) {
const spec = npa(input);
const version = await Promise.resolve(spec).then(decideVersion);
dependencies[spec.name] = version;
}
return dependencies;
});
chain = chain.then((dependencies) => {
this.conf.set("dependencies", dependencies);
});
return chain;
}
setFiles() {
const files = [this.outDir];
if (this.options.bin) {
files.unshift("bin");
}
this.conf.set("files", files);
}
setHomepage() {
let { homepage = this.project.manifest.get("homepage") } = this.options;
if (!homepage) {
return;
}
if (homepage.indexOf("http") !== 0) {
homepage = `http://${homepage}`;
}
const hurl = new URL(homepage);
const relativeTarget = path3.relative(this.project.rootPath, this.targetDir);
if (hurl.hostname.match("github")) {
hurl.pathname = path3.posix.join(hurl.pathname, "tree/main", relativeTarget);
hurl.hash = "readme";
} else if (!this.options.homepage) {
hurl.pathname = path3.posix.join(hurl.pathname, relativeTarget);
}
this.conf.set("homepage", hurl.href);
}
setPublishConfig() {
const scope = this.conf.get("scope");
const registry = this.options.registry || this.conf.get(`${scope}:registry`) || this.conf.get("registry");
const isPublicRegistry = registry === this.conf.root.registry;
const publishConfig = {};
if (scope && isPublicRegistry) {
publishConfig.access = this.options.access || "public";
}
if (registry && !isPublicRegistry) {
publishConfig.registry = registry;
}
if (this.options.tag) {
publishConfig.tag = this.options.tag;
}
if (Object.keys(publishConfig).length) {
this.conf.set("publishConfig", publishConfig);
}
}
setRepository() {
try {
const url = execSync("git", ["remote", "get-url", "origin"], this.execOpts);
this.conf.set("repository", url);
} catch (err) {
this.logger.warn("ENOREMOTE", "No git remote found, skipping repository property");
}
}
writeReadme() {
const readmeContent = dedent`
# \`${this.pkgName}\`
> ${this.options.description || "TODO: description"}
## Usage
\`\`\`
${this.options.bin ? dedent`
npm -g i ${this.pkgName}
${this.binFileName} --help
` : this.options.esModule ? `import ${this.camelName} from '${this.pkgName}';` : `const ${this.camelName} = require('${this.pkgName}');`}
// TODO: DEMONSTRATE API
\`\`\`
`;
return catFile(this.targetDir, "README.md", readmeContent);
}
writeLibFile() {
const libContent = this.options.esModule ? dedent`
export default function ${this.camelName}() {
return 'Hello from ${this.camelName}';
}
` : dedent`
'use strict';
module.exports = ${this.camelName};
function ${this.camelName}() {
return 'Hello from ${this.camelName}';
}
`;
return catFile(this.libDir, this.libFileName, libContent);
}
writeTestFile() {
const testContent = this.options.esModule ? dedent`
import ${this.camelName} from '../src/${this.dirName}.js';
import { strict as assert } from 'assert';
assert.strictEqual(${this.camelName}(), 'Hello from ${this.camelName}');
console.info('${this.camelName} tests passed');
` : dedent`
'use strict';
const ${this.camelName} = require('..');
const assert = require('assert').strict;
assert.strictEqual(${this.camelName}(), 'Hello from ${this.camelName}');
console.info('${this.camelName} tests passed');
`;
return catFile(this.testDir, this.testFileName, testContent);
}
writeCliFile() {
const cliFileName = "cli.js";
const cliContent = [
this.options.esModule ? dedent`
import factory from 'yargs/yargs';
import ${this.camelName} from './${this.dirName}';
export default cli;
` : dedent`
'use strict';
const factory = require('yargs/yargs');
const ${this.camelName} = require('./${this.dirName}');
module.exports = cli;
`,
"",
// blank line
dedent`
function cli(cwd) {
const parser = factory(null, cwd);
parser.alias('h', 'help');
parser.alias('v', 'version');
parser.usage(
"$0",
"TODO: description",
yargs => {
yargs.options({
// TODO: options
});
},
argv => ${this.camelName}(argv)
);
return parser;
}
`
].join(os.EOL);
return catFile(this.libDir, cliFileName, cliContent);
}
writeCliTest() {
const cliTestFileName = "cli.test.js";
const cliTestContent = [
this.options.esModule ? dedent`
import cli from '../src/cli';
` : dedent`
'use strict';
const cli = require('../lib/cli');
`,
"",
// blank line
dedent`
describe('${this.pkgName} cli', () => {
// const argv = cli(cwd).parse(['args']);
it('needs tests');
});
`
].join(os.EOL);
return catFile(this.testDir, cliTestFileName, cliTestContent);
}
writeBinFile() {
const binContent = dedent`
#!/usr/bin/env node
'use strict';
// eslint-disable-next-line no-unused-expressions
require('../${this.outDir}/cli')${this.options.esModule ? ".default" : ""}().parse(process.argv.slice(2));`;
return catFile(this.binDir, this.binFileName, binContent, { mode: 493 });
}
};
var commonJsExport = Object.assign(factory, { CreateCommand });
var src_default = commonJsExport;
export {
factory,
CreateCommand,
commonJsExport,
src_default
};