safeinstall-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-03-31
4
+
5
+ First public release. Open source under MIT license.
6
+
7
+ ### Added
8
+
9
+ - Local CLI wrapper for `npm`, `pnpm`, and `bun`
10
+ - Policy enforcement for release age, lifecycle scripts, untrusted sources, and trust downgrades
11
+ - Lockfile-aware project installs for `pnpm install`, `npm install`, and `npm ci`
12
+ - JSON output for CI and automation
13
+ - `safeinstall init` starter config generation
14
+ - Monorepo-aware package and lockfile discovery for npm and pnpm
15
+ - End-to-end CLI regression coverage and local-repo QA against real projects
16
+ - Configurable registry URL for private mirrors (Verdaccio, Artifactory)
17
+ - On-disk cache for exact-version registry metadata and publish timestamps
18
+ - Graceful SIGINT/SIGTERM shutdown with proper exit codes
19
+ - `--help` / `-h` and `--version` / `-v` flags
20
+
21
+ ### Hardened
22
+
23
+ - Fail-closed behavior for stale, missing, or ambiguous lockfiles
24
+ - Explicit blocking for ambiguous workspace-targeting flags
25
+ - Package-manager mismatch blocking when `package.json` declares a different manager
26
+ - Local workspace and file-based project references handled as local sources instead of external supply-chain inputs
27
+ - Abbreviated registry metadata for faster fetches
28
+ - Concurrency-limited registry requests
29
+
30
+ ### Notes
31
+
32
+ - Package name: `safeinstall-cli` (npm) — command: `safeinstall`
33
+ - `bun install` remains manifest-based for project installs in this release
34
+ - Policy checks apply to direct dependencies only in this release
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 SafeInstall Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,305 @@
1
+ <p align="center">
2
+ <strong>SafeInstall</strong>
3
+ <br />
4
+ <em>Stop risky package installs before they run.</em>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/safeinstall-cli"><img src="https://img.shields.io/npm/v/safeinstall-cli?style=flat-square&color=22c55e" alt="npm version" /></a>
9
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-22c55e?style=flat-square" alt="license" /></a>
10
+ <a href="https://github.com/Mickdownunder/SafeInstall"><img src="https://img.shields.io/github/stars/Mickdownunder/SafeInstall?style=flat-square" alt="stars" /></a>
11
+ <a href="https://github.com/Mickdownunder/SafeInstall"><img src="https://img.shields.io/badge/TypeScript-strict-blue?style=flat-square" alt="TypeScript" /></a>
12
+ <a href="https://safeinstall.dev"><img src="https://img.shields.io/badge/docs-safeinstall.dev-22c55e?style=flat-square" alt="docs" /></a>
13
+ </p>
14
+
15
+ <p align="center">
16
+ Local-first CLI wrapper for <strong>npm</strong>, <strong>pnpm</strong>, and <strong>bun</strong>.<br />
17
+ Policy runs first. Then your package manager. Not the other way around.<br /><br />
18
+ Open source · MIT licensed · Free forever
19
+ </p>
20
+
21
+ ---
22
+
23
+ ## Why SafeInstall
24
+
25
+ AI coding tools suggest packages in seconds. They don't check publish dates. They don't read install scripts. They don't verify the source. You type "yes" and move on.
26
+
27
+ SafeInstall is the gate between suggestion and execution.
28
+
29
+ ```
30
+ $ safeinstall pnpm add compromised-pkg@9.9.9
31
+
32
+ Install blocked.
33
+ - compromised-pkg@9.9.9
34
+ Blocked: release too new (published 3 hours ago; minimum is 72 hours).
35
+ Blocked: install script present (has postinstall).
36
+ ```
37
+
38
+ No dashboard. No account. No cloud. One command prefix — policy runs locally, blocks by default, then invokes the real tool.
39
+
40
+ ---
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ npm install -g safeinstall-cli
46
+ ```
47
+
48
+ > Node.js >=20 · macOS, Linux, Windows · Command: `safeinstall`
49
+
50
+ ## Quickstart
51
+
52
+ ```bash
53
+ safeinstall init # create safeinstall.config.json
54
+ safeinstall pnpm add axios # policy runs, then pnpm
55
+ safeinstall npm install # lockfile-aware project install
56
+ safeinstall check # audit direct deps against policy
57
+ ```
58
+
59
+ ---
60
+
61
+ ## How it works
62
+
63
+ ```
64
+ ┌─────────────────────┐ ┌──────────────┐ ┌─────────────────┐
65
+ │ safeinstall pnpm │ ──▶ │ Resolve & │ ──▶ │ Policy check │
66
+ │ add axios │ │ fetch meta │ │ (age, scripts, │
67
+ └─────────────────────┘ └──────────────┘ │ sources, ...) │
68
+ └────────┬────────┘
69
+
70
+ ┌─────────▼─────────┐
71
+ pass → │ Invoke pnpm add │
72
+ fail → │ Exit 2 (blocked) │
73
+ └───────────────────┘
74
+ ```
75
+
76
+ 1. Resolves what would be installed
77
+ 2. Fetches registry metadata (publish time, declared scripts)
78
+ 3. Evaluates policy rules
79
+ 4. Blocks (exit 2) or invokes the real package manager
80
+
81
+ No registry proxy. No tarball rewriting. No cloud dependency.
82
+
83
+ ---
84
+
85
+ ## Policy defaults
86
+
87
+ | Rule | Default | Block message |
88
+ |:---|:---|:---|
89
+ | **Release age** | 72 hours minimum | `Blocked: release too new` |
90
+ | **Lifecycle scripts** | preinstall, install, postinstall blocked | `Blocked: install script present` |
91
+ | **Source types** | registry, workspace, file, directory allowed | `Blocked: untrusted source` |
92
+ | **Trust downgrade** | Detects registry→git/url or new scripts on update | `Blocked: trust level dropped` |
93
+
94
+ All rules are configurable. Ambiguous or incomplete metadata **blocks instead of allowing**.
95
+
96
+ ---
97
+
98
+ ## Usage
99
+
100
+ ```bash
101
+ # Ad-hoc installs
102
+ safeinstall pnpm add axios
103
+ safeinstall npm install react@19.2.0
104
+ safeinstall bun add zod
105
+
106
+ # Project installs (lockfile-aware for npm/pnpm)
107
+ safeinstall pnpm install
108
+ safeinstall npm ci
109
+
110
+ # Monorepo — target one package
111
+ safeinstall pnpm -C packages/app install
112
+ safeinstall npm --prefix packages/app ci
113
+
114
+ # Utilities
115
+ safeinstall check # direct dependency audit
116
+ safeinstall check --json # machine-readable
117
+ safeinstall init # create starter config
118
+ safeinstall init --force # overwrite existing config
119
+ safeinstall --help
120
+ safeinstall --version
121
+
122
+ # JSON output (CI/automation)
123
+ safeinstall --json pnpm add axios
124
+ ```
125
+
126
+ ## Project installs
127
+
128
+ For `pnpm install` and `npm install` / `npm ci`, dependency versions come from the lockfile — not loose ranges in `package.json`.
129
+
130
+ - `pnpm-lock.yaml` for pnpm
131
+ - `package-lock.json` or `npm-shrinkwrap.json` for npm
132
+ - Stale, missing, or mismatched lockfile entries **fail closed**
133
+ - If `packageManager` is set in `package.json`, using a different CLI is blocked
134
+ - Workspace-targeting flags (`--filter`, `--workspace`) are blocked — use `-C` or `--prefix`
135
+ - `bun install` uses manifest-oriented analysis (full lockfile parity not yet implemented)
136
+
137
+ ---
138
+
139
+ ## Configuration
140
+
141
+ Optional `safeinstall.config.json` — discovered by walking upward from the project directory.
142
+
143
+ ```json
144
+ {
145
+ "minimumReleaseAgeHours": 72,
146
+ "registryUrl": "https://registry.npmjs.org",
147
+ "allowedScripts": {
148
+ "esbuild": ["postinstall"]
149
+ },
150
+ "allowedSources": ["registry", "workspace", "file", "directory"],
151
+ "allowedPackages": [],
152
+ "ciMode": false,
153
+ "packageManagerDefaults": {
154
+ "npm": { "ignoreScripts": true },
155
+ "pnpm": { "ignoreScripts": true },
156
+ "bun": { "ignoreScripts": true }
157
+ }
158
+ }
159
+ ```
160
+
161
+ | Field | Purpose |
162
+ |:---|:---|
163
+ | `minimumReleaseAgeHours` | Minimum age in hours for registry versions |
164
+ | `registryUrl` | npm-compatible registry URL for metadata (mirrors, Artifactory, Verdaccio) |
165
+ | `allowedScripts` | Per-package lifecycle script exceptions |
166
+ | `allowedSources` | Permitted source types |
167
+ | `allowedPackages` | Names that skip policy entirely (with warning) |
168
+ | `ciMode` | Reserved for future CI-specific behavior |
169
+ | `packageManagerDefaults` | Per-manager flags forwarded to the tool |
170
+
171
+ Run `safeinstall init` to generate a starter config.
172
+
173
+ ---
174
+
175
+ ## Exit codes
176
+
177
+ | Code | Meaning |
178
+ |:---|:---|
179
+ | `0` | Allowed / check passed |
180
+ | `1` | Runtime or config error |
181
+ | `2` | Blocked by policy |
182
+
183
+ Use exit code 2 like any other failing step in a CI pipeline.
184
+
185
+ ## JSON output
186
+
187
+ Pass `--json` anywhere in the command. Structured output goes to stdout.
188
+
189
+ ```bash
190
+ safeinstall --json pnpm add axios
191
+ ```
192
+
193
+ Fields: `command`, `commandString`, `packageManager`, `decision`, `summary`, `reasons`, `warnings`, `affectedPackages`, `exitCode`, `exitCodeMeaning`. Allowed installs include `execution.stdout` and `execution.stderr`.
194
+
195
+ ---
196
+
197
+ ## Examples
198
+
199
+ <details>
200
+ <summary><strong>Fresh release blocked</strong></summary>
201
+
202
+ ```
203
+ $ safeinstall pnpm add axios
204
+ Using config: built-in defaults
205
+ Install blocked.
206
+ - axios@1.14.0
207
+ Blocked: release too new (axios@1.14.0 is 6 hours old; minimum is 72 hours).
208
+ Suggestion: Retry later or lower minimumReleaseAgeHours if this package is intentionally urgent.
209
+ ```
210
+
211
+ </details>
212
+
213
+ <details>
214
+ <summary><strong>Git source blocked</strong></summary>
215
+
216
+ ```
217
+ $ safeinstall npm install github:axios/axios
218
+ Using config: built-in defaults
219
+ Install blocked.
220
+ - github:axios/axios
221
+ Blocked: untrusted source (git).
222
+ Suggestion: Use a registry release or allow this source intentionally.
223
+ ```
224
+
225
+ </details>
226
+
227
+ <details>
228
+ <summary><strong>Package manager mismatch</strong></summary>
229
+
230
+ ```
231
+ $ safeinstall npm install
232
+ Using config: built-in defaults
233
+ Install blocked.
234
+ - Project install blocked: package.json declares pnpm as packageManager, but this command uses npm.
235
+ ```
236
+
237
+ </details>
238
+
239
+ <details>
240
+ <summary><strong>Stale lockfile</strong></summary>
241
+
242
+ ```
243
+ $ safeinstall pnpm install
244
+ Using config: built-in defaults
245
+ Install blocked.
246
+ - Project install blocked: axios is declared in package.json but missing from pnpm-lock.yaml.
247
+ ```
248
+
249
+ </details>
250
+
251
+ ---
252
+
253
+ ## Limitations
254
+
255
+ - **Not a CVE scanner** — pair with `npm audit` or Snyk for vulnerability data
256
+ - **Transitive dependencies** not fully evaluated yet
257
+ - **`peerDependencies`** not evaluated unless also declared as direct dependencies
258
+ - **Trust downgrade detection** requires prior install state in `node_modules`
259
+ - **`bun install`** uses manifest-only analysis (lockfile parity not yet implemented)
260
+ - **`safeinstall check`** evaluates direct dependencies only
261
+ - Ambiguous metadata blocks instead of guessing — by design
262
+
263
+ ## What it does not do
264
+
265
+ - Vulnerability scanning or CVE databases
266
+ - Registry proxying or tarball rewriting
267
+ - Malware detection or provenance attestation
268
+ - Selective lifecycle script execution (forwards `--ignore-scripts` by default)
269
+
270
+ ---
271
+
272
+ ## Works with
273
+
274
+ SafeInstall works with any tool that runs package manager commands — including AI coding assistants:
275
+
276
+ **Cursor** · **GitHub Copilot** · **Cline** · **Claude Code** · **Windsurf** · **Aider** · **Devin** · **Continue**
277
+
278
+ Just prefix your install commands with `safeinstall`. Same workflow, one safety layer.
279
+
280
+ ---
281
+
282
+ ## Contributing
283
+
284
+ ```bash
285
+ pnpm install
286
+ pnpm typecheck
287
+ pnpm test
288
+ pnpm build
289
+ ```
290
+
291
+ Issues and PRs welcome. Author merges at own discretion — this is a solo-maintained project.
292
+
293
+ ## License
294
+
295
+ MIT — see [LICENSE](./LICENSE).
296
+
297
+ ## Disclaimer
298
+
299
+ SafeInstall is provided as-is under the MIT license. It is a policy tool that enforces configurable rules on package installs. It does not guarantee the safety of any package, does not detect all supply-chain attacks, and does not replace professional security review. Use at your own risk. The authors are not liable for any damages arising from the use of this software.
300
+
301
+ ---
302
+
303
+ <p align="center">
304
+ <a href="https://safeinstall.dev">safeinstall.dev</a> · <a href="https://www.npmjs.com/package/safeinstall">npm</a> · <a href="https://github.com/Mickdownunder/SafeInstall">GitHub</a>
305
+ </p>
package/SUPPORT.md ADDED
@@ -0,0 +1,42 @@
1
+ # Support
2
+
3
+ SafeInstall is a solo-maintained open-source project. Issues and PRs are welcome — the author reviews and merges at own discretion.
4
+
5
+ SafeInstall is designed to fail closed when project metadata is stale, inconsistent, or ambiguous.
6
+
7
+ ## Before Reporting a Problem
8
+
9
+ Run these commands in the affected project:
10
+
11
+ ```bash
12
+ safeinstall --json npm install
13
+ safeinstall --json npm ci
14
+ safeinstall --json pnpm install
15
+ safeinstall check --json
16
+ ```
17
+
18
+ Use the command that matches the package manager and workflow you expected to use.
19
+
20
+ ## Include This Information
21
+
22
+ - SafeInstall version (`safeinstall --version`)
23
+ - Node.js version
24
+ - Package manager and version
25
+ - Exact SafeInstall command
26
+ - Exact JSON output
27
+ - Relevant `packageManager` field from `package.json`
28
+ - Whether the project uses `package-lock.json`, `npm-shrinkwrap.json`, or `pnpm-lock.yaml`
29
+ - Redacted `safeinstall.config.json` if one exists
30
+
31
+ ## Expected Support Boundary
32
+
33
+ - SafeInstall supports direct dependency policy checks
34
+ - SafeInstall supports lockfile-aware project installs for npm and pnpm
35
+ - SafeInstall intentionally blocks ambiguous workspace-targeting commands
36
+ - SafeInstall intentionally blocks when lockfile state is incomplete or conflicting
37
+
38
+ ## Known Limits In 0.1.0
39
+
40
+ - No transitive dependency policy
41
+ - No bun lockfile-aware project install analysis
42
+ - No selective lifecycle-script execution even when scripts are allowlisted
package/dist/async.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mapConcurrent = mapConcurrent;
4
+ async function mapConcurrent(values, concurrency, mapper) {
5
+ if (!Number.isInteger(concurrency) || concurrency < 1) {
6
+ throw new Error(`Concurrency must be a positive integer. Received: ${concurrency}.`);
7
+ }
8
+ if (values.length === 0) {
9
+ return [];
10
+ }
11
+ const results = new Array(values.length);
12
+ let nextIndex = 0;
13
+ async function runWorker() {
14
+ while (true) {
15
+ const currentIndex = nextIndex;
16
+ nextIndex += 1;
17
+ if (currentIndex >= values.length) {
18
+ return;
19
+ }
20
+ results[currentIndex] = await mapper(values[currentIndex], currentIndex);
21
+ }
22
+ }
23
+ const workerCount = Math.min(concurrency, values.length);
24
+ await Promise.all(Array.from({ length: workerCount }, () => runWorker()));
25
+ return results;
26
+ }
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCheckFlow = runCheckFlow;
4
+ const config_1 = require("./config");
5
+ const evaluations_1 = require("./evaluations");
6
+ const output_1 = require("./output");
7
+ const project_state_1 = require("./project-state");
8
+ const project_discovery_1 = require("./project-discovery");
9
+ const project_installs_1 = require("./project-installs");
10
+ const registry_1 = require("./registry");
11
+ const signals_1 = require("./signals");
12
+ const specs_1 = require("./specs");
13
+ function configLabel(configPath) {
14
+ return configPath ?? "built-in defaults";
15
+ }
16
+ function createProjectIssueReason(message) {
17
+ if (message.includes("both pnpm-lock.yaml and an npm lockfile exist")) {
18
+ return {
19
+ code: "ambiguous-lockfiles",
20
+ message,
21
+ suggestion: "Set packageManager in package.json or remove the stale lockfile so SafeInstall can choose one source of truth."
22
+ };
23
+ }
24
+ if (message.includes("required for safeinstall")) {
25
+ return {
26
+ code: "lockfile-required",
27
+ message,
28
+ suggestion: "Create or refresh the lockfile before relying on SafeInstall for project-level checks."
29
+ };
30
+ }
31
+ if (message.includes("specifier")) {
32
+ return {
33
+ code: "lockfile-specifier-mismatch",
34
+ message,
35
+ suggestion: "Regenerate the lockfile so package.json and the lockfile match."
36
+ };
37
+ }
38
+ return {
39
+ code: "check-blocked",
40
+ message,
41
+ suggestion: "Fix the project metadata inconsistency and run safeinstall check again."
42
+ };
43
+ }
44
+ function createAffectedPackage(evaluation) {
45
+ return {
46
+ name: evaluation.requested.name,
47
+ requested: evaluation.requested.raw,
48
+ sourceType: evaluation.requested.sourceType,
49
+ resolvedVersion: evaluation.resolvedRegistryPackage?.resolvedVersion,
50
+ reasons: evaluation.blockedReasons,
51
+ warnings: evaluation.warnings
52
+ };
53
+ }
54
+ async function runCheckFlow(cwd, argv, options = {}) {
55
+ (0, signals_1.throwIfAborted)(options.signal);
56
+ const invocation = await (0, project_discovery_1.resolveInvocationContext)(cwd, []);
57
+ const { config, path } = await (0, config_1.loadConfig)(invocation.effectiveCwd);
58
+ const projectTargets = await (0, project_installs_1.inferProjectInstallTargetsForCheck)(invocation.effectiveCwd, invocation.packageDir);
59
+ const commandString = (0, output_1.formatCommand)("safeinstall", argv);
60
+ if (!invocation.packageDir) {
61
+ return {
62
+ mode: "check",
63
+ decision: "block",
64
+ exitCode: 2,
65
+ exitCodeMeaning: "Check was blocked because the current directory does not map to a package.",
66
+ command: argv,
67
+ commandString,
68
+ configPath: path,
69
+ configLabel: configLabel(path),
70
+ reasons: [
71
+ {
72
+ code: "package-root-not-found",
73
+ message: "Check blocked: the current directory does not map to a package.json-backed project.",
74
+ suggestion: "Run SafeInstall from a package directory."
75
+ }
76
+ ],
77
+ summary: "Check blocked.",
78
+ warnings: [],
79
+ affectedPackages: []
80
+ };
81
+ }
82
+ if (projectTargets?.issues.length) {
83
+ return {
84
+ mode: "check",
85
+ decision: "block",
86
+ exitCode: 2,
87
+ exitCodeMeaning: "Check was blocked because project metadata was incomplete or inconsistent.",
88
+ command: argv,
89
+ commandString,
90
+ configPath: path,
91
+ configLabel: configLabel(path),
92
+ reasons: projectTargets.issues.map(createProjectIssueReason),
93
+ summary: "Check blocked.",
94
+ warnings: [],
95
+ affectedPackages: []
96
+ };
97
+ }
98
+ const requestedPackages = projectTargets
99
+ ? projectTargets.targets.map((target) => target.requested)
100
+ : Object.entries(await (0, project_state_1.loadManifestDependencies)(invocation.packageDir ?? invocation.effectiveCwd)).map(([name, spec]) => (0, specs_1.parseManifestDependency)(name, spec));
101
+ if (requestedPackages.length === 0) {
102
+ return {
103
+ mode: "check",
104
+ decision: "allow",
105
+ exitCode: 0,
106
+ exitCodeMeaning: "Check passed; there were no direct dependencies to evaluate.",
107
+ command: argv,
108
+ commandString,
109
+ configPath: path,
110
+ configLabel: configLabel(path),
111
+ reasons: [],
112
+ summary: "Check skipped: package.json has no direct dependencies.",
113
+ warnings: [],
114
+ affectedPackages: []
115
+ };
116
+ }
117
+ const registryClient = new registry_1.RegistryClient({
118
+ registryUrl: config.registryUrl,
119
+ signal: options.signal
120
+ });
121
+ const evaluations = await (0, evaluations_1.evaluateRequestedPackages)(invocation.packageDir ?? invocation.effectiveCwd, requestedPackages, registryClient, config);
122
+ const blocked = evaluations.filter((evaluation) => evaluation.blockedReasons.length > 0);
123
+ const warnings = evaluations.flatMap((evaluation) => evaluation.warnings);
124
+ if (blocked.length > 0) {
125
+ return {
126
+ mode: "check",
127
+ decision: "block",
128
+ exitCode: 2,
129
+ exitCodeMeaning: "Check found dependencies that violate the current policy.",
130
+ command: argv,
131
+ commandString,
132
+ configPath: path,
133
+ configLabel: configLabel(path),
134
+ reasons: blocked.flatMap((evaluation) => evaluation.blockedReasons),
135
+ summary: "Check blocked.",
136
+ warnings,
137
+ affectedPackages: blocked.map(createAffectedPackage)
138
+ };
139
+ }
140
+ return {
141
+ mode: "check",
142
+ decision: "allow",
143
+ exitCode: 0,
144
+ exitCodeMeaning: "Check passed with no direct dependency policy violations.",
145
+ command: argv,
146
+ commandString,
147
+ configPath: path,
148
+ configLabel: configLabel(path),
149
+ reasons: [],
150
+ summary: "Check passed: no direct dependency policy violations found.",
151
+ warnings,
152
+ affectedPackages: requestedPackages.map((requested) => ({
153
+ name: requested.name,
154
+ requested: requested.raw,
155
+ sourceType: requested.sourceType,
156
+ reasons: [],
157
+ warnings: []
158
+ }))
159
+ };
160
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseCliOptions = parseCliOptions;
4
+ function parseCliOptions(argv) {
5
+ let json = false;
6
+ const args = [];
7
+ for (const token of argv) {
8
+ if (token === "--json") {
9
+ json = true;
10
+ continue;
11
+ }
12
+ args.push(token);
13
+ }
14
+ return { args, json };
15
+ }