@pob/version 5.2.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -15
- package/README.md +147 -10
- package/dist/index-node.mjs +1482 -0
- package/dist/index-node.mjs.map +1 -0
- package/package.json +78 -19
- package/src/commands/version.test.ts +120 -0
- package/src/commands/version.ts +956 -0
- package/src/index.ts +13 -0
- package/src/missing-types.d.ts +10 -0
- package/src/pm/BunPackageManager.ts +29 -0
- package/src/pm/PackageManager.ts +90 -0
- package/src/pm/PnpmPackageManager.ts +23 -0
- package/src/pm/YarnPackageManager.ts +44 -0
- package/src/utils/UsageError.ts +6 -0
- package/src/utils/bumpTypeUtils.test.ts +65 -0
- package/src/utils/bumpTypeUtils.ts +111 -0
- package/src/utils/conventionalChangelogUtils.test.ts +762 -0
- package/src/utils/conventionalChangelogUtils.ts +104 -0
- package/src/utils/conventionalCommitConfigUtils.ts +19 -0
- package/src/utils/execCommand.ts +214 -0
- package/src/utils/generatorUtils.ts +11 -0
- package/src/utils/gitUtils.ts +193 -0
- package/src/utils/githubUtils.ts +120 -0
- package/src/utils/mapUtils.ts +7 -0
- package/src/utils/mapWorkspaces.test.ts +89 -0
- package/src/utils/packageDependenciesUtils.test.ts +25 -0
- package/src/utils/packageDependenciesUtils.ts +80 -0
- package/src/utils/packageUtils.test.ts +32 -0
- package/src/utils/packageUtils.ts +129 -0
- package/src/utils/updateChangelog.ts +39 -0
- package/src/utils/workspaceUtils.ts +142 -0
- package/.editorconfig +0 -13
- package/.eslintrc.json +0 -7
- package/CHANGELOG.md +0 -125
- package/generate-index.js +0 -13
- package/lib/index.d.ts +0 -8
- package/lib/index.js +0 -5
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { createProjectWorkspace, createWorkspace } from "./packageUtils.ts";
|
|
4
|
+
|
|
5
|
+
const toAbs = (p: string) => path.resolve(p);
|
|
6
|
+
|
|
7
|
+
const fixturesRoot = path.resolve(
|
|
8
|
+
new URL("../../__fixtures__", import.meta.url).pathname,
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
describe("mapWorkspaces - baseline checks", () => {
|
|
12
|
+
it("should map packages for array workspaces (yarn-monorepo)", async () => {
|
|
13
|
+
const root = path.join(fixturesRoot, "yarn-monorepo");
|
|
14
|
+
|
|
15
|
+
const rootWorkspace = await createWorkspace(root);
|
|
16
|
+
const project = await createProjectWorkspace(rootWorkspace);
|
|
17
|
+
const keys = [...project.children.keys()].toSorted();
|
|
18
|
+
|
|
19
|
+
expect(keys.length).toBe(5);
|
|
20
|
+
expect(keys).toContain("package-1");
|
|
21
|
+
expect(keys).toContain("package-2");
|
|
22
|
+
expect(project.children.get("package-1")!.cwd).toBe(
|
|
23
|
+
toAbs(path.join(root, "packages/package-1")),
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should map packages for object workspaces (monorepo-object-form)", async () => {
|
|
28
|
+
const root = path.join(fixturesRoot, "yarn-monorepo-object-form");
|
|
29
|
+
const rootWorkspace = await createWorkspace(root);
|
|
30
|
+
const project = await createProjectWorkspace(rootWorkspace);
|
|
31
|
+
const keys = [...project.children.keys()].toSorted();
|
|
32
|
+
expect(keys.length).toBe(5);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should map nested patterns and double star patterns", async () => {
|
|
36
|
+
const root = path.join(fixturesRoot, "yarn-monorepo-nested");
|
|
37
|
+
const rootWorkspace = await createWorkspace(root);
|
|
38
|
+
const project = await createProjectWorkspace(rootWorkspace);
|
|
39
|
+
const keys = [...project.children.keys()];
|
|
40
|
+
expect(keys).toContain("nested-package-1");
|
|
41
|
+
expect(keys).toContain("nested-package-2");
|
|
42
|
+
expect(keys).toContain("deep-nested-package");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should fallback to folder name when package.json has no name", async () => {
|
|
46
|
+
const root = path.join(fixturesRoot, "yarn-monorepo-no-name");
|
|
47
|
+
const rootWorkspace = await createWorkspace(root);
|
|
48
|
+
const project = await createProjectWorkspace(rootWorkspace);
|
|
49
|
+
const keys = [...project.children.keys()];
|
|
50
|
+
expect(keys).toContain("no-name-package");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should throw when duplicate workspace names exist (baseline behavior)", async () => {
|
|
54
|
+
const root = path.join(fixturesRoot, "yarn-monorepo-duplicate-names");
|
|
55
|
+
const rootWorkspace = await createWorkspace(root);
|
|
56
|
+
let thrown = false;
|
|
57
|
+
try {
|
|
58
|
+
await createProjectWorkspace(rootWorkspace);
|
|
59
|
+
} catch {
|
|
60
|
+
thrown = true;
|
|
61
|
+
}
|
|
62
|
+
expect(thrown).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should expose an error or skip invalid package.json (baseline behavior)", async () => {
|
|
66
|
+
const root = path.join(fixturesRoot, "yarn-monorepo-invalid-package-json");
|
|
67
|
+
const rootWorkspace = await createWorkspace(root);
|
|
68
|
+
// Ensure it throws or skips: we'll try to call createProjectWorkspace and assert throw
|
|
69
|
+
let thrown = false;
|
|
70
|
+
try {
|
|
71
|
+
await createProjectWorkspace(rootWorkspace);
|
|
72
|
+
} catch {
|
|
73
|
+
thrown = true;
|
|
74
|
+
}
|
|
75
|
+
// Accept either behavior as baseline: ensure we detect if it currently throws.
|
|
76
|
+
// We assert that at least it doesn't silently include the invalid package by name
|
|
77
|
+
// so if it didn't throw, ensure the invalid package isn't listed
|
|
78
|
+
if (!thrown) {
|
|
79
|
+
const project = await createProjectWorkspace(rootWorkspace);
|
|
80
|
+
const keys = [...project.children.keys()];
|
|
81
|
+
expect(keys).not.toContain("invalid-json-package");
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should have node:fs/promises glob available (engine requirement)", async () => {
|
|
86
|
+
const fsp = await import("node:fs/promises");
|
|
87
|
+
expect(typeof (fsp as any).glob).toBe("function");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { PackageDependencyDescriptorUtils } from "./packageDependenciesUtils.ts";
|
|
3
|
+
|
|
4
|
+
describe("PackageDependencyDescriptorUtils.parse", () => {
|
|
5
|
+
it.each([
|
|
6
|
+
[
|
|
7
|
+
["name", "1.1.1"],
|
|
8
|
+
{ key: "name", name: { name: "name" }, selector: "1.1.1" },
|
|
9
|
+
],
|
|
10
|
+
[
|
|
11
|
+
["name2", "npm:@scope/name@1.1.1"],
|
|
12
|
+
{
|
|
13
|
+
key: "name2",
|
|
14
|
+
name: { scope: "scope", name: "name" },
|
|
15
|
+
selector: "1.1.1",
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
] as const)('should parse "%s" to "%s"', ([k, v], expected) => {
|
|
19
|
+
expect(PackageDependencyDescriptorUtils.parse(k, v)).toStrictEqual(
|
|
20
|
+
expected,
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// describe(stringifyPackageDescriptor.name, () => {});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combination of an optional scope and name.
|
|
3
|
+
*
|
|
4
|
+
* eg `@npm/types`
|
|
5
|
+
*/
|
|
6
|
+
export interface PackageDescriptorName {
|
|
7
|
+
scope?: string;
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface DescriptorUtils<Descriptor> {
|
|
12
|
+
parse: (value: string) => Descriptor;
|
|
13
|
+
stringify: (descriptor: Descriptor) => string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const PackageDescriptorNameUtils: DescriptorUtils<PackageDescriptorName> =
|
|
17
|
+
{
|
|
18
|
+
parse: (value) => {
|
|
19
|
+
if (value.startsWith("@")) {
|
|
20
|
+
const [scope, name] = value.slice(1).split("/", 2);
|
|
21
|
+
if (!scope || !name) {
|
|
22
|
+
throw new Error(`Invalid package descriptor name: ${value}`);
|
|
23
|
+
}
|
|
24
|
+
return { scope, name };
|
|
25
|
+
}
|
|
26
|
+
return { name: value };
|
|
27
|
+
},
|
|
28
|
+
stringify: (descriptor) => {
|
|
29
|
+
return descriptor.scope === undefined
|
|
30
|
+
? descriptor.name
|
|
31
|
+
: `@${descriptor.scope}/${descriptor.name}`;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export interface PackageDependencyDescriptor {
|
|
36
|
+
key: string;
|
|
37
|
+
name: PackageDescriptorName;
|
|
38
|
+
selector: string; // can be npm tag or version or version range or git url or local folder path
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface PackageDependencyDescriptorUtils<
|
|
42
|
+
Descriptor = PackageDependencyDescriptor,
|
|
43
|
+
> {
|
|
44
|
+
make: (descriptor: Descriptor, selector: string) => Descriptor;
|
|
45
|
+
parse: (dependencyKey: string, dependencyValue: string) => Descriptor;
|
|
46
|
+
stringify: (descriptor: Descriptor) => [key: string, value: string];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const PackageDependencyDescriptorUtils: PackageDependencyDescriptorUtils =
|
|
50
|
+
{
|
|
51
|
+
make: (descriptor, selector) => {
|
|
52
|
+
return { key: descriptor.key, name: descriptor.name, selector };
|
|
53
|
+
},
|
|
54
|
+
parse: (dependencyKey, dependencyValue) => {
|
|
55
|
+
const [name, selector] = dependencyValue.startsWith("npm:")
|
|
56
|
+
? (() => {
|
|
57
|
+
const v = dependencyValue.slice("npm:".length);
|
|
58
|
+
if (!v.startsWith("@")) return v.split("@", 2) as [string, string];
|
|
59
|
+
const [packageNameWithoutFirstChar, selector] = v
|
|
60
|
+
.slice(1)
|
|
61
|
+
.split("@", 2);
|
|
62
|
+
if (!packageNameWithoutFirstChar || !selector) {
|
|
63
|
+
throw new Error(`Invalid package descriptor: ${dependencyValue}`);
|
|
64
|
+
}
|
|
65
|
+
return [`@${packageNameWithoutFirstChar}`, selector];
|
|
66
|
+
})()
|
|
67
|
+
: [dependencyKey, dependencyValue];
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
key: dependencyKey,
|
|
71
|
+
name: PackageDescriptorNameUtils.parse(name),
|
|
72
|
+
selector,
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
stringify: (descriptor) => {
|
|
76
|
+
return [descriptor.key, descriptor.selector];
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// export const PackageDependencySelectorUtils;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
createProjectWorkspace,
|
|
5
|
+
createWorkspace,
|
|
6
|
+
findRootWorkspace,
|
|
7
|
+
} from "./packageUtils.ts";
|
|
8
|
+
|
|
9
|
+
const fixturesRoot = path.resolve(
|
|
10
|
+
new URL("../../__fixtures__", import.meta.url).pathname,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
describe("packageUtils integration", () => {
|
|
14
|
+
it("findRootWorkspace finds root from subpackage path", async () => {
|
|
15
|
+
const subPath = path.join(fixturesRoot, "yarn-monorepo/packages/package-1");
|
|
16
|
+
const root = await findRootWorkspace(subPath);
|
|
17
|
+
expect(root).toBeTruthy();
|
|
18
|
+
expect(root!.pkg.name).toBe("yarn-monorepo");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("createProjectWorkspace returns children and correct relativeCwd", async () => {
|
|
22
|
+
const rootPath = path.join(fixturesRoot, "yarn-monorepo");
|
|
23
|
+
const rootWorkspace = await createWorkspace(rootPath);
|
|
24
|
+
const project = await createProjectWorkspace(rootWorkspace);
|
|
25
|
+
const children = [...project.children.values()];
|
|
26
|
+
expect(children.length).toBe(5);
|
|
27
|
+
// Check a sample relativeCwd
|
|
28
|
+
const pkg1 = project.children.get("package-1");
|
|
29
|
+
expect(pkg1).toBeTruthy();
|
|
30
|
+
expect(pkg1!.relativeCwd).toBe("packages/package-1");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
2
|
+
import fs, { glob } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import type { PackageJson } from "type-fest";
|
|
5
|
+
|
|
6
|
+
export interface Workspace {
|
|
7
|
+
readonly cwd: string;
|
|
8
|
+
readonly relativeCwd?: string;
|
|
9
|
+
readonly pkg: PackageJson;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ProjectWorkspace {
|
|
13
|
+
readonly root: Workspace;
|
|
14
|
+
readonly children: Map<string, Workspace>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function mapWorkspacesFromPkg({
|
|
18
|
+
cwd,
|
|
19
|
+
pkg,
|
|
20
|
+
}: {
|
|
21
|
+
cwd: string;
|
|
22
|
+
pkg: PackageJson;
|
|
23
|
+
}): Promise<Map<string, Workspace>> {
|
|
24
|
+
const workspacesPatterns: string[] = Array.isArray(pkg.workspaces)
|
|
25
|
+
? pkg.workspaces
|
|
26
|
+
: pkg.workspaces?.packages || [];
|
|
27
|
+
|
|
28
|
+
const map = new Map<string, Workspace>();
|
|
29
|
+
|
|
30
|
+
for (const pattern of workspacesPatterns) {
|
|
31
|
+
const normalized = pattern.endsWith("package.json")
|
|
32
|
+
? pattern
|
|
33
|
+
: `${pattern.replace(/\/*$/, "")}/package.json`;
|
|
34
|
+
|
|
35
|
+
const files = glob(normalized, { cwd });
|
|
36
|
+
for await (const file of files) {
|
|
37
|
+
const packageDir = path.join(cwd, path.dirname(file));
|
|
38
|
+
const packageJson: PackageJson = await readPkg(packageDir);
|
|
39
|
+
const workspaceName = packageJson.name || path.basename(packageDir);
|
|
40
|
+
if (map.has(workspaceName)) {
|
|
41
|
+
const existingWorkspace = map.get(workspaceName);
|
|
42
|
+
if (existingWorkspace && existingWorkspace.cwd !== packageDir) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`must not have multiple workspaces with the same name\npackage '${workspaceName}' has conflicts in the following paths:\n ${existingWorkspace.cwd}\n ${packageDir}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
// if it's the same package dir, ignore duplicate match
|
|
48
|
+
if (existingWorkspace?.cwd === packageDir) continue;
|
|
49
|
+
}
|
|
50
|
+
map.set(workspaceName, { cwd: packageDir, pkg: packageJson });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return map;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const createProjectWorkspace = async (
|
|
57
|
+
root: Workspace,
|
|
58
|
+
): Promise<ProjectWorkspace> => {
|
|
59
|
+
const map: Map<string, Workspace> = root.pkg.workspaces
|
|
60
|
+
? await mapWorkspacesFromPkg({ cwd: root.cwd, pkg: root.pkg })
|
|
61
|
+
: new Map();
|
|
62
|
+
|
|
63
|
+
const children = new Map<string, Workspace>(
|
|
64
|
+
[...map.entries()].map(([packageName, workspace]) => {
|
|
65
|
+
const w: Workspace = {
|
|
66
|
+
...workspace,
|
|
67
|
+
relativeCwd: path.relative(root.cwd, workspace.cwd),
|
|
68
|
+
};
|
|
69
|
+
return [packageName, w] as const;
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
return { root, children };
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const isAccessible = (path: string): Promise<boolean> =>
|
|
76
|
+
fs.access(path).then(
|
|
77
|
+
() => true,
|
|
78
|
+
() => false,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
export const findRootWorkspace = async (
|
|
82
|
+
cwd: string,
|
|
83
|
+
): Promise<Workspace | null> => {
|
|
84
|
+
let currentPath = cwd;
|
|
85
|
+
do {
|
|
86
|
+
const isRootIfOneOfThesePathsExists = await Promise.all([
|
|
87
|
+
isAccessible(path.join(currentPath, ".yarnrc.yml")),
|
|
88
|
+
isAccessible(path.join(currentPath, "yarn.lock")),
|
|
89
|
+
isAccessible(path.join(currentPath, "bun.lock")),
|
|
90
|
+
isAccessible(path.join(currentPath, "pnpm-lock.yaml")),
|
|
91
|
+
]);
|
|
92
|
+
if (isRootIfOneOfThesePathsExists.some(Boolean)) {
|
|
93
|
+
return createWorkspace(currentPath);
|
|
94
|
+
}
|
|
95
|
+
currentPath = path.dirname(currentPath);
|
|
96
|
+
} while (currentPath && currentPath !== "/");
|
|
97
|
+
return null;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const getPackageJsonPath = (cwd: string): string =>
|
|
101
|
+
path.join(cwd, "package.json");
|
|
102
|
+
|
|
103
|
+
export const createWorkspace = async (path: string): Promise<Workspace> => {
|
|
104
|
+
const pkg = await readPkg(path);
|
|
105
|
+
return { cwd: path, pkg };
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export async function writePkg(workspace: Workspace): Promise<void> {
|
|
109
|
+
const string = `${JSON.stringify(workspace.pkg, null, 2)}\n`;
|
|
110
|
+
await fs.writeFile(getPackageJsonPath(workspace.cwd), string, "utf8");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function readPkg(cwd: string): Promise<PackageJson> {
|
|
114
|
+
const packagePath = getPackageJsonPath(cwd);
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/use-unknown-in-catch-callback-variable
|
|
116
|
+
const pkg = await fs.readFile(packagePath, "utf8").catch((error: Error) => {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Failed to read "${packagePath}": ${error instanceof Error ? error.message : String(error)}`,
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(pkg);
|
|
123
|
+
} catch (error: unknown) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Failed to parse "${packagePath}": ${error instanceof Error ? error.message : String(error)}`,
|
|
126
|
+
{ cause: error },
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
const COMMIT_GUIDELINE =
|
|
4
|
+
"See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.";
|
|
5
|
+
|
|
6
|
+
const CHANGELOG_HEADER = [
|
|
7
|
+
"# Changelog",
|
|
8
|
+
"",
|
|
9
|
+
"All notable changes to this project will be documented in this file.",
|
|
10
|
+
COMMIT_GUIDELINE,
|
|
11
|
+
"",
|
|
12
|
+
].join("\n");
|
|
13
|
+
|
|
14
|
+
export const updateChangelogFile = async (
|
|
15
|
+
versionChangelogContent: string,
|
|
16
|
+
tagPrefix: string,
|
|
17
|
+
file = "CHANGELOG.md",
|
|
18
|
+
): Promise<void> => {
|
|
19
|
+
const START_OF_LAST_RELEASE_PATTERN = new RegExp(
|
|
20
|
+
`(^#+ \\[?(?:${tagPrefix})?\\d+\\.\\d+\\.\\d+|<a name=)`,
|
|
21
|
+
"m",
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
let oldContent = "";
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
oldContent = await fs.readFile(file, "utf8");
|
|
28
|
+
const oldContentStart = oldContent.search(START_OF_LAST_RELEASE_PATTERN);
|
|
29
|
+
// find the position of the last release and remove header:
|
|
30
|
+
if (oldContentStart !== -1) {
|
|
31
|
+
oldContent = oldContent.slice(Math.max(0, oldContentStart));
|
|
32
|
+
}
|
|
33
|
+
} catch {}
|
|
34
|
+
|
|
35
|
+
await fs.writeFile(
|
|
36
|
+
file,
|
|
37
|
+
`${CHANGELOG_HEADER}\n${versionChangelogContent}\n${oldContent}`,
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/* eslint-disable complexity */
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getMapArrayItemForKey } from "./mapUtils.ts";
|
|
4
|
+
import type { PackageDependencyDescriptor } from "./packageDependenciesUtils.ts";
|
|
5
|
+
import {
|
|
6
|
+
PackageDependencyDescriptorUtils,
|
|
7
|
+
PackageDescriptorNameUtils,
|
|
8
|
+
} from "./packageDependenciesUtils.ts";
|
|
9
|
+
import type { ProjectWorkspace, Workspace } from "./packageUtils.ts";
|
|
10
|
+
|
|
11
|
+
const allDependenciesTypes = [
|
|
12
|
+
"dependencies",
|
|
13
|
+
"devDependencies",
|
|
14
|
+
"peerDependencies",
|
|
15
|
+
] as const;
|
|
16
|
+
export type DependencyType = (typeof allDependenciesTypes)[number];
|
|
17
|
+
|
|
18
|
+
export const getWorkspaceName = (workspace: Workspace): string => {
|
|
19
|
+
return (
|
|
20
|
+
workspace.pkg.name ?? (path.basename(workspace.cwd) || "unnamed-workspace")
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type WorkspacesDependenciesMap = Map<
|
|
25
|
+
Workspace,
|
|
26
|
+
[Workspace, DependencyType, PackageDependencyDescriptor][]
|
|
27
|
+
>;
|
|
28
|
+
|
|
29
|
+
export const buildDependentsMaps = (
|
|
30
|
+
project: ProjectWorkspace,
|
|
31
|
+
): WorkspacesDependenciesMap => {
|
|
32
|
+
const dependentsMap: WorkspacesDependenciesMap = new Map<
|
|
33
|
+
Workspace,
|
|
34
|
+
[Workspace, DependencyType, PackageDependencyDescriptor][]
|
|
35
|
+
>();
|
|
36
|
+
|
|
37
|
+
// Note that we need to do this before applying the new versions,
|
|
38
|
+
// otherwise the `findWorkspacesByDescriptor` calls won't be able to
|
|
39
|
+
// resolve the workspaces anymore (because the workspace versions will
|
|
40
|
+
// have changed and won't match the outdated dependencies).
|
|
41
|
+
|
|
42
|
+
for (const [, dependent] of project.children) {
|
|
43
|
+
for (const set of allDependenciesTypes) {
|
|
44
|
+
for (const [dependencyKey, dependencyValue] of Object.entries(
|
|
45
|
+
dependent.pkg[set] || {},
|
|
46
|
+
)) {
|
|
47
|
+
if (!dependencyValue) continue;
|
|
48
|
+
const descriptor = PackageDependencyDescriptorUtils.parse(
|
|
49
|
+
dependencyKey,
|
|
50
|
+
dependencyValue,
|
|
51
|
+
);
|
|
52
|
+
const workspace = project.children.get(
|
|
53
|
+
PackageDescriptorNameUtils.stringify(descriptor.name),
|
|
54
|
+
);
|
|
55
|
+
if (!workspace) continue;
|
|
56
|
+
|
|
57
|
+
const dependents = getMapArrayItemForKey(dependentsMap, workspace);
|
|
58
|
+
dependents.push([dependent, set, descriptor]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return dependentsMap;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const buildDependenciesMaps = (
|
|
67
|
+
project: ProjectWorkspace,
|
|
68
|
+
): WorkspacesDependenciesMap => {
|
|
69
|
+
const dependenciesMap: WorkspacesDependenciesMap = new Map<
|
|
70
|
+
Workspace,
|
|
71
|
+
[Workspace, DependencyType, PackageDependencyDescriptor][]
|
|
72
|
+
>();
|
|
73
|
+
|
|
74
|
+
// Note that we need to do this before applying the new versions,
|
|
75
|
+
// otherwise the `findWorkspacesByDescriptor` calls won't be able to
|
|
76
|
+
// resolve the workspaces anymore (because the workspace versions will
|
|
77
|
+
// have changed and won't match the outdated dependencies).
|
|
78
|
+
|
|
79
|
+
for (const dependent of project.children.values()) {
|
|
80
|
+
for (const set of allDependenciesTypes) {
|
|
81
|
+
for (const [dependencyKey, dependencyValue] of Object.entries(
|
|
82
|
+
dependent.pkg[set] || {},
|
|
83
|
+
)) {
|
|
84
|
+
if (!dependencyValue) continue;
|
|
85
|
+
const descriptor = PackageDependencyDescriptorUtils.parse(
|
|
86
|
+
dependencyKey,
|
|
87
|
+
dependencyValue,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const workspace = project.children.get(
|
|
91
|
+
PackageDescriptorNameUtils.stringify(descriptor.name),
|
|
92
|
+
);
|
|
93
|
+
if (!workspace) continue;
|
|
94
|
+
|
|
95
|
+
const dependencies = getMapArrayItemForKey(dependenciesMap, dependent);
|
|
96
|
+
dependencies.push([workspace, set, descriptor]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return dependenciesMap;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const buildTopologicalOrderBatches = (
|
|
105
|
+
project: ProjectWorkspace,
|
|
106
|
+
dependenciesMap: WorkspacesDependenciesMap,
|
|
107
|
+
): Workspace[][] => {
|
|
108
|
+
const batches: Workspace[][] = [];
|
|
109
|
+
|
|
110
|
+
const added = new Set<Workspace>();
|
|
111
|
+
const toAdd = new Set<Workspace>([
|
|
112
|
+
project.root,
|
|
113
|
+
...project.children.values(),
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
while (toAdd.size > 0) {
|
|
117
|
+
const batch = new Set<Workspace>();
|
|
118
|
+
for (const workspace of toAdd) {
|
|
119
|
+
// make sure top level workspace is always in the last batch
|
|
120
|
+
if (workspace === project.root && toAdd.size > 1) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const dependencies = dependenciesMap.get(workspace);
|
|
125
|
+
if (!dependencies || dependencies.every((w) => added.has(w[0]))) {
|
|
126
|
+
batch.add(workspace);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const workspace of batch) {
|
|
131
|
+
added.add(workspace);
|
|
132
|
+
toAdd.delete(workspace);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (batch.size === 0) {
|
|
136
|
+
throw new Error("Circular dependency detected");
|
|
137
|
+
}
|
|
138
|
+
batches.push([...batch]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return batches;
|
|
142
|
+
};
|
package/.editorconfig
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# EditorConfig helps developers define and maintain consistent
|
|
2
|
-
# coding styles between different editors and IDEs
|
|
3
|
-
# http://editorconfig.org
|
|
4
|
-
|
|
5
|
-
root = true
|
|
6
|
-
|
|
7
|
-
[*]
|
|
8
|
-
indent_style = space
|
|
9
|
-
indent_size = 2
|
|
10
|
-
end_of_line = lf
|
|
11
|
-
charset = utf-8
|
|
12
|
-
trim_trailing_whitespace = true
|
|
13
|
-
insert_final_newline = true
|
package/.eslintrc.json
DELETED
package/CHANGELOG.md
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
# Change Log
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
-
|
|
6
|
-
# [5.2.0](https://github.com/christophehurpeau/pob/compare/@pob/version@5.1.0...@pob/version@5.2.0) (2020-02-07)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
### Bug Fixes
|
|
10
|
-
|
|
11
|
-
* format json without parser option ([765bfcf](https://github.com/christophehurpeau/pob/commit/765bfcfd70e90c208d8c6e95d5f27c477ace2328))
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
### Features
|
|
15
|
-
|
|
16
|
-
* eslint monorepo config ([d962a0e](https://github.com/christophehurpeau/pob/commit/d962a0e158001d039d72a7f1bababd699c782d58))
|
|
17
|
-
* update eslint pob config packages ([a8a808b](https://github.com/christophehurpeau/pob/commit/a8a808bedd34983165f981d069af59bdbf5487e2))
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# [5.1.0](https://github.com/christophehurpeau/pob/compare/@pob/version@5.0.1...@pob/version@5.1.0) (2020-02-02)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
### Features
|
|
27
|
-
|
|
28
|
-
* update eslint configs ([2add77e](https://github.com/christophehurpeau/pob/commit/2add77e6fa5d81065cec87007ae54b22921de3c5))
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
## [5.0.1](https://github.com/christophehurpeau/pob/compare/@pob/version@5.0.0...@pob/version@5.0.1) (2019-12-29)
|
|
35
|
-
|
|
36
|
-
**Note:** Version bump only for package @pob/version
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# [5.0.0](https://github.com/christophehurpeau/pob/compare/@pob/version@4.33.4...@pob/version@5.0.0) (2019-12-13)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
### Features
|
|
46
|
-
|
|
47
|
-
* add nullish-coalescing-operator and optional-chaining ([fca6e0b](https://github.com/christophehurpeau/pob/commit/fca6e0b6ddfd5b5851134fa0cdbb1eb56930c8d4))
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
### BREAKING CHANGES
|
|
51
|
-
|
|
52
|
-
* drop node 8
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
## [4.33.4](https://github.com/christophehurpeau/pob/compare/@pob/version@4.33.3...@pob/version@4.33.4) (2019-09-14)
|
|
59
|
-
|
|
60
|
-
**Note:** Version bump only for package @pob/version
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
## [4.33.3](https://github.com/christophehurpeau/pob/compare/@pob/version@4.33.2...@pob/version@4.33.3) (2019-09-13)
|
|
67
|
-
|
|
68
|
-
**Note:** Version bump only for package @pob/version
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
## [4.33.2](https://github.com/christophehurpeau/pob/compare/@pob/version@4.33.0...@pob/version@4.33.2) (2019-09-13)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
### Bug Fixes
|
|
78
|
-
|
|
79
|
-
* self update ([02173ba](https://github.com/christophehurpeau/pob/commit/02173ba))
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
## [4.33.1](https://github.com/christophehurpeau/pob/compare/@pob/version@4.33.0...@pob/version@4.33.1) (2019-08-30)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
### Bug Fixes
|
|
89
|
-
|
|
90
|
-
* self update ([02173ba](https://github.com/christophehurpeau/pob/commit/02173ba))
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
# [4.33.0](https://github.com/christophehurpeau/pob/compare/@pob/version@4.32.1...@pob/version@4.33.0) (2019-08-29)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
### Bug Fixes
|
|
100
|
-
|
|
101
|
-
* fix @pob/version package.json import ([e457914](https://github.com/christophehurpeau/pob/commit/e457914))
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
### Features
|
|
105
|
-
|
|
106
|
-
* workspaces for scoped packages ([c85fd52](https://github.com/christophehurpeau/pob/commit/c85fd52))
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
## [4.32.1](https://github.com/christophehurpeau/pob/compare/@pob/version@4.32.0...@pob/version@4.32.1) (2019-07-12)
|
|
113
|
-
|
|
114
|
-
**Note:** Version bump only for package @pob/version
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
# 4.32.0 (2019-07-12)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
### Features
|
|
124
|
-
|
|
125
|
-
* add @pob/version for future use ([3b74e5b](https://github.com/christophehurpeau/pob/commit/3b74e5b))
|