@vnejs/monorepo 0.0.4 → 0.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vnejs/monorepo",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Monorepo tooling for @vnejs",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -16,6 +16,8 @@
16
16
  "license": "ISC",
17
17
  "dependencies": {
18
18
  "commander": "~15.0.0",
19
- "typescript": "6.0.3"
19
+ "happy-dom": "~20.0.0",
20
+ "typescript": "6.0.3",
21
+ "vitest": "~3.2.4"
20
22
  }
21
23
  }
package/src/bump/index.js CHANGED
@@ -3,6 +3,8 @@ const listWorkspacePackages = require("../workspace/list-packages");
3
3
  const syncDeps = require("../sync-deps");
4
4
  const runChanged = require("../run-changed");
5
5
  const { run } = require("../publish/utils");
6
+ const confirm = require("../utils/confirm");
7
+ const { formatPackageLine } = require("../utils/format-package");
6
8
 
7
9
  const PHASES = ["graph", "sync-all", "patch"];
8
10
 
@@ -27,9 +29,45 @@ const previousMinorLine = (version) => {
27
29
  process.exit(1);
28
30
  };
29
31
 
30
- module.exports = (version, options = {}) => {
32
+ const describeBumpPlan = (version, options, phases) => {
33
+ const expectVersion = options.expectVersion ?? version;
34
+ const onlyVersion = options.onlyVersion ?? previousMinorLine(version);
35
+ const lines = [`\nBump target: ${expectVersion}.x`];
36
+
37
+ if (phases.includes("graph")) {
38
+ const { order } = resolveGraph({
39
+ cwd: options.cwd,
40
+ onlyVersion,
41
+ script: "publish:minor",
42
+ });
43
+
44
+ if (!order.length) {
45
+ console.error("No packages matched graph filters for phase 1.");
46
+ process.exit(1);
47
+ }
48
+
49
+ lines.push(`Phase 1: sync-deps + publish:minor (${order.length} packages, filter ${onlyVersion}.x)`);
50
+ }
51
+
52
+ if (phases.includes("sync-all")) {
53
+ const { packages } = listWorkspacePackages({ cwd: options.cwd });
54
+ lines.push(`Phase 2: sync-deps --all (${packages.length} packages)`);
55
+ }
56
+
57
+ if (phases.includes("patch")) {
58
+ lines.push(`Phase 3: publish:patch changed packages (ref ${options.ref ?? "HEAD"})`);
59
+ }
60
+
61
+ if (options.dryRun) {
62
+ lines.push("\nMode: dry run");
63
+ }
64
+
65
+ return lines.join("\n");
66
+ };
67
+
68
+ module.exports = async (version, options = {}) => {
31
69
  const dryRun = Boolean(options.dryRun);
32
- const phases = options.phase ? [options.phase] : ["graph", "sync-all", "patch"];
70
+ const phases = options.phase ? [options.phase] : PHASES;
33
71
 
34
72
  if (options.phase && !PHASES.includes(options.phase)) {
35
73
  console.error(`phase must be one of: ${PHASES.join(", ")}`);
@@ -41,6 +79,17 @@ module.exports = (version, options = {}) => {
41
79
  const expectVersion = options.expectVersion ?? version;
42
80
  const onlyVersion = options.onlyVersion ?? previousMinorLine(version);
43
81
 
82
+ console.log(describeBumpPlan(version, { ...options, expectVersion, onlyVersion }, phases));
83
+
84
+ if (!dryRun) {
85
+ const confirmed = await confirm("Continue? [y/N] ");
86
+
87
+ if (!confirmed) {
88
+ console.log("Aborted.");
89
+ return;
90
+ }
91
+ }
92
+
44
93
  const graphOptions = {
45
94
  cwd: options.cwd,
46
95
  onlyVersion,
@@ -66,7 +115,7 @@ module.exports = (version, options = {}) => {
66
115
  }
67
116
 
68
117
  order.forEach((pkg, index) => {
69
- console.log(`\n==> [${index + 1}/${order.length}] ${pkg.name} (${pkg.dir})`);
118
+ console.log(formatPackageLine(pkg, { leadingNewline: true, tag: `[${index + 1}/${order.length}]` }));
70
119
 
71
120
  if (dryRun) {
72
121
  console.log(` sync-deps --expect-version ${syncOptions.expectVersion}`);
@@ -96,7 +145,8 @@ module.exports = (version, options = {}) => {
96
145
  if (phases.includes("patch")) {
97
146
  console.log(`\n=== Phase 3: publish:changed:patch ===`);
98
147
 
99
- runChanged("publish:patch", {
148
+ await runChanged({
149
+ script: "publish:patch",
100
150
  ref: options.ref ?? "HEAD",
101
151
  dryRun,
102
152
  });
@@ -3,6 +3,7 @@ const { existsSync } = require("fs");
3
3
  const path = require("path");
4
4
  const readPackageJson = require("./workspace/read-package-json");
5
5
  const findMonorepoRoot = require("./workspace/find-root");
6
+ const isStubTestScript = require("./workspace/is-stub-test-script");
6
7
 
7
8
  const getChangedFiles = (root, ref = "HEAD") => {
8
9
  const files = new Set();
@@ -30,7 +31,7 @@ const findPackageForFile = (root, filePath) => {
30
31
  const pkg = readPackageJson(dir);
31
32
  if (dir === root) return null;
32
33
 
33
- return { name: pkg.name, dir: path.relative(root, dir) || "." };
34
+ return { name: pkg.name, version: pkg.version ?? "0.0.0", dir: path.relative(root, dir) || "." };
34
35
  }
35
36
 
36
37
  if (dir === root) break;
@@ -54,7 +55,12 @@ const getChangedPackages = (options = {}) => {
54
55
  if (options.script) {
55
56
  result = result.filter((pkg) => {
56
57
  const pkgJson = readPackageJson(path.join(root, pkg.dir));
57
- return Boolean(pkgJson.scripts?.[options.script]);
58
+ const script = pkgJson.scripts?.[options.script];
59
+
60
+ if (!script) return false;
61
+ if (options.script === "test" && isStubTestScript(script)) return false;
62
+
63
+ return true;
58
64
  });
59
65
  }
60
66
 
@@ -0,0 +1,24 @@
1
+ const findRoot = require("../workspace/find-root");
2
+ const { run } = require("../publish/utils");
3
+
4
+ module.exports = (options = {}) => {
5
+ const root = findRoot(options.cwd ?? process.cwd());
6
+
7
+ console.log(`Monorepo root: ${root}\n`);
8
+
9
+ console.log("==> Removing node_modules...");
10
+ run("rm -rf node_modules", root);
11
+ run('find . -name node_modules -type d -prune -exec rm -rf {} +', root);
12
+
13
+ console.log("==> Removing Turbo cache...");
14
+ run("rm -rf .turbo", root);
15
+ run('find . -name .turbo -type d -prune -exec rm -rf {} +', root);
16
+
17
+ console.log("==> Removing dist folders...");
18
+ run('find . -name dist -type d -prune -exec rm -rf {} +', root);
19
+
20
+ console.log("==> Removing package-lock files...");
21
+ run('find . -name package-lock.json -type f -delete', root);
22
+
23
+ console.log("\n==> Clear complete.");
24
+ };
package/src/cli.js CHANGED
@@ -4,11 +4,12 @@ const { Command } = require("commander");
4
4
 
5
5
  const packageJson = require("../package.json");
6
6
  const publishAction = require("./publish");
7
- const { getChangedPackages } = require("./changed-packages");
8
7
  const runChangedAction = require("./run-changed");
9
8
  const graphAction = require("./graph");
10
9
  const syncDepsAction = require("./sync-deps");
11
10
  const bumpAction = require("./bump");
11
+ const runAction = require("./run");
12
+ const clearAction = require("./clear");
12
13
 
13
14
  const program = new Command();
14
15
 
@@ -37,7 +38,7 @@ program
37
38
  .option("--only-version <pattern>", "filter by version prefix, e.g. 0.0.x")
38
39
  .option("--name <pattern>", "filter by package name substring")
39
40
  .option("--script <script>", "only packages that define this npm script")
40
- .option("--layers", "group packages into parallel publish layers", false)
41
+ .option("--flat", "show packages as a flat list with layer column", false)
41
42
  .option("--format <format>", "output format: text | json", "text")
42
43
  .action(graphAction);
43
44
 
@@ -65,7 +66,7 @@ program
65
66
  .option("--dry-run", "show planned steps without making changes", false)
66
67
  .option("--only-version <pattern>", "graph version filter for phase 1 (default: previous minor line)")
67
68
  .option("--phase <phase>", "run only one phase: graph | sync-all | patch")
68
- .option("--ref <ref>", "git ref for phase 3 changed-packages", "HEAD")
69
+ .option("--ref <ref>", "git ref for phase 3 run-changed", "HEAD")
69
70
  .option("--workspace", "use workspace versions in sync-deps (phase 2)", false)
70
71
  .option("--expect-version <version>", "wait until npm has this major.minor line (default: <version>)")
71
72
  .option("--expect-wait <ms>", "delay between npm retries", "10000")
@@ -79,26 +80,33 @@ program
79
80
  );
80
81
 
81
82
  program
82
- .command("changed-packages")
83
- .description("List workspace packages changed in git")
84
- .option("--ref <ref>", "git ref to compare against", "HEAD")
85
- .option("--script <script>", "only packages that define this npm script")
86
- .action((options) => {
87
- const { packages } = getChangedPackages({ ref: options.ref, script: options.script });
83
+ .command("test")
84
+ .description("run vitest in current directory")
85
+ .action((options) => require("./test")(options));
88
86
 
89
- packages.forEach((pkg, index) => {
90
- console.log(`==> [${index + 1}/${packages.length}] ${pkg.name} (${pkg.dir})`);
91
- });
92
- console.log("");
93
- console.log(`Total: ${packages.length}`);
94
- });
87
+ program
88
+ .command("run")
89
+ .description("Run an npm script in workspace packages in dependency order (turbo per layer)")
90
+ .option("--script <script>", "npm script to run in each package", "build")
91
+ .option("--only-version <pattern>", "filter by version prefix, e.g. 0.0.x")
92
+ .option("--name <pattern>", "filter by package name substring (includes dependency closure)")
93
+ .option("--sequential", "run one package at a time in flat graph order", false)
94
+ .option("--force", "pass --force to turbo (bypass cache)", false)
95
+ .option("--dry-run", "show run plan without executing", false)
96
+ .action((options) => runAction(options));
95
97
 
96
98
  program
97
99
  .command("run-changed")
98
100
  .description("Run npm script sequentially in git-changed workspace packages")
99
- .argument("<script>", "npm script name from package.json")
101
+ .option("--script <script>", "npm script name from package.json")
100
102
  .option("--ref <ref>", "git ref to compare against", "HEAD")
101
- .option("--dry-run", "list packages without running the script", false)
103
+ .option("--dry-run", "list changed packages without running the script", false)
104
+ .option("--confirm", "ask for confirmation before running the script", false)
102
105
  .action(runChangedAction);
103
106
 
107
+ program
108
+ .command("clear")
109
+ .description("Clean node_modules, caches, dist, and lockfiles")
110
+ .action(clearAction);
111
+
104
112
  program.parse();
@@ -1,5 +1,30 @@
1
1
  const path = require("path");
2
2
  const resolveGraph = require("../workspace/resolve-graph");
3
+ const { formatPackageLine, formatPackageRef } = require("../utils/format-package");
4
+
5
+ const buildLayerMeta = (layers) => {
6
+ const byName = new Map();
7
+
8
+ layers.forEach((layer, layerIndex) => {
9
+ layer.forEach((pkg, packageIndex) => {
10
+ byName.set(pkg.name, {
11
+ layer: layerIndex + 1,
12
+ layerTotal: layers.length,
13
+ layerIndex: packageIndex + 1,
14
+ layerSize: layer.length,
15
+ });
16
+ });
17
+ });
18
+
19
+ return byName;
20
+ };
21
+
22
+ const toPackageEntry = (pkg, meta) => ({
23
+ name: pkg.name,
24
+ version: pkg.version,
25
+ dir: pkg.dir,
26
+ ...meta,
27
+ });
3
28
 
4
29
  module.exports = (options = {}) => {
5
30
  const { root, order, layers } = resolveGraph(options);
@@ -9,16 +34,26 @@ module.exports = (options = {}) => {
9
34
  return { root, packages: [], layers: [], order: [] };
10
35
  }
11
36
 
37
+ const layerMetaByName = buildLayerMeta(layers);
38
+
12
39
  if (options.format === "json") {
13
40
  console.log(
14
41
  JSON.stringify(
15
42
  {
16
43
  root,
17
44
  total: order.length,
18
- layers: layers.map((layer) =>
19
- layer.map((pkg) => ({ name: pkg.name, version: pkg.version, dir: pkg.dir })),
45
+ layerTotal: layers.length,
46
+ layers: layers.map((layer, layerIndex) =>
47
+ layer.map((pkg, packageIndex) =>
48
+ toPackageEntry(pkg, {
49
+ layer: layerIndex + 1,
50
+ layerTotal: layers.length,
51
+ layerIndex: packageIndex + 1,
52
+ layerSize: layer.length,
53
+ }),
54
+ ),
20
55
  ),
21
- order: order.map((pkg) => ({ name: pkg.name, version: pkg.version, dir: pkg.dir })),
56
+ order: order.map((pkg) => toPackageEntry(pkg, layerMetaByName.get(pkg.name))),
22
57
  },
23
58
  null,
24
59
  2,
@@ -28,26 +63,31 @@ module.exports = (options = {}) => {
28
63
  return { root, packages: order, layers, order: order.map((pkg) => pkg.name) };
29
64
  }
30
65
 
31
- if (options.layers) {
32
- layers.forEach((layer, index) => {
33
- console.log(`# layer ${index + 1} (${layer.length})`);
66
+ console.log(`\n=== Graph: ${order.length} packages, ${layers.length} layers ===`);
67
+ console.log(`Root: ${path.relative(process.cwd(), root) || "."}\n`);
34
68
 
35
- layer.forEach((pkg) => {
36
- console.log(`${pkg.name}\t${pkg.version}\t${pkg.dir}`);
37
- });
38
-
39
- if (index < layers.length - 1) console.log("");
40
- });
41
- } else {
69
+ if (options.flat) {
42
70
  order.forEach((pkg, index) => {
43
- console.log(`${index + 1}\t${pkg.name}\t${pkg.version}\t${pkg.dir}`);
71
+ const meta = layerMetaByName.get(pkg.name);
72
+ console.log(
73
+ `${index + 1}\t${meta.layer}/${meta.layerTotal}\t${formatPackageRef(pkg)}\t${pkg.dir}`,
74
+ );
44
75
  });
45
- }
76
+ } else {
77
+ for (let layerIndex = 0; layerIndex < layers.length; layerIndex++) {
78
+ const layer = layers[layerIndex];
79
+ console.log(`\n=== Layer ${layerIndex + 1}/${layers.length} (${layer.length} packages) ===`);
46
80
 
47
- console.log("");
48
- console.log(`Root: ${path.relative(process.cwd(), root) || "."}`);
49
- console.log(`Packages: ${order.length}`);
50
- console.log(`Layers: ${layers.length}`);
81
+ layer.forEach((pkg, packageIndex) => {
82
+ console.log(
83
+ formatPackageLine(pkg, {
84
+ indent: " ",
85
+ tag: `[${layerIndex + 1}/${layers.length}:${packageIndex + 1}/${layer.length}]`,
86
+ }),
87
+ );
88
+ });
89
+ }
90
+ }
51
91
 
52
92
  return {
53
93
  root,
package/src/index.js CHANGED
@@ -1,11 +1,14 @@
1
1
  module.exports = {
2
2
  package: require("./package"),
3
+ test: require("./test"),
3
4
  publish: require("./publish"),
4
5
  getChangedPackages: require("./changed-packages").getChangedPackages,
5
6
  runChanged: require("./run-changed"),
6
7
  graph: require("./graph"),
7
8
  syncDeps: require("./sync-deps"),
8
9
  bump: require("./bump"),
10
+ run: require("./run"),
11
+ clear: require("./clear"),
9
12
  resolveGraph: require("./workspace/resolve-graph"),
10
13
  listWorkspacePackages: require("./workspace/list-packages"),
11
14
  toTildeMinor: require("./workspace/to-tilde-minor"),
@@ -0,0 +1,70 @@
1
+ const resolveGraph = require("../workspace/resolve-graph");
2
+ const findRoot = require("../workspace/find-root");
3
+ const { run } = require("../publish/utils");
4
+ const { formatPackageLine } = require("../utils/format-package");
5
+
6
+ const runLayerTurbo = (layer, root, { script, force }) => {
7
+ const filters = layer.map((pkg) => `--filter=${pkg.name}`).join(" ");
8
+ const forceFlag = force ? " --force" : "";
9
+
10
+ run(`npx turbo run ${script} ${filters}${forceFlag}`, root);
11
+ };
12
+
13
+ module.exports = (options = {}) => {
14
+ const script = options.script ?? "build";
15
+ const root = findRoot(options.cwd ?? process.cwd());
16
+ const dryRun = Boolean(options.dryRun);
17
+ const sequential = Boolean(options.sequential);
18
+
19
+ const { order, layers } = resolveGraph({
20
+ cwd: options.cwd ?? root,
21
+ script,
22
+ onlyVersion: options.onlyVersion,
23
+ name: options.name,
24
+ includeDependencies: Boolean(options.name || options.onlyVersion),
25
+ });
26
+
27
+ if (!order.length) {
28
+ console.error(`No workspace packages matched filters with script "${script}".`);
29
+ process.exit(1);
30
+ }
31
+
32
+ console.log(`\n=== Run: ${order.length} packages, ${layers.length} layers ===`);
33
+ console.log(`Script: ${script}`);
34
+ console.log(`Root: ${root}`);
35
+ console.log(`Mode: ${sequential ? "sequential" : "layers (turbo)"}\n`);
36
+
37
+ const runPackage = (pkg, label) => {
38
+ console.log(formatPackageLine(pkg, { leadingNewline: true, tag: label }));
39
+
40
+ if (dryRun) return;
41
+
42
+ run(`npm run ${script}`, pkg.absDir);
43
+ };
44
+
45
+ if (sequential) {
46
+ order.forEach((pkg, index) => runPackage(pkg, `[${index + 1}/${order.length}]`));
47
+ } else {
48
+ for (let layerIndex = 0; layerIndex < layers.length; layerIndex++) {
49
+ const layer = layers[layerIndex];
50
+ console.log(`\n=== Layer ${layerIndex + 1}/${layers.length} (${layer.length} packages) ===`);
51
+
52
+ if (dryRun) {
53
+ console.log(` turbo run ${script} ${layer.map((pkg) => `--filter=${pkg.name}`).join(" ")}`);
54
+ layer.forEach((pkg, index) => {
55
+ console.log(
56
+ formatPackageLine(pkg, {
57
+ indent: " ",
58
+ tag: `[${layerIndex + 1}/${layers.length}:${index + 1}/${layer.length}]`,
59
+ }),
60
+ );
61
+ });
62
+ continue;
63
+ }
64
+
65
+ runLayerTurbo(layer, root, { script, force: options.force });
66
+ }
67
+ }
68
+
69
+ if (dryRun) console.log("\nDry run complete.");
70
+ };
@@ -1,24 +1,70 @@
1
1
  const path = require("path");
2
2
 
3
3
  const { getChangedPackages } = require("../changed-packages");
4
+ const resolveGraph = require("../workspace/resolve-graph");
4
5
  const { run } = require("../publish/utils");
6
+ const confirm = require("../utils/confirm");
7
+ const { formatPackageLine } = require("../utils/format-package");
5
8
 
6
- module.exports = (script, options = {}) => {
7
- const { root, packages } = getChangedPackages({ ref: options.ref, script });
9
+ const sortByGraphOrder = (root, changedPackages, script) => {
10
+ const changedNames = new Set(changedPackages.map((pkg) => pkg.name));
11
+ const { order } = resolveGraph({ cwd: root, script });
8
12
 
9
- if (!packages.length) {
10
- console.log(`No changed packages with script "${script}".`);
13
+ const sorted = order.filter((pkg) => changedNames.has(pkg.name));
14
+
15
+ const sortedNames = new Set(sorted.map((pkg) => pkg.name));
16
+ const rest = changedPackages.filter((pkg) => !sortedNames.has(pkg.name));
17
+
18
+ return [...sorted, ...rest];
19
+ };
20
+
21
+ const printPackages = (packages) => {
22
+ packages.forEach((pkg, index) => {
23
+ console.log(formatPackageLine(pkg, { tag: `[${index + 1}/${packages.length}]` }));
24
+ });
25
+ console.log("");
26
+ console.log(`Total: ${packages.length}`);
27
+ };
28
+
29
+ module.exports = async (options = {}) => {
30
+ const script = options.script;
31
+
32
+ if (!options.dryRun && !script) {
33
+ console.error('Missing required option "--script".');
34
+ process.exit(1);
35
+ }
36
+
37
+ const { root, packages: changedPackages } = getChangedPackages({ ref: options.ref, script });
38
+
39
+ if (!changedPackages.length) {
40
+ console.log(script ? `No changed packages with script "${script}".` : "No changed packages.");
11
41
  return;
12
42
  }
13
43
 
14
- for (const [index, pkg] of packages.entries()) {
15
- const pkgDir = path.join(root, pkg.dir);
16
- console.log(`\n==> [${index + 1}/${packages.length}] ${pkg.name} (${pkg.dir})`);
44
+ const packages = sortByGraphOrder(root, changedPackages, script);
45
+
46
+ if (options.dryRun) {
47
+ printPackages(packages);
48
+ return;
49
+ }
17
50
 
18
- if (options.dryRun) continue;
51
+ if (options.confirm) {
52
+ console.log(`\nThe following packages will run "${script}":\n`);
53
+ printPackages(packages);
19
54
 
20
- run(`npm run ${script}`, pkgDir);
55
+ const confirmed = await confirm(`Continue? [y/N] `);
56
+
57
+ if (!confirmed) {
58
+ console.log("Aborted.");
59
+ return;
60
+ }
61
+
62
+ console.log("");
21
63
  }
22
64
 
23
- if (options.dryRun) console.log(`\nTotal: ${packages.length}`);
65
+ for (const [index, pkg] of packages.entries()) {
66
+ const pkgDir = path.join(root, pkg.dir);
67
+ console.log(formatPackageLine(pkg, { leadingNewline: true, tag: `[${index + 1}/${packages.length}]` }));
68
+ run(`npm run ${script}`, pkgDir);
69
+ }
24
70
  };
@@ -5,6 +5,7 @@ const listWorkspacePackages = require("../workspace/list-packages");
5
5
  const fetchNpmVersion = require("../workspace/fetch-npm-version");
6
6
  const toTildeMinor = require("../workspace/to-tilde-minor");
7
7
  const { DEPENDENCY_FIELDS } = require("../workspace/build-graph");
8
+ const { formatPackageLine } = require("../utils/format-package");
8
9
 
9
10
  const resolveVersion = (name, options) => {
10
11
  if (options.workspace) {
@@ -102,7 +103,7 @@ module.exports = (options = {}) => {
102
103
  console.log(`Syncing dependencies in ${packages.length} workspace packages...`);
103
104
 
104
105
  for (const pkg of packages) {
105
- console.log(`\n==> ${pkg.name} (${pkg.dir})`);
106
+ console.log(formatPackageLine(pkg, { leadingNewline: true }));
106
107
 
107
108
  if (options.dryRun) {
108
109
  console.log(" sync-deps");
@@ -0,0 +1,16 @@
1
+ const { execSync } = require("child_process");
2
+ const { existsSync } = require("fs");
3
+ const path = require("path");
4
+
5
+ module.exports = (options = {}) => {
6
+ const pkgDir = options.cwd || process.cwd();
7
+ const configPath = path.join(pkgDir, "vitest.config.js");
8
+
9
+ if (!existsSync(configPath)) {
10
+ console.error(`vitest.config.js not found: ${configPath}`);
11
+ process.exit(1);
12
+ }
13
+
14
+ const vitest = require.resolve("vitest/vitest.mjs");
15
+ execSync(`node "${vitest}" run`, { cwd: pkgDir, stdio: "inherit" });
16
+ };
@@ -0,0 +1,14 @@
1
+ const readline = require("readline/promises");
2
+
3
+ const confirm = async (message) => {
4
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
5
+
6
+ try {
7
+ const answer = await rl.question(message);
8
+ return /^y(es)?$/i.test(answer.trim());
9
+ } finally {
10
+ rl.close();
11
+ }
12
+ };
13
+
14
+ module.exports = confirm;
@@ -0,0 +1,13 @@
1
+ const formatPackageRef = (pkg) => `${pkg.name}@${pkg.version ?? "0.0.0"}`;
2
+
3
+ const formatPackageLine = (pkg, options = {}) => {
4
+ const { prefix = "==>", tag, indent = "", leadingNewline = false } = options;
5
+ const tagPart = tag ? `${tag} ` : "";
6
+
7
+ return `${leadingNewline ? "\n" : ""}${indent}${prefix} ${tagPart}${formatPackageRef(pkg)} (${pkg.dir})`;
8
+ };
9
+
10
+ module.exports = {
11
+ formatPackageRef,
12
+ formatPackageLine,
13
+ };
@@ -25,6 +25,24 @@ const buildDependencyGraph = (packages) => {
25
25
  return { graph, byName };
26
26
  };
27
27
 
28
+ const expandDependencyClosure = (packageNames, graph, byName) => {
29
+ const expanded = new Set(packageNames);
30
+ const queue = [...packageNames];
31
+
32
+ while (queue.length) {
33
+ const name = queue.shift();
34
+
35
+ for (const dep of graph.get(name) ?? []) {
36
+ if (!byName.has(dep) || expanded.has(dep)) continue;
37
+
38
+ expanded.add(dep);
39
+ queue.push(dep);
40
+ }
41
+ }
42
+
43
+ return [...expanded];
44
+ };
45
+
28
46
  const restrictGraph = (graph, packageNames) => {
29
47
  const names = new Set(packageNames);
30
48
  const restricted = new Map();
@@ -42,5 +60,6 @@ const restrictGraph = (graph, packageNames) => {
42
60
  module.exports = {
43
61
  DEPENDENCY_FIELDS,
44
62
  buildDependencyGraph,
63
+ expandDependencyClosure,
45
64
  restrictGraph,
46
65
  };
@@ -1,5 +1,6 @@
1
1
  const readPackageJson = require("./read-package-json");
2
2
  const matchVersion = require("./match-version");
3
+ const isStubTestScript = require("./is-stub-test-script");
3
4
 
4
5
  const DEFAULT_EXCLUDED_DIRS = ["cli"];
5
6
 
@@ -24,7 +25,12 @@ module.exports = (packages, options = {}) => {
24
25
  if (options.script) {
25
26
  result = result.filter((pkg) => {
26
27
  const pkgJson = readPackageJson(pkg.absDir);
27
- return Boolean(pkgJson.scripts?.[options.script]);
28
+ const script = pkgJson.scripts?.[options.script];
29
+
30
+ if (!script) return false;
31
+ if (options.script === "test" && isStubTestScript(script)) return false;
32
+
33
+ return true;
28
34
  });
29
35
  }
30
36
 
@@ -0,0 +1,5 @@
1
+ const STUB_TEST_SCRIPT = 'echo "Error: no test specified" && exit 1';
2
+
3
+ module.exports = (script) => script === STUB_TEST_SCRIPT;
4
+
5
+ module.exports.STUB_TEST_SCRIPT = STUB_TEST_SCRIPT;
@@ -18,9 +18,13 @@ const expandWorkspacePattern = (root, pattern) => {
18
18
  if (part === "**") {
19
19
  if (!existsSync(dir)) return;
20
20
 
21
+ walk(dir, idx + 1);
22
+
21
23
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
22
24
  if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "dist") {
23
- walk(path.join(dir, entry.name), idx + 1);
25
+ const subdir = path.join(dir, entry.name);
26
+ walk(subdir, idx + 1);
27
+ walk(subdir, idx);
24
28
  }
25
29
  }
26
30
 
@@ -1,26 +1,28 @@
1
1
  const listWorkspacePackages = require("./list-packages");
2
2
  const filterPackages = require("./filter-packages");
3
- const { buildDependencyGraph, restrictGraph } = require("./build-graph");
3
+ const { buildDependencyGraph, expandDependencyClosure, restrictGraph } = require("./build-graph");
4
4
  const { topoSort, topoSortLayers } = require("./topo-sort");
5
5
 
6
6
  module.exports = (options = {}) => {
7
7
  const { root, packages } = listWorkspacePackages({ cwd: options.cwd });
8
- const selected = filterPackages(packages, options);
8
+ let selected = filterPackages(packages, options);
9
9
 
10
10
  if (!selected.length) {
11
11
  return { root, selected: [], order: [], layers: [], byName: new Map() };
12
12
  }
13
13
 
14
14
  const byName = new Map(packages.map((pkg) => [pkg.name, pkg]));
15
- const restricted = restrictGraph(
16
- buildDependencyGraph(packages).graph,
17
- selected.map((pkg) => pkg.name),
18
- );
19
- const layerNames = topoSortLayers(
20
- restricted,
21
- selected.map((pkg) => pkg.name),
22
- );
23
- const order = topoSort(restricted, selected.map((pkg) => pkg.name)).map((name) => byName.get(name));
15
+ const { graph } = buildDependencyGraph(packages);
16
+ let packageNames = selected.map((pkg) => pkg.name);
17
+
18
+ if (options.includeDependencies && packageNames.length) {
19
+ packageNames = expandDependencyClosure(packageNames, graph, byName);
20
+ selected = packageNames.map((name) => byName.get(name)).filter(Boolean);
21
+ }
22
+
23
+ const restricted = restrictGraph(graph, packageNames);
24
+ const layerNames = topoSortLayers(restricted, packageNames);
25
+ const order = topoSort(restricted, packageNames).map((name) => byName.get(name));
24
26
  const layers = layerNames.map((layer) => layer.map((name) => byName.get(name)));
25
27
 
26
28
  return { root, selected, order, layers, byName };