@vistta/cli 1.1.0 → 1.2.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.
@@ -0,0 +1,150 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "VISTTA project configuration",
4
+ "description": "VISTTA settings in tsconfig.json or jsconfig.json. Other TypeScript and framework properties are preserved.",
5
+ "type": "object",
6
+ "properties": {
7
+ "extends": {
8
+ "description": "Relative or package presets, applied from left to right before local settings.",
9
+ "oneOf": [
10
+ {
11
+ "type": "string"
12
+ },
13
+ {
14
+ "$ref": "#/definitions/stringList"
15
+ }
16
+ ]
17
+ },
18
+ "compilerOptions": {
19
+ "type": "object",
20
+ "description": "Compiler options used unchanged by TypeScript, with noEmit enabled for checks."
21
+ },
22
+ "include": {
23
+ "$ref": "#/definitions/stringList"
24
+ },
25
+ "exclude": {
26
+ "$ref": "#/definitions/stringList"
27
+ },
28
+ "files": {
29
+ "$ref": "#/definitions/stringList"
30
+ },
31
+ "cli": {
32
+ "$ref": "#/definitions/cli"
33
+ },
34
+ "cliOptions": {
35
+ "$ref": "#/definitions/cli",
36
+ "description": "Legacy alias. The cli block takes precedence."
37
+ },
38
+ "bundlerOptions": {
39
+ "type": "object"
40
+ }
41
+ },
42
+ "definitions": {
43
+ "stringList": {
44
+ "type": "array",
45
+ "items": {
46
+ "type": "string"
47
+ }
48
+ },
49
+ "cli": {
50
+ "type": "object",
51
+ "properties": {
52
+ "commands": {
53
+ "type": "object",
54
+ "additionalProperties": {
55
+ "type": "string"
56
+ }
57
+ },
58
+ "check": {
59
+ "type": "object",
60
+ "additionalProperties": false,
61
+ "properties": {
62
+ "format": {
63
+ "type": "boolean",
64
+ "default": true
65
+ },
66
+ "lint": {
67
+ "type": "boolean",
68
+ "default": true
69
+ },
70
+ "types": {
71
+ "type": "boolean",
72
+ "default": true
73
+ }
74
+ }
75
+ },
76
+ "format": {
77
+ "type": "object",
78
+ "additionalProperties": false,
79
+ "properties": {
80
+ "engine": {
81
+ "const": "prettier",
82
+ "default": "prettier"
83
+ },
84
+ "options": {
85
+ "type": "object",
86
+ "description": "Prettier options overriding any discovered or referenced configuration."
87
+ },
88
+ "config": {
89
+ "type": "string",
90
+ "description": "Optional module exporting Prettier options as its default export."
91
+ },
92
+ "include": {
93
+ "$ref": "#/definitions/stringList"
94
+ },
95
+ "exclude": {
96
+ "$ref": "#/definitions/stringList"
97
+ }
98
+ }
99
+ },
100
+ "lint": {
101
+ "type": "object",
102
+ "additionalProperties": false,
103
+ "properties": {
104
+ "engine": {
105
+ "const": "eslint",
106
+ "default": "eslint"
107
+ },
108
+ "rules": {
109
+ "type": "object",
110
+ "description": "ESLint rules applied after the existing or referenced flat config."
111
+ },
112
+ "languageOptions": {
113
+ "type": "object"
114
+ },
115
+ "options": {
116
+ "type": "object",
117
+ "description": "Additional JSON-compatible ESLint flat-config properties, such as settings or files."
118
+ },
119
+ "config": {
120
+ "type": "string",
121
+ "description": "Optional module exporting an ESLint flat config or array. Replaces config-file discovery."
122
+ },
123
+ "include": {
124
+ "$ref": "#/definitions/stringList"
125
+ },
126
+ "exclude": {
127
+ "$ref": "#/definitions/stringList"
128
+ }
129
+ }
130
+ },
131
+ "tasks": {
132
+ "type": "object",
133
+ "additionalProperties": {
134
+ "type": "object",
135
+ "additionalProperties": false,
136
+ "properties": {
137
+ "dependsOn": {
138
+ "$ref": "#/definitions/stringList"
139
+ },
140
+ "script": {
141
+ "type": "string",
142
+ "description": "A script name from the current package.json. Dependencies run first."
143
+ }
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
package/index.js CHANGED
@@ -6,5 +6,8 @@ export { TestRunner } from "./classes/test-runner.js";
6
6
  export { default as BundleCommand } from "./commands/bundle.js";
7
7
  export { default as DefaultCommand } from "./commands/default.js";
8
8
  export { default as ProjectCommand } from "./commands/project.js";
9
+ export { default as QualityCommand } from "./commands/quality.js";
10
+ export { default as TaskCommand } from "./commands/task.js";
9
11
  export { default as TestCommand } from "./commands/test.js";
12
+ export { loadProjectConfig } from "./config.js";
10
13
  export { parseArgs } from "./utils.js";
package/package.json CHANGED
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "name": "@vistta/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "exports": {
7
7
  ".": "./index.js",
8
8
  "./global": "./global.d.ts",
9
9
  "./config": "./tsconfig.json",
10
+ "./schema": "./config.schema.json",
10
11
  "./default": "./commands/default.js",
11
12
  "./bundle": "./commands/bundle.js",
12
13
  "./project": "./commands/project.js",
13
14
  "./test": "./commands/test.js",
15
+ "./quality": "./commands/quality.js",
16
+ "./task": "./commands/task.js",
14
17
  "./test-runner": "./classes/test-runner.js",
15
18
  "./loaders/bundler": "./loaders/bundler.js",
16
19
  "./loaders/file": "./loaders/file.js",
@@ -28,10 +31,11 @@
28
31
  "node": ">=22.0.0"
29
32
  },
30
33
  "dependencies": {
31
- "@vistta/console": "latest",
32
- "@vistta/fs": "latest",
33
- "@vistta/utils": "latest",
34
+ "@vistta/console": "^1.1.0",
35
+ "@vistta/fs": "^1.1.0",
36
+ "@vistta/utils": "^1.1.0",
34
37
  "esbuild": "^0.28.2",
38
+ "jsonc-parser": "^3.3.1",
35
39
  "semver": "^7.8.5"
36
40
  },
37
41
  "author": "Tiago Terenas Almeida",
package/register.js CHANGED
@@ -33,6 +33,13 @@ const config = await importConfig({
33
33
  bundle: resolve(import.meta.dirname, "./commands/bundle.js"),
34
34
  project: resolve(import.meta.dirname, "./commands/project.js"),
35
35
  test: resolve(import.meta.dirname, "./commands/test.js"),
36
+ check: resolve(import.meta.dirname, "./commands/quality.js"),
37
+ lint: resolve(import.meta.dirname, "./commands/quality.js"),
38
+ fmt: resolve(import.meta.dirname, "./commands/quality.js"),
39
+ types: resolve(import.meta.dirname, "./commands/quality.js"),
40
+ config: resolve(import.meta.dirname, "./commands/quality.js"),
41
+ run: resolve(import.meta.dirname, "./commands/task.js"),
42
+ task: resolve(import.meta.dirname, "./commands/task.js"),
36
43
  },
37
44
  },
38
45
  });
@@ -0,0 +1,217 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { loadProjectConfig } from "../config.js";
8
+ import { planTasks, runChecks, runTasks, toolFiles } from "../tools.js";
9
+
10
+ function fixture(files) {
11
+ const cwd = mkdtempSync(join(tmpdir(), "vistta-config-"));
12
+ for (const [path, content] of Object.entries(files)) {
13
+ mkdirSync(dirname(join(cwd, path)), { recursive: true });
14
+ writeFileSync(join(cwd, path), typeof content === "string" ? content : JSON.stringify(content));
15
+ }
16
+ return cwd;
17
+ }
18
+
19
+ suite("CLI configuration", async () => {
20
+ await test("loads JSONC, relative presets, legacy settings, arrays, and setting sources", () => {
21
+ const cwd = fixture({
22
+ "presets/base.json": {
23
+ compilerOptions: { checkJs: true },
24
+ include: ["src/**/*.js"],
25
+ cliOptions: { check: { format: false, lint: true }, commands: { custom: "./custom.js" } },
26
+ cli: { format: { options: { semi: false } } },
27
+ },
28
+ "presets/custom.js": "export default class {}",
29
+ "tsconfig.json":
30
+ '{ // comment\n "extends": "./presets/base", "include": ["app/*.js"], "cli": { "check": { "types": false }, "format": { "options": { "singleQuote": true } } }, }',
31
+ });
32
+ try {
33
+ const result = loadProjectConfig(cwd);
34
+ assert.deepEqual(result.config.cli.check, { format: false, lint: true, types: false });
35
+ assert.deepEqual(result.config.include, ["app/*.js"]);
36
+ assert.deepEqual(result.config.cli.format.options, { semi: false, singleQuote: true });
37
+ assert.equal(fileURLToPath(result.config.cli.commands.custom), join(cwd, "presets/custom.js"));
38
+ assert.equal(result.sources["cli.format.options.semi"], join(cwd, "presets/base.json"));
39
+ assert.equal(result.sources["cli.format.options.singleQuote"], join(cwd, "tsconfig.json"));
40
+ } finally {
41
+ rmSync(cwd, { recursive: true, force: true });
42
+ }
43
+ });
44
+
45
+ await test("supports package presets, jsconfig, and multiple ordered parents", () => {
46
+ const cwd = fixture({
47
+ "node_modules/preset/package.json": { name: "preset", exports: { "./config": "./config.json" } },
48
+ "node_modules/preset/config.json": { cli: { format: { options: { semi: true, singleQuote: false } } } },
49
+ "second.json": { cli: { format: { options: { semi: false } } } },
50
+ "jsconfig.json": { extends: ["preset/config", "./second.json"] },
51
+ });
52
+ try {
53
+ assert.deepEqual(loadProjectConfig(cwd).config.cli.format.options, { semi: false, singleQuote: false });
54
+ } finally {
55
+ rmSync(cwd, { recursive: true, force: true });
56
+ }
57
+ });
58
+
59
+ await test("rejects malformed, circular, and invalid configuration", () => {
60
+ const cwd = fixture({ "tsconfig.json": "{broken}" });
61
+ try {
62
+ assert.throws(() => loadProjectConfig(cwd), /Invalid configuration/);
63
+ writeFileSync(join(cwd, "tsconfig.json"), JSON.stringify({ extends: "./tsconfig.json" }));
64
+ assert.throws(() => loadProjectConfig(cwd), /Circular/);
65
+ for (const cli of [
66
+ { check: { types: "yes" } },
67
+ { lint: { engine: "unknown" } },
68
+ { format: { include: "*.js" } },
69
+ { tasks: { build: { dependsOn: "test" } } },
70
+ ]) {
71
+ writeFileSync(join(cwd, "tsconfig.json"), JSON.stringify({ cli }));
72
+ assert.throws(() => loadProjectConfig(cwd));
73
+ }
74
+ } finally {
75
+ rmSync(cwd, { recursive: true, force: true });
76
+ }
77
+ });
78
+
79
+ await test("resolves inherited file patterns relative to their config and excludes dependencies", () => {
80
+ const cwd = fixture({
81
+ "preset/base.json": { include: ["src/**/*.js"], exclude: ["src/ignored/**"] },
82
+ "tsconfig.json": { extends: "./preset/base.json" },
83
+ "preset/src/main.js": "export {};",
84
+ "preset/src/ignored/skip.js": "export {};",
85
+ "preset/src/node_modules/skip/index.js": "export {};",
86
+ });
87
+ try {
88
+ assert.deepEqual(toolFiles(loadProjectConfig(cwd), "lint", [], cwd), [join(cwd, "preset/src/main.js")]);
89
+ assert.deepEqual(toolFiles(loadProjectConfig(cwd), "lint", ["preset/src/ignored/*.js"], cwd), []);
90
+ } finally {
91
+ rmSync(cwd, { recursive: true, force: true });
92
+ }
93
+ });
94
+
95
+ await test("orders dependencies once, rejects cycles and missing tasks, and stops on failure", async () => {
96
+ const tasks = {
97
+ validate: { dependsOn: ["lint", "test"], script: "finish" },
98
+ test: { dependsOn: ["lint"], script: "unit" },
99
+ };
100
+ const plan = planTasks("validate", tasks, { unit: "node unit.js", finish: "node finish.js" });
101
+ assert.deepEqual(
102
+ plan.map((task) => task.name),
103
+ ["lint", "test", "validate"],
104
+ );
105
+ assert.throws(() => planTasks("bad", { bad: { dependsOn: ["bad"] } }), /Circular/);
106
+ assert.throws(() => planTasks("unknown"), /Unknown/);
107
+ assert.throws(() => planTasks("build"), /Unknown/);
108
+ assert.deepEqual(planTasks("build", {}, {}, ["build"]), [{ name: "build", command: "build" }]);
109
+ assert.throws(() => planTasks("bad", { bad: { script: "missing" } }), /Unknown package script/);
110
+ const calls = [];
111
+ assert.equal(
112
+ await runTasks(plan, process.cwd(), [], async (args) => {
113
+ calls.push(args);
114
+ return 2;
115
+ }),
116
+ 2,
117
+ );
118
+ assert.equal(calls.length, 1);
119
+ calls.length = 0;
120
+ assert.equal(
121
+ await runTasks(plan, process.cwd(), ["--flag"], async (args) => {
122
+ calls.push(args);
123
+ return 0;
124
+ }),
125
+ 0,
126
+ );
127
+ assert.deepEqual(calls.at(-1), ["--run", "finish", "--", "--flag"]);
128
+ });
129
+ });
130
+
131
+ suite("CLI tool adapters", async () => {
132
+ await test("formats and lints temporary files with project-local tools and checks types without emitting", async () => {
133
+ const root = resolve(import.meta.dirname, "../../..");
134
+ const cwd = fixture({
135
+ "package.json": { name: "fixture", type: "module" },
136
+ "tsconfig.json": {
137
+ include: ["src/*.js"],
138
+ compilerOptions: { allowJs: true, checkJs: true, noEmit: true, types: [], target: "ES2022" },
139
+ cli: {
140
+ lint: {
141
+ engine: "eslint",
142
+ config: "./eslint.config.mjs",
143
+ rules: { "no-unused-vars": "error", semi: ["error", "always"] },
144
+ },
145
+ format: { engine: "prettier", config: "./prettier.config.mjs", options: { semi: true, singleQuote: false } },
146
+ },
147
+ },
148
+ "src/main.js": "export const value=1",
149
+ "eslint.config.mjs": 'export default [{ rules: { semi: ["error", "never"] } }];',
150
+ "prettier.config.mjs": "export default { semi: false };",
151
+ "node_modules/eslint/package.json": { main: join(root, "node_modules/eslint/lib/api.js") },
152
+ "node_modules/prettier/package.json": { main: join(root, "node_modules/prettier/index.cjs") },
153
+ "node_modules/typescript/package.json": { main: join(root, "node_modules/typescript/lib/typescript.js") },
154
+ });
155
+ try {
156
+ const project = loadProjectConfig(cwd);
157
+ assert.equal(await runChecks(["format"], project, { cwd }), 1);
158
+ assert.equal(readFileSync(join(cwd, "src/main.js"), "utf8"), "export const value=1");
159
+ assert.equal(await runChecks(["format"], project, { cwd, fix: true }), 0);
160
+ assert.equal(readFileSync(join(cwd, "src/main.js"), "utf8"), "export const value = 1;\n");
161
+ assert.equal(await runChecks(["lint", "types"], project, { cwd }), 0);
162
+ writeFileSync(join(cwd, "src/main.js"), "const unused = 1;\nexport const value = 1;\nvalue();\n");
163
+ assert.equal(await runChecks(["lint", "types"], project, { cwd }), 1);
164
+ assert.equal(existsSync(join(cwd, "src/main.d.ts")), false);
165
+ } finally {
166
+ rmSync(cwd, { recursive: true, force: true });
167
+ }
168
+ });
169
+ });
170
+
171
+ suite("CLI public commands", async () => {
172
+ await test("propagates check, fix, config, and task results through the executable", () => {
173
+ const root = resolve(import.meta.dirname, "../../..");
174
+ const bin = fileURLToPath(new URL("../bin.js", import.meta.url));
175
+ const cwd = fixture({
176
+ "package.json": {
177
+ name: "fixture",
178
+ scripts: { smoke: "node success.js", fail: "node failure.js", args: "node arguments.js" },
179
+ },
180
+ "tsconfig.json": {
181
+ files: ["main.js"],
182
+ cli: {
183
+ check: { lint: false, types: false },
184
+ format: { options: { semi: true } },
185
+ tasks: { validate: { dependsOn: ["check"], script: "smoke" } },
186
+ },
187
+ },
188
+ "main.js": "const value=1",
189
+ "success.js": "process.exit(0);",
190
+ "failure.js": "process.exit(7);",
191
+ "arguments.js": 'if (process.argv[2] !== "--help") process.exit(3); console.log("FORWARDED");',
192
+ "node_modules/prettier/package.json": { main: join(root, "node_modules/prettier/index.cjs") },
193
+ });
194
+ const invoke = (...args) => spawnSync(process.execPath, [bin, ...args], { cwd, encoding: "utf8" });
195
+ try {
196
+ assert.equal(invoke("check").status, 1);
197
+ const fixed = invoke("check", "--fix");
198
+ assert.equal(fixed.status, 0, fixed.stdout + fixed.stderr);
199
+ assert.equal(readFileSync(join(cwd, "main.js"), "utf8"), "const value = 1;\n");
200
+ const config = invoke("config", "--sources");
201
+ assert.equal(config.status, 0, config.stdout + config.stderr);
202
+ const effective = JSON.parse(config.stdout.slice(config.stdout.indexOf("{")));
203
+ assert.equal(effective.config.cli.check.lint, false);
204
+ assert.equal(effective.sources["cli.check.lint"], join(cwd, "tsconfig.json"));
205
+ const task = invoke("task", "validate");
206
+ assert.equal(task.status, 0, task.stdout + task.stderr);
207
+ assert.equal(invoke("run", "smoke").status, 0);
208
+ const forwarded = invoke("task", "args", "--", "--help");
209
+ assert.equal(forwarded.status, 0, forwarded.stdout + forwarded.stderr);
210
+ assert.match(forwarded.stdout, /FORWARDED/);
211
+ assert.notEqual(invoke("task", "fail").status, 0);
212
+ assert.notEqual(invoke("task", "missing").status, 0);
213
+ } finally {
214
+ rmSync(cwd, { recursive: true, force: true });
215
+ }
216
+ });
217
+ });
@@ -1,4 +1,9 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
1
5
  import { Command } from "../classes/command.js";
6
+ import { getOutdatedPackages } from "../utils.js";
2
7
 
3
8
  suite("CLI", () => {
4
9
  let cmd;
@@ -36,3 +41,138 @@ suite("CLI", () => {
36
41
  expect(process.env["BAR"]).toEqual("baz");
37
42
  });
38
43
  });
44
+
45
+ async function checkNpmReport(report, code, packages = {}, workspace = "") {
46
+ const dirname = mkdtempSync(join(tmpdir(), "vistta-outdated-"));
47
+ const npmPath = process.env.npm_execpath;
48
+ try {
49
+ writeFileSync(join(dirname, "package.json"), JSON.stringify({ name: "fixture", private: true }));
50
+ writeFileSync(join(dirname, "package-lock.json"), JSON.stringify({ packages }));
51
+ const script = join(dirname, "npm.cjs");
52
+ writeFileSync(
53
+ script,
54
+ [
55
+ "const assert = require('node:assert/strict');",
56
+ "assert.deepEqual(process.argv.slice(2), ['outdated', '--json', '--long', '--include=dev']);",
57
+ `process.stdout.write(${JSON.stringify(JSON.stringify(report))});`,
58
+ `process.exit(${code});`,
59
+ ].join("\n"),
60
+ );
61
+ process.env.npm_execpath = script;
62
+ const cwd = join(dirname, workspace);
63
+ if (workspace) {
64
+ mkdirSync(cwd, { recursive: true });
65
+ writeFileSync(join(cwd, "package.json"), JSON.stringify({ name: "workspace" }));
66
+ }
67
+ return { dirname, modules: await getOutdatedPackages(cwd) };
68
+ } finally {
69
+ if (npmPath === undefined) delete process.env.npm_execpath;
70
+ else process.env.npm_execpath = npmPath;
71
+ rmSync(dirname, { recursive: true, force: true });
72
+ }
73
+ }
74
+
75
+ suite("CLI outdated", async () => {
76
+ await test("uses npm wanted versions and preserves separate workspace consumers", async () => {
77
+ const { dirname, modules } = await checkNpmReport(
78
+ {
79
+ shared: [
80
+ { current: "1.0.0", wanted: "1.5.0", latest: "2.0.0", type: "dependencies", dependedByLocation: "packages/first" },
81
+ {
82
+ current: "1.0.0",
83
+ wanted: "1.0.0",
84
+ latest: "2.0.0",
85
+ type: "devDependencies",
86
+ dependedByLocation: "packages/second",
87
+ },
88
+ ],
89
+ linked: { current: "2.0.0", wanted: "1.0.0", latest: "1.0.0", type: "dependencies" },
90
+ peer: { current: "1.0.0", wanted: "2.0.0", latest: "2.0.0", type: "peerDependencies" },
91
+ },
92
+ 1,
93
+ { "node_modules/linked": { link: true } },
94
+ );
95
+ assert.deepEqual(modules, [
96
+ {
97
+ name: "shared",
98
+ package: join(dirname, "packages/first/package.json"),
99
+ current: "1.0.0",
100
+ wanted: "1.5.0",
101
+ latest: "2.0.0",
102
+ dev: false,
103
+ },
104
+ {
105
+ name: "shared",
106
+ package: join(dirname, "packages/second/package.json"),
107
+ current: "1.0.0",
108
+ wanted: "1.0.0",
109
+ latest: "2.0.0",
110
+ dev: true,
111
+ },
112
+ ]);
113
+ });
114
+
115
+ await test("accepts an empty npm report", async () => {
116
+ const { modules } = await checkNpmReport({}, 0);
117
+ assert.deepEqual(modules, []);
118
+ });
119
+
120
+ await test("resolves workspace owners relative to the lockfile when invoked inside a package", async () => {
121
+ const { dirname, modules } = await checkNpmReport(
122
+ {
123
+ dependency: {
124
+ current: "1.0.0",
125
+ wanted: "1.1.0",
126
+ latest: "1.1.0",
127
+ type: "dependencies",
128
+ dependedByLocation: "packages/child",
129
+ },
130
+ },
131
+ 1,
132
+ {},
133
+ "packages/child",
134
+ );
135
+ assert.equal(modules[0].package, join(dirname, "packages/child/package.json"));
136
+ });
137
+
138
+ await test("surfaces npm errors instead of reporting everything up to date", async () => {
139
+ await assert.rejects(checkNpmReport({ error: { summary: "Registry unavailable" } }, 1), /Registry unavailable/);
140
+ });
141
+
142
+ await test("limits package checks to their own dependency consumers", async () => {
143
+ const { dirname, modules } = await checkNpmReport(
144
+ {
145
+ root: { current: "1.0.0", wanted: "1.1.0", latest: "1.1.0", type: "dependencies" },
146
+ shared: [
147
+ {
148
+ current: "1.0.0",
149
+ wanted: "1.1.0",
150
+ latest: "1.1.0",
151
+ type: "devDependencies",
152
+ dependedByLocation: "packages/child",
153
+ },
154
+ {
155
+ current: "1.0.0",
156
+ wanted: "1.2.0",
157
+ latest: "1.2.0",
158
+ type: "dependencies",
159
+ dependedByLocation: "packages/sibling",
160
+ },
161
+ ],
162
+ },
163
+ 1,
164
+ {},
165
+ "packages/child",
166
+ );
167
+ assert.deepEqual(modules, [
168
+ {
169
+ name: "shared",
170
+ package: join(dirname, "packages/child/package.json"),
171
+ current: "1.0.0",
172
+ wanted: "1.1.0",
173
+ latest: "1.1.0",
174
+ dev: true,
175
+ },
176
+ ]);
177
+ });
178
+ });