@fuman/build 0.0.13 → 0.0.15

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.
Files changed (41) hide show
  1. package/README.md +15 -15
  2. package/cli/commands/_utils.d.ts +0 -1
  3. package/cli/commands/_utils.js +2 -3
  4. package/cli/commands/bump-version.d.ts +2 -4
  5. package/cli/commands/bump-version.js +2 -6
  6. package/cli/commands/find-changed-packages.d.ts +0 -2
  7. package/cli/commands/find-changed-packages.js +0 -2
  8. package/cli/commands/gen-changelog.d.ts +0 -2
  9. package/cli/commands/gen-changelog.js +0 -2
  10. package/cli/commands/lint/config.d.ts +30 -0
  11. package/cli/commands/lint/index.d.ts +8 -0
  12. package/cli/commands/lint/index.js +47 -0
  13. package/cli/commands/lint/validate-workspace-deps.d.ts +41 -0
  14. package/cli/commands/lint/validate-workspace-deps.js +94 -0
  15. package/cli/commands/release.d.ts +2 -2
  16. package/cli/commands/release.js +29 -18
  17. package/cli/index.d.ts +1 -1
  18. package/config.d.ts +3 -0
  19. package/fuman-build.js +2 -2
  20. package/git/utils.d.ts +3 -1
  21. package/git/utils.js +10 -2
  22. package/index.js +3 -2
  23. package/jsr/_deno-directives.d.ts +1 -0
  24. package/jsr/_deno-directives.js +23 -0
  25. package/jsr/config.d.ts +44 -0
  26. package/jsr/generate-workspace.js +4 -0
  27. package/misc/_config.d.ts +1 -1
  28. package/misc/_config.js +4 -3
  29. package/package-json/process-package-json.d.ts +6 -0
  30. package/package-json/process-package-json.js +14 -1
  31. package/package-json/types.d.ts +10 -1
  32. package/package-json/types.js +1 -0
  33. package/package.json +5 -17
  34. package/versioning/bump-version.d.ts +3 -1
  35. package/versioning/bump-version.js +28 -5
  36. package/versioning/collect-files.d.ts +1 -1
  37. package/versioning/types.d.ts +15 -0
  38. package/vite/build-plugin.d.ts +1 -1
  39. package/vite/build-plugin.js +4 -1
  40. package/cli/commands/validate-workspace-deps.d.ts +0 -50
  41. package/cli/commands/validate-workspace-deps.js +0 -87
package/README.md CHANGED
@@ -16,12 +16,12 @@ this package works on some assumptions, allowing to abstract some of the complex
16
16
  - `.exports` and `.bin` fields in package.json are build entrypoints, e.g.:
17
17
  ```json
18
18
  {
19
- "exports": {
20
- ".": "./src/index.ts"
21
- },
22
- "bin": {
23
- "fuman-build": "./src/cli.ts"
24
- }
19
+ "exports": {
20
+ ".": "./src/index.ts"
21
+ },
22
+ "bin": {
23
+ "fuman-build": "./src/cli.ts"
24
+ }
25
25
  }
26
26
  - `.main`, `.module`, `.types`, `.browser` are explicitly **not** supported. for browser-specific code, you should make a secondary entrypoint/package instead of relying on bundle-time magic
27
27
  - package versioning is continous and semver-compliant (except "fixed version" releases, which all share the same version)
@@ -71,15 +71,15 @@ just put this in your `vite.config.js`:
71
71
  import { fumanBuild } from '@fuman/build/vite'
72
72
 
73
73
  export default {
74
- plugins: [
75
- fumanBuild({
76
- root: __dirname,
77
- // if you're using `vite-plugin-dts`, make sure to add this option,
78
- // otherwise additional entrypoints will not have proper types
79
- // (and DO NOT add this option to the dts plugin itself)
80
- insertTypesEntry: true,
81
- }),
82
- ],
74
+ plugins: [
75
+ fumanBuild({
76
+ root: __dirname,
77
+ // if you're using `vite-plugin-dts`, make sure to add this option,
78
+ // otherwise additional entrypoints will not have proper types
79
+ // (and DO NOT add this option to the dts plugin itself)
80
+ insertTypesEntry: true,
81
+ }),
82
+ ],
83
83
  }
84
84
  ```
85
85
 
@@ -3,6 +3,5 @@ import * as bc from '@drizzle-team/brocli';
3
3
  export { bc };
4
4
  export declare function loadConfig(params: {
5
5
  workspaceRoot: string;
6
- configPath?: string;
7
6
  require?: boolean;
8
7
  }): Promise<RootConfigObject | null>;
@@ -3,12 +3,11 @@ import { loadBuildConfig } from "../../misc/_config.js";
3
3
  async function loadConfig(params) {
4
4
  const {
5
5
  workspaceRoot,
6
- configPath = "build.config.js",
7
6
  require: require2 = true
8
7
  } = params;
9
- const config = await loadBuildConfig(workspaceRoot, configPath);
8
+ const config = await loadBuildConfig(workspaceRoot);
10
9
  if (!config && require2) {
11
- throw new Error(`Config not found: ${configPath}`);
10
+ throw new Error(`Config not found at ${workspaceRoot}`);
12
11
  }
13
12
  return config ?? null;
14
13
  }
@@ -3,15 +3,13 @@ import { bc } from './_utils.js';
3
3
  export declare function formatBumpVersionResult(result: BumpVersionResult, withReleaseType: boolean): string;
4
4
  export declare const bumpVersionCli: bc.Command<{
5
5
  root: string | undefined;
6
- config: string | undefined;
7
- type: "auto" | "major" | "minor" | "patch";
6
+ type: "major" | "minor" | "patch" | "auto";
8
7
  since: string | undefined;
9
8
  dryRun: boolean | undefined;
10
9
  quiet: boolean | undefined;
11
10
  }, {
12
11
  root: string | undefined;
13
- config: string | undefined;
14
- type: "auto" | "major" | "minor" | "patch";
12
+ type: "major" | "minor" | "patch" | "auto";
15
13
  since: string | undefined;
16
14
  dryRun: boolean | undefined;
17
15
  quiet: boolean | undefined;
@@ -13,8 +13,6 @@ function formatBumpVersionResult(result, withReleaseType) {
13
13
  lines.push(` has new features: ${result.hasFeatures}`);
14
14
  lines.push("");
15
15
  }
16
- lines.push(`next version: ${result.nextVersion}`);
17
- lines.push("");
18
16
  lines.push("list of changed packages:");
19
17
  for (const { package: pkg, because, prevVersion } of result.changedPackages) {
20
18
  let versionStr = prevVersion;
@@ -30,7 +28,6 @@ const bumpVersionCli = bc.command({
30
28
  desc: "bump the version of changed packages",
31
29
  options: {
32
30
  root: bc.string().desc("path to the root of the workspace (default: process.cwd())"),
33
- config: bc.string().desc("path to the build.config.js file"),
34
31
  type: bc.string().desc("override type of release (major, minor, patch) (default: auto-detect)").enum("major", "minor", "patch", "auto").default("auto"),
35
32
  since: bc.string().desc("starting point for the changelog (default: latest tag)"),
36
33
  dryRun: bc.boolean("dry-run").desc("whether to only print the detected changes without actually modifying anything"),
@@ -42,7 +39,6 @@ const bumpVersionCli = bc.command({
42
39
  const workspace = await collectPackageJsons(root);
43
40
  const config = await loadConfig({
44
41
  workspaceRoot: root,
45
- configPath: args.config,
46
42
  require: false
47
43
  });
48
44
  const since = args.since ?? await getLatestTag(root);
@@ -58,12 +54,12 @@ const bumpVersionCli = bc.command({
58
54
  dryRun: args.dryRun
59
55
  });
60
56
  if (args.quiet) {
61
- console.log(result.nextVersion);
57
+ console.log(JSON.stringify(result.nextVersions));
62
58
  } else {
63
59
  console.log(formatBumpVersionResult(result, releaseType == null));
64
60
  }
65
61
  if (isRunningInGithubActions()) {
66
- writeGithubActionsOutput("version", result.nextVersion);
62
+ writeGithubActionsOutput("versions", JSON.stringify(result.nextVersions));
67
63
  writeGithubActionsOutput("hasBreakingChanges", String(result.hasBreakingChanges));
68
64
  writeGithubActionsOutput("hasFeatures", String(result.hasFeatures));
69
65
  writeGithubActionsOutput("changedPackages", result.changedPackages.map((it) => it.package.json.name).join(","));
@@ -1,12 +1,10 @@
1
1
  import { bc } from './_utils.js';
2
2
  export declare const findChangedPackagesCli: bc.Command<{
3
3
  root: string | undefined;
4
- config: string | undefined;
5
4
  since: string | undefined;
6
5
  until: string | undefined;
7
6
  }, {
8
7
  root: string | undefined;
9
- config: string | undefined;
10
8
  since: string | undefined;
11
9
  until: string | undefined;
12
10
  }>;
@@ -9,7 +9,6 @@ const findChangedPackagesCli = bc.command({
9
9
  desc: "find changed packages between two commits, and output a comma-separated list of package names",
10
10
  options: {
11
11
  root: bc.string().desc("path to the root of the workspace (default: process.cwd())"),
12
- config: bc.string("config").desc("path to the build.config.js file"),
13
12
  since: bc.string().desc("starting point for the changelog (default: latest tag)"),
14
13
  until: bc.string().desc("ending point for the changelog (default: HEAD)")
15
14
  },
@@ -17,7 +16,6 @@ const findChangedPackagesCli = bc.command({
17
16
  const root = args.root ?? process.cwd();
18
17
  const config = await loadConfig({
19
18
  workspaceRoot: root,
20
- configPath: args.config,
21
19
  require: false
22
20
  });
23
21
  const since = args.since ?? await getLatestTag(root);
@@ -2,11 +2,9 @@ import { bc } from './_utils.js';
2
2
  export declare const generateChangelogCli: bc.Command<{
3
3
  only: string | undefined;
4
4
  root: string | undefined;
5
- config: string | undefined;
6
5
  since: string | undefined;
7
6
  }, {
8
7
  only: string | undefined;
9
8
  root: string | undefined;
10
- config: string | undefined;
11
9
  since: string | undefined;
12
10
  }>;
@@ -11,14 +11,12 @@ const generateChangelogCli = bc.command({
11
11
  options: {
12
12
  only: bc.string().desc("comma-separated list of packages to include"),
13
13
  root: bc.string().desc("path to the root of the workspace (default: process.cwd())"),
14
- config: bc.string("config").desc("path to the build.config.js file"),
15
14
  since: bc.string().desc("starting point for the changelog (default: latest tag)")
16
15
  },
17
16
  handler: async (args) => {
18
17
  const root = args.root ?? process.cwd();
19
18
  const config = await loadConfig({
20
19
  workspaceRoot: root,
21
- configPath: args.config,
22
20
  require: false
23
21
  });
24
22
  let workspacePackages = await collectPackageJsons(root, false);
@@ -0,0 +1,30 @@
1
+ import { WorkspacePackage } from '../../../package-json/collect-package-jsons.js';
2
+ export interface LintConfig {
3
+ /**
4
+ * whether to also validate the root package.json
5
+ *
6
+ * @default false
7
+ */
8
+ includeRoot?: boolean;
9
+ /** validation of external dependencies */
10
+ externalDependencies?: {
11
+ /** @default true */
12
+ enabled?: boolean;
13
+ /**
14
+ * whether to skip validating peer dependencies
15
+ *
16
+ * @default false
17
+ */
18
+ skipPeerDependencies?: boolean;
19
+ shouldSkip?: (ctx: {
20
+ /** package currently being validated */
21
+ package: WorkspacePackage;
22
+ /** name of the dependency */
23
+ dependency: string;
24
+ /** version of the dependency */
25
+ version: string;
26
+ /** field in which the dependency is declared */
27
+ field: 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies';
28
+ }) => boolean;
29
+ };
30
+ }
@@ -0,0 +1,8 @@
1
+ import { bc } from '../_utils.js';
2
+ export declare const lintCli: bc.Command<{
3
+ workspace: string | undefined;
4
+ noErrorCode: boolean;
5
+ }, {
6
+ workspace: string | undefined;
7
+ noErrorCode: boolean;
8
+ }>;
@@ -0,0 +1,47 @@
1
+ import process from "node:process";
2
+ import { loadConfig } from "../_utils.js";
3
+ import { validateWorkspaceDeps } from "./validate-workspace-deps.js";
4
+ import * as bc from "@drizzle-team/brocli";
5
+ const INTERNAL_MESSAGES = {
6
+ not_workspace_proto: "internal dependencies must be linked with workspace: protocol",
7
+ standalone_dep: "non-standalone packages cannot depend on standalone packages with workspace: protocol",
8
+ not_workspace_dep: "workspace: protocol is used to link to a package not found in the workspace"
9
+ };
10
+ const lintCli = bc.command({
11
+ name: "lint",
12
+ desc: "check the workspace for any issues",
13
+ options: {
14
+ workspace: bc.string().desc("path to the workspace root (default: cwd)"),
15
+ noErrorCode: bc.boolean("no-error-code").desc("whether to always exit with a zero code").default(false)
16
+ },
17
+ handler: async (args) => {
18
+ const workspaceRoot = args.workspace ?? process.cwd();
19
+ const config = (await loadConfig({ workspaceRoot }))?.lint;
20
+ const errors = await validateWorkspaceDeps({
21
+ workspaceRoot,
22
+ config
23
+ });
24
+ if (errors.length > 0) {
25
+ const externalErrors = errors.filter((it) => it.type === "external");
26
+ const internalErrors = errors.filter((it) => it.type === "internal");
27
+ if (externalErrors.length > 0) {
28
+ console.error("⚠️ Found external dependencies mismatch:");
29
+ for (const error of externalErrors) {
30
+ console.error(` - at ${error.package}: ${error.at} has ${error.dependency}@${error.version}, but ${error.otherPackage} has @${error.otherVersion}`);
31
+ }
32
+ }
33
+ if (internalErrors.length > 0) {
34
+ console.error("⚠️ Found issues with internal dependencies:");
35
+ for (const error of internalErrors) {
36
+ console.error(` - at ${error.package}, dependency ${error.dependency}: ${INTERNAL_MESSAGES[error.subtype]}`);
37
+ }
38
+ }
39
+ if (!args.noErrorCode) {
40
+ process.exit(1);
41
+ }
42
+ }
43
+ }
44
+ });
45
+ export {
46
+ lintCli
47
+ };
@@ -0,0 +1,41 @@
1
+ import { WorkspacePackage } from '../../../package-json/collect-package-jsons.js';
2
+ import { LintConfig } from './config.js';
3
+ /** information about a mismatch between a package and its dependencies */
4
+ export interface ExternalDepsError {
5
+ type: 'external';
6
+ /** package name where the mismatch occurred */
7
+ package: string;
8
+ /** name of the mismatched dependency */
9
+ dependency: string;
10
+ /** version of the mismatched dependency */
11
+ version: string;
12
+ /** type of dependency (`dependencies`, `devDependencies`, etc) */
13
+ at: string;
14
+ /** package name where the dependency was originally declared */
15
+ otherPackage: string;
16
+ /** original version of the dependency */
17
+ otherVersion: string;
18
+ }
19
+ export interface InternalDepsError {
20
+ type: 'internal';
21
+ /** package name where the mismatch occurred */
22
+ package: string;
23
+ /** name of the mismatched dependency */
24
+ dependency: string;
25
+ /**
26
+ * sub-type of the error
27
+ * - not_workspace_proto: internal dependencies must be linked with workspace: protocol
28
+ * - standalone_dep: non-standalone packages cannot depend on standalone packages
29
+ * - not_workspace_dep: `workspace:` protocol is used to link to a package not found in the workspace
30
+ */
31
+ subtype: 'not_workspace_proto' | 'standalone_dep' | 'not_workspace_dep';
32
+ }
33
+ export type WorkspaceDepsError = ExternalDepsError | InternalDepsError;
34
+ /**
35
+ * validate the external dependencies of a workspace
36
+ */
37
+ export declare function validateWorkspaceDeps(params: {
38
+ workspaceRoot: string | URL;
39
+ packages?: WorkspacePackage[];
40
+ config?: LintConfig;
41
+ }): Promise<WorkspaceDepsError[]>;
@@ -0,0 +1,94 @@
1
+ import { valid, satisfies, validRange, subset } from "semver";
2
+ import { collectPackageJsons } from "../../../package-json/collect-package-jsons.js";
3
+ async function validateWorkspaceDeps(params) {
4
+ const {
5
+ workspaceRoot,
6
+ config: {
7
+ includeRoot,
8
+ externalDependencies: {
9
+ enabled: externalDependenciesEnabled = true,
10
+ skipPeerDependencies: externalDependenciesSkipPeerDependencies = false,
11
+ shouldSkip: externalDependenciesShouldSkip
12
+ } = {}
13
+ } = {}
14
+ } = params;
15
+ const packages = params.packages ?? await collectPackageJsons(workspaceRoot, includeRoot);
16
+ const packagesMap = new Map(packages.map((pj) => [pj.json.name, pj]));
17
+ const versions = {};
18
+ const errors = [];
19
+ for (const pkg of packages) {
20
+ const pj = pkg.json;
21
+ if (pj.name === void 0) {
22
+ throw new Error("package.json without name is not supported");
23
+ }
24
+ for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
25
+ const deps = pj[field];
26
+ if (!deps) continue;
27
+ for (const [name, version] of Object.entries(deps)) {
28
+ if (packagesMap.has(name)) {
29
+ const otherPkg = packagesMap.get(name);
30
+ const otherPkgStandalone = Boolean(otherPkg?.json.fuman?.standalone);
31
+ if (!otherPkgStandalone && !version.startsWith("workspace:")) {
32
+ errors.push({
33
+ type: "internal",
34
+ package: pj.name,
35
+ dependency: name,
36
+ subtype: "not_workspace_proto"
37
+ });
38
+ continue;
39
+ }
40
+ if (!pj.fuman?.standalone && otherPkgStandalone && version.startsWith("workspace:")) {
41
+ errors.push({
42
+ type: "internal",
43
+ package: pj.name,
44
+ dependency: name,
45
+ subtype: "standalone_dep"
46
+ });
47
+ }
48
+ continue;
49
+ }
50
+ if (version.startsWith("workspace:")) {
51
+ errors.push({
52
+ type: "internal",
53
+ package: pj.name,
54
+ dependency: name,
55
+ subtype: "not_workspace_dep"
56
+ });
57
+ continue;
58
+ }
59
+ if (!externalDependenciesEnabled) continue;
60
+ if (field === "peerDependencies" && externalDependenciesSkipPeerDependencies) continue;
61
+ if (externalDependenciesShouldSkip?.({ package: pkg, dependency: name, version, field })) continue;
62
+ if (versions[name] === void 0) {
63
+ versions[name] = {};
64
+ }
65
+ for (const [pkgName, pkgDepVersions] of Object.entries(versions[name])) {
66
+ let ok = true;
67
+ if (pkgDepVersions.match(/^(?:https?:\/\/|catalog:)/)) {
68
+ ok = version === pkgDepVersions;
69
+ } else if (valid(version) != null) {
70
+ ok = satisfies(version, pkgDepVersions);
71
+ } else if (validRange(version) != null) {
72
+ ok = subset(pkgDepVersions, version);
73
+ }
74
+ if (!ok) {
75
+ errors.push({
76
+ type: "external",
77
+ package: pj.name,
78
+ dependency: name,
79
+ version,
80
+ at: field,
81
+ otherPackage: pkgName,
82
+ otherVersion: pkgDepVersions
83
+ });
84
+ }
85
+ }
86
+ versions[name][pj.name] = version;
87
+ }
88
+ }
89
+ }
90
+ return errors;
91
+ }
92
+ export {
93
+ validateWorkspaceDeps
94
+ };
@@ -1,6 +1,6 @@
1
1
  import { bc } from './_utils.js';
2
2
  export declare const releaseCli: bc.Command<{
3
- kind: "auto" | "major" | "minor" | "patch";
3
+ kind: "major" | "minor" | "patch" | "auto";
4
4
  withGithubRelease: boolean;
5
5
  gitExtraOrigins: string | undefined;
6
6
  githubToken: string | undefined;
@@ -18,7 +18,7 @@ export declare const releaseCli: bc.Command<{
18
18
  npmRegistry: string | undefined;
19
19
  dryRun: boolean | undefined;
20
20
  }, {
21
- kind: "auto" | "major" | "minor" | "patch";
21
+ kind: "major" | "minor" | "patch" | "auto";
22
22
  withGithubRelease: boolean;
23
23
  gitExtraOrigins: string | undefined;
24
24
  githubToken: string | undefined;
@@ -39,10 +39,9 @@ const releaseCli = bc.command({
39
39
  dryRun: bc.boolean("dry-run").desc("whether to skip publishing and only print what is going to happen")
40
40
  },
41
41
  handler: async (args) => {
42
- const root = process.cwd();
42
+ const root = process.env.FUMAN_ROOT ?? process.cwd();
43
43
  const config = await loadConfig({
44
44
  workspaceRoot: root,
45
- configPath: "build.config.js",
46
45
  require: false
47
46
  });
48
47
  const workspaceWithRoot = await collectPackageJsons(root, true);
@@ -79,28 +78,40 @@ const releaseCli = bc.command({
79
78
  } else {
80
79
  changedPackages = workspace;
81
80
  }
81
+ const taggingSchema = config?.versioning?.taggingSchema ?? "semver";
82
82
  let tagName;
83
- if (!bumpVersionResult) {
84
- const versions = sort(workspace.map((pkg) => asNonNull(pkg.json.version)));
83
+ if (taggingSchema === "semver") {
84
+ const versions = sort(
85
+ workspace.filter((pkg) => !pkg.json.fuman?.ownVersioning && !pkg.json.fuman?.standalone).map((pkg) => asNonNull(pkg.json.version))
86
+ );
85
87
  tagName = `v${versions[versions.length - 1]}`;
86
- } else {
87
- tagName = `v${bumpVersionResult.nextVersion}`;
88
- }
89
- if (await gitTagExists(tagName, root)) {
90
- console.log(`❗ tag ${tagName} already exists. did the previous release complete successfully?`);
91
- console.log("❗ if so, please verify versions in package.json and try again");
92
- if (!args.dryRun) {
93
- process.exit(1);
88
+ if (await gitTagExists(tagName, root)) {
89
+ console.log(`❗ tag ${tagName} already exists. did the previous release complete successfully?`);
90
+ console.log("❗ if so, please verify versions in package.json and try again");
91
+ if (!args.dryRun) {
92
+ process.exit(1);
93
+ }
94
94
  }
95
- }
96
- if (!changedPackages.some((pkg) => pkg.json.version === tagName.replace(/^v/, ""))) {
97
- console.log(`❗ tag ${tagName} does not match any of the package versions. did the previous release complete successfully?`);
98
- console.log("❗ if so, please verify versions in package.json, tag the commit release and try again");
99
- if (!args.dryRun) {
100
- process.exit(1);
95
+ if (!changedPackages.some((pkg) => pkg.json.version === tagName.replace(/^v/, ""))) {
96
+ console.log(`❗ tag ${tagName} does not match any of the package versions. did the previous release complete successfully?`);
97
+ console.log("❗ if so, please verify versions in package.json, tag the commit release and try again");
98
+ if (!args.dryRun) {
99
+ process.exit(1);
100
+ }
101
101
  }
102
+ } else if (taggingSchema === "date") {
103
+ const date = /* @__PURE__ */ new Date();
104
+ const tagNamePrefix = `v${date.getFullYear()}.${(date.getMonth() + 1).toString().padStart(2, "0")}.${date.getDate().toString().padStart(2, "0")}`;
105
+ let currentLetter = "a";
106
+ do {
107
+ tagName = `${tagNamePrefix}${currentLetter}`;
108
+ currentLetter = String.fromCharCode(currentLetter.charCodeAt(0) + 1);
109
+ } while (await gitTagExists(tagName, root));
110
+ } else {
111
+ throw new Error(`Unknown tagging schema: ${taggingSchema}`);
102
112
  }
103
113
  console.log("");
114
+ console.log("🚀 next tag:", tagName);
104
115
  console.log("📝 generating changelog...");
105
116
  const changelog = prevTag != null ? await generateChangelog({
106
117
  workspace: changedPackages,
package/cli/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { buildPackage } from './commands/build.js';
2
2
  export { generateDocs } from './commands/docs.js';
3
3
  export { generateDepsGraph } from './commands/gen-deps-graph.js';
4
- export { validateWorkspaceDeps, type WorkspaceDepsError } from './commands/validate-workspace-deps.js';
4
+ export { validateWorkspaceDeps, type WorkspaceDepsError } from './commands/lint/validate-workspace-deps.js';
package/config.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { AnyToNever } from '@fuman/utils';
2
2
  import { TypeDocOptions } from 'typedoc';
3
+ import { LintConfig } from './cli/commands/lint/config.js';
3
4
  import { JsrConfig } from './jsr/config.js';
4
5
  import { PackageJson } from './package-json/types.js';
5
6
  import { VersioningOptions } from './versioning/types.js';
@@ -49,6 +50,8 @@ export interface RootConfigObject {
49
50
  */
50
51
  excludePackages?: string[];
51
52
  }>;
53
+ /** `lint` command configuration */
54
+ lint?: LintConfig;
52
55
  }
53
56
  /** root configuration (either an object or a function that returns an object) */
54
57
  export type RootConfig = RootConfigObject | (() => RootConfigObject);
package/fuman-build.js CHANGED
@@ -9,11 +9,11 @@ import { findChangedPackagesCli } from "./cli/commands/find-changed-packages.js"
9
9
  import { generateChangelogCli } from "./cli/commands/gen-changelog.js";
10
10
  import { generateDepsGraphCli } from "./cli/commands/gen-deps-graph.js";
11
11
  import { jsrCli } from "./cli/commands/jsr.js";
12
+ import { lintCli } from "./cli/commands/lint/index.js";
12
13
  import { publishPackagesCli } from "./cli/commands/publish.js";
13
14
  import { releaseCli } from "./cli/commands/release.js";
14
- import { validateWorkspaceDepsCli } from "./cli/commands/validate-workspace-deps.js";
15
15
  await bc.run([
16
- validateWorkspaceDepsCli,
16
+ lintCli,
17
17
  generateDepsGraphCli,
18
18
  buildPackageCli,
19
19
  jsrCli,
package/git/utils.d.ts CHANGED
@@ -70,7 +70,9 @@ export interface CommitInfo {
70
70
  */
71
71
  export declare function getCommitsBetween(params: {
72
72
  /** starting point for the diff */
73
- since: string;
73
+ since?: string;
74
+ /** only include commits that modified these files */
75
+ files?: string[];
74
76
  /**
75
77
  * ending point for the diff
76
78
  * @default 'HEAD'
package/git/utils.js CHANGED
@@ -50,13 +50,21 @@ async function findChangedFiles(params) {
50
50
  return files;
51
51
  }
52
52
  async function getCommitsBetween(params) {
53
- const { since, until = "HEAD", cwd } = params;
53
+ const { since, until = "HEAD", cwd, files } = params;
54
54
  const delim = `---${randomUUID()}---`;
55
- const res = await exec(["git", "log", `--pretty=format:%H %s%n%an%n%ae%n%aI%n%cn%n%ce%n%cI%n%b%n${delim}`, `${since}..${until}`], {
55
+ const res = await exec([
56
+ "git",
57
+ "log",
58
+ `--pretty=format:%H %s%n%an%n%ae%n%aI%n%cn%n%ce%n%cI%n%b%n${delim}`,
59
+ since != null ? `${since}..${until}` : until,
60
+ // eslint-disable-next-line ts/strict-boolean-expressions
61
+ ...files?.length ? ["--", ...files] : []
62
+ ], {
56
63
  cwd,
57
64
  throwOnError: true
58
65
  });
59
66
  const lines = res.stdout.trim().split("\n");
67
+ if (lines.length === 1 && lines[0] === "") return [];
60
68
  const items = [];
61
69
  let current = null;
62
70
  for (let i = 0; i < lines.length; i++) {
package/index.js CHANGED
@@ -2,7 +2,7 @@ import { getGithubActionsInput, isRunningInGithubActions, writeGithubActionsOutp
2
2
  import { buildPackage } from "./cli/commands/build.js";
3
3
  import { generateDocs } from "./cli/commands/docs.js";
4
4
  import { generateDepsGraph } from "./cli/commands/gen-deps-graph.js";
5
- import { validateWorkspaceDeps } from "./cli/commands/validate-workspace-deps.js";
5
+ import { validateWorkspaceDeps } from "./cli/commands/lint/validate-workspace-deps.js";
6
6
  import { findChangedFiles, getCommitsBetween, getCurrentBranch, getCurrentCommit, getFirstCommit, getLatestTag, gitTagExists, parseConventionalCommit } from "./git/utils.js";
7
7
  import { ExecError, exec } from "./misc/exec.js";
8
8
  import { normalizeFilePath } from "./misc/path.js";
@@ -12,7 +12,7 @@ import { NPM_PACKAGE_NAME_REGEX, npmCheckVersion } from "./npm/npm-api.js";
12
12
  import { collectPackageJsons, filterPackageJsonsForPublish } from "./package-json/collect-package-jsons.js";
13
13
  import { findPackageJson } from "./package-json/find-package-json.js";
14
14
  import { parsePackageJson, parsePackageJsonFile, parsePackageJsonFromDir, parseWorkspaceRootPackageJson } from "./package-json/parse.js";
15
- import { processPackageJson } from "./package-json/process-package-json.js";
15
+ import { processPackageJson, removeCommonjsExports } from "./package-json/process-package-json.js";
16
16
  import { PackageJsonSchema } from "./package-json/types.js";
17
17
  import { bumpVersion } from "./versioning/bump-version.js";
18
18
  import { findProjectChangedFiles, findProjectChangedPackages } from "./versioning/collect-files.js";
@@ -52,6 +52,7 @@ export {
52
52
  parsePackageJsonFromDir,
53
53
  parseWorkspaceRootPackageJson,
54
54
  processPackageJson,
55
+ removeCommonjsExports,
55
56
  sortWorkspaceByPublishOrder,
56
57
  validateWorkspaceDeps,
57
58
  writeGithubActionsOutput
@@ -0,0 +1 @@
1
+ export declare function applyDenoDirectives(code: string): string;
@@ -0,0 +1,23 @@
1
+ import { asNonNull } from "@fuman/utils";
2
+ function applyDenoDirectives(code) {
3
+ if (!code.match("<deno-(insert|remove|tsignore)>")) return code;
4
+ let insertContent = code.match(/\/\/\s*<deno-insert>(.*?)<\/deno-insert>/s);
5
+ while (insertContent) {
6
+ code = code.slice(0, insertContent.index) + insertContent[1].replace(/\/\/\s*/g, "") + code.slice(asNonNull(insertContent.index) + insertContent[0].length);
7
+ insertContent = code.match(/\/\/\s*<deno-insert>(.*?)<\/deno-insert>/s);
8
+ }
9
+ let removeContent = code.match(/\/\/\s*<deno-remove>(.*?)<\/deno-remove>/s);
10
+ while (removeContent) {
11
+ code = code.slice(0, removeContent.index) + code.slice(asNonNull(removeContent.index) + removeContent[0].length);
12
+ removeContent = code.match(/\/\/\s*<deno-remove>(.*?)<\/deno-remove>/s);
13
+ }
14
+ let tsIgnoreContent = code.match(/\/\/\s*<deno-tsignore>/);
15
+ while (tsIgnoreContent) {
16
+ code = `${code.slice(0, tsIgnoreContent.index)}/* @ts-ignore */${code.slice(asNonNull(tsIgnoreContent.index) + tsIgnoreContent[0].length)}`;
17
+ tsIgnoreContent = code.match(/\/\/\s*<deno-tsignore>/);
18
+ }
19
+ return code;
20
+ }
21
+ export {
22
+ applyDenoDirectives
23
+ };
package/jsr/config.d.ts CHANGED
@@ -45,4 +45,48 @@ export interface JsrConfig {
45
45
  transformAst?: (ast: ts.SourceFile) => boolean;
46
46
  /** similar to transformAst, but for the code itself (runs after transformAst) */
47
47
  transformCode?: (path: string, code: string) => string;
48
+ /**
49
+ * whether to enable pre-processor directives when processing typescript files.
50
+ * this is useful to provide deno-specific typings for stuff.
51
+ *
52
+ * **note**: this is run *before* the transformCode hook, but *after* the transformAst hook
53
+ *
54
+ * supported directives:
55
+ *
56
+ * `<deno-insert>`: inserts the given code at transform time. example:
57
+ * ```ts
58
+ * // <deno-insert>
59
+ * // declare type SharedWorker = never
60
+ * // </deno-insert>
61
+ * ```
62
+ * transforms to:
63
+ * ```ts
64
+ * declare type SharedWorker = never
65
+ * ```
66
+ *
67
+ * `<deno-remove>`: remove the given code at transform time. example:
68
+ * ```ts
69
+ * // <deno-remove>
70
+ * if (self instanceof SharedWorkerGlobalScope) {
71
+ * // do something
72
+ * }
73
+ * // </deno-remove>
74
+ * ```
75
+ * transforms to: (nothing)
76
+ *
77
+ * `<deno-tsignore>`: insert a `// @ts-ignore` comment when building for deno at the given position
78
+ * example:
79
+ * ```ts
80
+ * // <deno-tsignore>
81
+ * const foo: string = 123
82
+ * ```
83
+ * transforms to:
84
+ * ```ts
85
+ * // @ts-ignore
86
+ * const foo = 123
87
+ * ```
88
+ *
89
+ * @default false
90
+ */
91
+ enableDenoDirectives?: boolean;
48
92
  }
@@ -12,6 +12,7 @@ import { normalizeFilePath } from "../misc/path.js";
12
12
  import { collectPackageJsons, filterPackageJsonsForPublish } from "../package-json/collect-package-jsons.js";
13
13
  import { processPackageJson } from "../package-json/process-package-json.js";
14
14
  import { findRootPackage, collectVersions } from "../package-json/utils.js";
15
+ import { applyDenoDirectives } from "./_deno-directives.js";
15
16
  import { packageJsonToDeno } from "./deno-json.js";
16
17
  function mergeArrays(a, b, defaultValue = []) {
17
18
  if (!a) return b ?? defaultValue;
@@ -111,6 +112,9 @@ async function generateDenoWorkspace(params) {
111
112
  fileContent = printer.printFile(file);
112
113
  changed = true;
113
114
  }
115
+ if (rootConfig?.enableDenoDirectives) {
116
+ fileContent = applyDenoDirectives(fileContent);
117
+ }
114
118
  if (rootConfig?.transformCode || packageConfigJsr?.transformCode) {
115
119
  const origFileContent = fileContent;
116
120
  if (rootConfig?.transformCode) {
package/misc/_config.d.ts CHANGED
@@ -1 +1 @@
1
- export declare function loadBuildConfig<T>(packageRoot: string, configName?: string): Promise<T | undefined>;
1
+ export declare function loadBuildConfig<T>(packageRoot: string): Promise<T | undefined>;
package/misc/_config.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { join } from "node:path";
2
- async function loadBuildConfig(packageRoot, configName = "build.config.js") {
2
+ const CONFIG_NAME = "build.config.js";
3
+ async function loadBuildConfig(packageRoot) {
3
4
  try {
4
- const mod = (await import(join(packageRoot, configName))).default;
5
+ const mod = (await import(join(packageRoot, CONFIG_NAME))).default;
5
6
  if (typeof mod === "function") {
6
7
  return mod();
7
8
  } else {
@@ -9,7 +10,7 @@ async function loadBuildConfig(packageRoot, configName = "build.config.js") {
9
10
  }
10
11
  } catch (e) {
11
12
  if (!(e instanceof Error && e.code === "ERR_MODULE_NOT_FOUND")) {
12
- throw new Error(`Could not load ${configName}`, { cause: e });
13
+ throw new Error(`Could not load ${CONFIG_NAME}`, { cause: e });
13
14
  }
14
15
  }
15
16
  }
@@ -12,3 +12,9 @@ export declare function processPackageJson(params: {
12
12
  packageJsonOrig: PackageJson;
13
13
  entrypoints: Record<string, string>;
14
14
  };
15
+ /**
16
+ * remove comonjs export definitions from package.json
17
+ *
18
+ * **note**: this function modifies the input object
19
+ */
20
+ export declare function removeCommonjsExports(exports: Record<string, unknown>): void;
@@ -138,6 +138,19 @@ function processPackageJson(params) {
138
138
  entrypoints
139
139
  };
140
140
  }
141
+ function removeCommonjsExports(exports) {
142
+ const keys = Object.keys(exports);
143
+ if (keys.includes("import")) {
144
+ delete exports.require;
145
+ return;
146
+ }
147
+ for (const key of keys) {
148
+ const value = exports[key];
149
+ if (value == null || typeof value !== "object") continue;
150
+ delete value.require;
151
+ }
152
+ }
141
153
  export {
142
- processPackageJson
154
+ processPackageJson,
155
+ removeCommonjsExports
143
156
  };
@@ -45,12 +45,21 @@ export interface PackageJson {
45
45
  */
46
46
  distOnlyFields?: Record<string, unknown>;
47
47
  /**
48
- * whether this package has its own versioning scheme
48
+ * whether this package has its own versioning scheme, not managed by @fuman/build
49
49
  * (be careful with this option! this might break cross-release semver compatibility)
50
50
  */
51
51
  ownVersioning?: boolean;
52
52
  /** whether this package should not be published */
53
53
  private?: boolean;
54
+ /**
55
+ * whether this is a "standalone" package
56
+ *
57
+ * standalone packages have a few differences from normal ones:
58
+ * - (bump-version) each standalone package has independent versioning
59
+ * - (lint) normal packages can't depend on standalone packages
60
+ * - (lint) unlike normal packages, standalone packages are allowed to depend on older versions of workspace packages
61
+ */
62
+ standalone?: boolean;
54
63
  };
55
64
  [key: string]: any;
56
65
  }
@@ -47,6 +47,7 @@ const PackageJsonSchema = z.object({
47
47
  keepScripts: z.array(z.string()),
48
48
  distOnlyFields: z.record(z.unknown()),
49
49
  ownVersioning: z.boolean(),
50
+ standalone: z.boolean(),
50
51
  private: z.boolean()
51
52
  }).partial(),
52
53
  // todo: properly type this
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@fuman/build",
3
3
  "type": "module",
4
- "version": "0.0.13",
4
+ "version": "0.0.15",
5
5
  "description": "utils for building packages and managing monorepos",
6
6
  "license": "MIT",
7
7
  "scripts": {},
8
8
  "dependencies": {
9
9
  "@drizzle-team/brocli": "^0.10.2",
10
- "@fuman/fetch": "^0.0.13",
11
- "@fuman/io": "^0.0.11",
12
- "@fuman/node": "^0.0.11",
13
- "@fuman/utils": "^0.0.11",
10
+ "@fuman/fetch": "0.0.13",
11
+ "@fuman/io": "^0.0.15",
12
+ "@fuman/node": "^0.0.15",
13
+ "@fuman/utils": "^0.0.15",
14
14
  "cross-spawn": "^7.0.5",
15
15
  "detect-indent": "^7.0.1",
16
16
  "js-yaml": "^4.1.0",
@@ -30,30 +30,18 @@
30
30
  "import": {
31
31
  "types": "./index.d.ts",
32
32
  "default": "./index.js"
33
- },
34
- "require": {
35
- "types": "./index.d.cts",
36
- "default": "./index.cjs"
37
33
  }
38
34
  },
39
35
  "./vite": {
40
36
  "import": {
41
37
  "types": "./vite.d.ts",
42
38
  "default": "./vite.js"
43
- },
44
- "require": {
45
- "types": "./vite.d.cts",
46
- "default": "./vite.cjs"
47
39
  }
48
40
  },
49
41
  "./jsr": {
50
42
  "import": {
51
43
  "types": "./jsr.d.ts",
52
44
  "default": "./jsr.js"
53
- },
54
- "require": {
55
- "types": "./jsr.d.cts",
56
- "default": "./jsr.cjs"
57
45
  }
58
46
  }
59
47
  },
@@ -17,7 +17,7 @@ export interface BumpVersionResult {
17
17
  /** max version of all packages */
18
18
  maxVersion: string;
19
19
  /** next version */
20
- nextVersion: string;
20
+ nextVersions: Record<string, string>;
21
21
  /** changed packages which will be bumped to `nextVersion` */
22
22
  changedPackages: BumpVersionPackage[];
23
23
  /** detected release type */
@@ -36,6 +36,8 @@ export interface BumpVersionResult {
36
36
  export declare function bumpVersion(params: {
37
37
  /** packages for which to generate the changelog */
38
38
  workspace: WorkspacePackage[];
39
+ /** previous tag */
40
+ prevTag?: string | null;
39
41
  /** whether to bump version of all packages, not just changed ones */
40
42
  all?: boolean;
41
43
  type?: ReleaseType;
@@ -4,7 +4,7 @@ import process from "node:process";
4
4
  import { asNonNull } from "@fuman/utils";
5
5
  import detectIndent from "detect-indent";
6
6
  import { parse, inc, satisfies, gt } from "semver";
7
- import { getCommitsBetween, parseConventionalCommit } from "../git/utils.js";
7
+ import { getCommitsBetween, parseConventionalCommit, getLatestTag } from "../git/utils.js";
8
8
  import { collectVersions, findRootPackage } from "../package-json/utils.js";
9
9
  import { findProjectChangedPackages } from "./collect-files.js";
10
10
  async function bumpVersion(params) {
@@ -21,7 +21,7 @@ async function bumpVersion(params) {
21
21
  for (const pkg of workspaceWithoutRoot) {
22
22
  if (pkg.root) continue;
23
23
  const version = asNonNull(pkg.json.version);
24
- if (pkg.json.fuman?.ownVersioning) {
24
+ if (pkg.json.fuman?.ownVersioning || pkg.json.fuman?.standalone) {
25
25
  continue;
26
26
  }
27
27
  if (maxVersion == null || gt(version, maxVersion)) {
@@ -89,6 +89,9 @@ async function bumpVersion(params) {
89
89
  if (obj == null) continue;
90
90
  if (obj[pkgName] == null || typeof obj[pkgName] !== "string") continue;
91
91
  let expandedVersion = obj[pkgName];
92
+ if (!expandedVersion.startsWith("workspace:") && otherPkg.json.fuman?.standalone) {
93
+ continue;
94
+ }
92
95
  if (expandedVersion === "workspace:^") expandedVersion = `^${workspaceVersions[pkgName]}`;
93
96
  if (expandedVersion === "workspace:*") expandedVersion = workspaceVersions[pkgName];
94
97
  if (!satisfies(nextVersion, expandedVersion)) {
@@ -115,22 +118,42 @@ async function bumpVersion(params) {
115
118
  if (withRoot) {
116
119
  packagesToBump.push(findRootPackage(workspace));
117
120
  }
121
+ const nextVersions = {};
122
+ let prevTag = params.prevTag;
118
123
  for (const pkg of packagesToBump) {
119
124
  if (pkg.json.fuman?.ownVersioning) continue;
125
+ let newVersion = nextVersion;
126
+ if (pkg.json.fuman?.standalone) {
127
+ newVersion = asNonNull(pkg.json.version);
128
+ if (prevTag === void 0) {
129
+ prevTag = await getLatestTag(pkg.path);
130
+ }
131
+ if (prevTag != null) {
132
+ const commits = await getCommitsBetween({
133
+ until: prevTag,
134
+ cwd,
135
+ files: [join(pkg.path, "package.json")]
136
+ });
137
+ if (commits.length > 0) {
138
+ newVersion = asNonNull(inc(newVersion, type));
139
+ }
140
+ }
141
+ }
120
142
  if (!dryRun) {
121
143
  const pkgJsonPath = join(pkg.path, "package.json");
122
144
  const pkgJsonText = await fsp.readFile(pkgJsonPath, "utf8");
123
145
  const indent = detectIndent(pkgJsonText).indent || " ";
124
146
  const pkgJson = JSON.parse(pkgJsonText);
125
- pkgJson.version = nextVersion;
147
+ pkgJson.version = newVersion;
126
148
  await fsp.writeFile(pkgJsonPath, `${JSON.stringify(pkgJson, null, indent)}
127
149
  `);
128
150
  }
129
- pkg.json.version = nextVersion;
151
+ pkg.json.version = newVersion;
152
+ nextVersions[asNonNull(pkg.json.name)] = newVersion;
130
153
  }
131
154
  return {
132
155
  maxVersion,
133
- nextVersion,
156
+ nextVersions,
134
157
  changedPackages: result,
135
158
  releaseType: type,
136
159
  hasBreakingChanges,
@@ -1,5 +1,5 @@
1
- import { VersioningOptions } from './types.js';
2
1
  import { WorkspacePackage } from '../package-json/index.js';
2
+ import { VersioningOptions } from './types.js';
3
3
  export interface ProjectChangedFile {
4
4
  /** package to which the file belongs */
5
5
  package: WorkspacePackage;
@@ -12,6 +12,21 @@ export interface ChangelogGeneratorParams {
12
12
  }
13
13
  /** settings for versioning manager */
14
14
  export interface VersioningOptions {
15
+ /**
16
+ * schema to use when tagging commits
17
+ * - `semver`: semver-compatible schema (e.g. `v1.2.3`), based on the max. version of the workspace
18
+ * - `date`: date-based schema (e.g. `v2023.01.01`), based on the date of the release
19
+ *
20
+ * unless your monorepo has standalone packages, you should probably use `semver` schema.
21
+ * `date` schema is primarily useful for repos where different packages have separate release cycles,
22
+ * to avoid conflicts when bumping versions.
23
+ *
24
+ * > note: this is **only** used for the `release` command, when tagging commits.
25
+ * > this does **not** affect the versioning of the packages themselves, they always use semver.
26
+ *
27
+ * @default 'semver'
28
+ */
29
+ taggingSchema?: 'semver' | 'date';
15
30
  /**
16
31
  * globs of files changes to which to white-list (relative to package root)
17
32
  *
@@ -1,6 +1,6 @@
1
+ import { MaybeArray, MaybePromise } from '@fuman/utils';
1
2
  import { PluginOption } from 'vite';
2
3
  import { BuildHookContext } from '../config.js';
3
- import { MaybeArray, MaybePromise } from '@fuman/utils';
4
4
  export declare function fumanBuild(params: {
5
5
  root: URL | string;
6
6
  /**
@@ -7,7 +7,7 @@ import { loadBuildConfig } from "../misc/_config.js";
7
7
  import { tryCopy, fileExists, directoryExists } from "../misc/fs.js";
8
8
  import { normalizeFilePath } from "../misc/path.js";
9
9
  import { collectPackageJsons } from "../package-json/collect-package-jsons.js";
10
- import { processPackageJson } from "../package-json/process-package-json.js";
10
+ import { processPackageJson, removeCommonjsExports } from "../package-json/process-package-json.js";
11
11
  import { collectVersions } from "../package-json/utils.js";
12
12
  async function fumanBuild(params) {
13
13
  const {
@@ -113,6 +113,9 @@ async function fumanBuild(params) {
113
113
  },
114
114
  async closeBundle() {
115
115
  if (isNoop) return;
116
+ if (!buildCjs) {
117
+ removeCommonjsExports(packageJson.exports);
118
+ }
116
119
  await params.finalizePackageJson?.(hookContext);
117
120
  await packageConfig?.finalizePackageJson?.(hookContext);
118
121
  await fsp.writeFile(join(buildDir, "package.json"), JSON.stringify(packageJson, null, 4));
@@ -1,50 +0,0 @@
1
- import { bc } from './_utils.js';
2
- /** information about a mismatch between a package and its dependencies */
3
- export interface WorkspaceDepsError {
4
- /** package name where the mismatch occurred */
5
- package: string;
6
- /** name of the mismatched dependency */
7
- dependency: string;
8
- /** version of the mismatched dependency */
9
- version: string;
10
- /** type of dependency (`dependencies`, `devDependencies`, etc) */
11
- at: string;
12
- /** package name where the dependency was originally declared */
13
- otherPackage: string;
14
- /** original version of the dependency */
15
- otherVersion: string;
16
- }
17
- /**
18
- * validate the external dependencies of a workspace
19
- */
20
- export declare function validateWorkspaceDeps(params: {
21
- /** path to the workspace root */
22
- workspaceRoot: string | URL;
23
- /**
24
- * whether to also validate the root package.json
25
- *
26
- * @default true
27
- */
28
- includeRoot?: boolean;
29
- /**
30
- * whether to skip validating dependencies of other workspace packages
31
- *
32
- * @default true
33
- */
34
- skipWorkspaceDeps?: boolean;
35
- /** whether to skip validating peer dependencies */
36
- skipPeerDeps?: boolean;
37
- }): Promise<WorkspaceDepsError[]>;
38
- export declare const validateWorkspaceDepsCli: bc.Command<{
39
- includeRoot: boolean;
40
- noSkipWorkspaceDeps: boolean | undefined;
41
- root: string | undefined;
42
- withErrorCode: boolean | undefined;
43
- skipPeerDeps: boolean | undefined;
44
- }, {
45
- includeRoot: boolean;
46
- noSkipWorkspaceDeps: boolean | undefined;
47
- root: string | undefined;
48
- withErrorCode: boolean | undefined;
49
- skipPeerDeps: boolean | undefined;
50
- }>;
@@ -1,87 +0,0 @@
1
- import process from "node:process";
2
- import { satisfies } from "semver";
3
- import { collectPackageJsons } from "../../package-json/collect-package-jsons.js";
4
- import * as bc from "@drizzle-team/brocli";
5
- async function validateWorkspaceDeps(params) {
6
- const {
7
- workspaceRoot,
8
- includeRoot = true,
9
- skipWorkspaceDeps = true,
10
- skipPeerDeps = true
11
- } = params;
12
- const pjs = await collectPackageJsons(workspaceRoot, includeRoot);
13
- const workspacePackages = new Set(skipWorkspaceDeps ? pjs.map((pj) => pj.json.name) : []);
14
- const versions = {};
15
- const errors = [];
16
- for (const { json: pj } of pjs) {
17
- if (pj.name === void 0) {
18
- throw new Error("package.json without name is not supported");
19
- }
20
- for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
21
- const deps = pj[field];
22
- if (!deps) continue;
23
- if (field === "peerDependencies" && skipPeerDeps) continue;
24
- for (const [name, version] of Object.entries(deps)) {
25
- if (workspacePackages.has(name)) continue;
26
- if (version.startsWith("workspace:")) continue;
27
- if (versions[name] === void 0) {
28
- versions[name] = {};
29
- }
30
- for (const [pkgName, pkgDepVersions] of Object.entries(versions[name])) {
31
- let ok = true;
32
- if (pkgDepVersions.match(/^(?:https?:\/\/|catalog:)/)) {
33
- ok = version === pkgDepVersions;
34
- } else {
35
- ok = satisfies(version, pkgDepVersions);
36
- }
37
- if (!ok) {
38
- errors.push({
39
- package: pj.name,
40
- dependency: name,
41
- version,
42
- at: field,
43
- otherPackage: pkgName,
44
- otherVersion: pkgDepVersions
45
- });
46
- }
47
- }
48
- versions[name][pj.name] = version;
49
- }
50
- }
51
- }
52
- return errors;
53
- }
54
- const validateWorkspaceDepsCli = bc.command({
55
- name: "validate-workspace-deps",
56
- desc: "validate the external dependencies of a workspace",
57
- options: {
58
- includeRoot: bc.boolean("include-root").desc("whether to also validate the root package.json").default(false),
59
- noSkipWorkspaceDeps: bc.boolean("no-skip-workspace-deps").desc("whether to not skip validating dependencies of other workspace packages"),
60
- root: bc.string().desc("path to the root of the workspace (default: cwd)"),
61
- withErrorCode: bc.boolean("with-error-code").desc("whether to exit with a non-zero code if there are mismatches"),
62
- skipPeerDeps: bc.boolean("skip-peer-deps").desc("whether to skip validating peer dependencies")
63
- },
64
- handler: async (args) => {
65
- const errors = await validateWorkspaceDeps({
66
- workspaceRoot: args.root ?? process.cwd(),
67
- includeRoot: args.includeRoot,
68
- skipWorkspaceDeps: !args.noSkipWorkspaceDeps,
69
- skipPeerDeps: args.skipPeerDeps
70
- });
71
- if (errors.length > 0) {
72
- console.error("⚠️ Found external dependencies mismatch:");
73
- for (const error of errors) {
74
- console.error(` - at ${error.package}: ${error.at} has ${error.dependency}@${error.version}, but ${error.otherPackage} has @${error.otherVersion}`);
75
- }
76
- if (args.withErrorCode) {
77
- process.exit(1);
78
- }
79
- } else {
80
- console.log("✅ All external dependencies match!");
81
- }
82
- }
83
- });
84
- export {
85
- validateWorkspaceDeps,
86
- validateWorkspaceDepsCli
87
- };