@cassiomc1/forgeloop 0.1.14 → 0.1.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.
@@ -1,3 +1,4 @@
1
+ import fs from "node:fs/promises";
1
2
  import {
2
3
  E_AUTHORITY_UNTRUSTED_SOURCE,
3
4
  resolveTrustedAuthority,
@@ -11,6 +12,7 @@ export {
11
12
 
12
13
  export const E_VERIFICATION_TOOL_UNAVAILABLE = "E_VERIFICATION_TOOL_UNAVAILABLE";
13
14
  export const E_INSTALLATION_AUTHORITY_REQUIRED = "E_INSTALLATION_AUTHORITY_REQUIRED";
15
+ export const E_COMMAND_RESOLUTION_AMBIGUOUS = "E_COMMAND_RESOLUTION_AMBIGUOUS";
14
16
  export const E_AUTHORITY_INVALID = "E_AUTHORITY_INVALID";
15
17
  export const E_AUTHORITY_SCOPE_MISMATCH = "E_AUTHORITY_SCOPE_MISMATCH";
16
18
 
@@ -155,8 +157,397 @@ function extractToolFromArgs(args) {
155
157
  return null;
156
158
  }
157
159
 
158
- function classifySingleCommand(commandString) {
159
- const tokens = tokenizeCommand(commandString);
160
+ function extractNpmExecTool(args) {
161
+ if (!Array.isArray(args) || args.length === 0) return null;
162
+ for (let idx = 0; idx < args.length; idx++) {
163
+ const arg = args[idx];
164
+ if (arg === "-p" || arg === "--package") {
165
+ if (args[idx + 1] && !args[idx + 1].startsWith("-")) {
166
+ return args[idx + 1];
167
+ }
168
+ }
169
+ if (arg.startsWith("--package=")) {
170
+ return arg.slice("--package=".length);
171
+ }
172
+ }
173
+ let afterDoubleDash = false;
174
+ for (let idx = 0; idx < args.length; idx++) {
175
+ const arg = args[idx];
176
+ if (arg === "--") {
177
+ afterDoubleDash = true;
178
+ if (args[idx + 1] && !args[idx + 1].startsWith("-")) {
179
+ return args[idx + 1];
180
+ }
181
+ continue;
182
+ }
183
+ if (!afterDoubleDash && !arg.startsWith("-")) {
184
+ return arg;
185
+ }
186
+ }
187
+ return null;
188
+ }
189
+
190
+ function normalizeExecutableName(binaryToken) {
191
+ const base = binaryToken.split(/[\\/]/u).pop() ?? binaryToken;
192
+ return base.toLowerCase().replace(/\.(?:cmd|bat|exe)$/u, "");
193
+ }
194
+
195
+ const NPM_INSTALL_COMMANDS = new Set([
196
+ "install", "add", "i", "in", "ins", "inst", "insta", "instal", "isnt", "isnta", "isntal", "isntall",
197
+ ]);
198
+
199
+ const NPM_CI_COMMANDS = new Set([
200
+ "ci", "clean-install", "ic", "install-clean", "isntall-clean",
201
+ ]);
202
+
203
+ const NPM_INSTALL_TEST_COMMANDS = new Set([
204
+ "install-test", "it",
205
+ ]);
206
+
207
+ const NPM_INSTALL_CI_TEST_COMMANDS = new Set([
208
+ "install-ci-test", "cit", "clean-install-test", "sit",
209
+ ]);
210
+
211
+ const NPM_EXEC_COMMANDS = new Set([
212
+ "exec", "x",
213
+ ]);
214
+
215
+ const NPM_INIT_COMMANDS = new Set([
216
+ "init", "create", "innit",
217
+ ]);
218
+
219
+ const NPM_SCRIPT_COMMANDS = new Set([
220
+ "test", "t", "tst", "start", "stop", "restart", "run", "run-script", "rum", "urn",
221
+ ]);
222
+
223
+ const NPM_KNOWN_NON_INSTALLING_COMMANDS = new Set([
224
+ "v", "view", "info", "show", "list", "ls", "outdated", "config", "help", "help-search", "doctor", "ping", "root", "prefix", "bin", "whoami",
225
+ ]);
226
+
227
+ const NPM_KNOWN_BOOLEAN_OPTIONS = new Set([
228
+ "--silent", "--json", "--long", "--parseable", "--global", "-g", "--force", "--yes", "-y", "--no",
229
+ ]);
230
+
231
+ const NPM_OPTIONS_WITH_VALUE = new Set([
232
+ "--workspace",
233
+ "-w",
234
+ "--loglevel",
235
+ "--prefix",
236
+ "-C",
237
+ "--userconfig",
238
+ "--registry",
239
+ "--cache",
240
+ ]);
241
+
242
+ export function parseNpmInvocationArgs(rest) {
243
+ if (!Array.isArray(rest)) {
244
+ return {
245
+ subcommand: null,
246
+ subcommandIndex: -1,
247
+ args: [],
248
+ leadingOptions: [],
249
+ workspace: null,
250
+ workspaces: false,
251
+ ambiguous: true,
252
+ };
253
+ }
254
+
255
+ const leadingOptions = [];
256
+ let workspace = null;
257
+ let workspaces = false;
258
+
259
+ let i = 0;
260
+ while (i < rest.length) {
261
+ const arg = rest[i];
262
+
263
+ if (arg === "--") {
264
+ break;
265
+ }
266
+
267
+ if (!arg.startsWith("-")) {
268
+ break;
269
+ }
270
+
271
+ if (arg === "--workspaces" || arg === "--ws") {
272
+ workspaces = true;
273
+ leadingOptions.push(arg);
274
+ i += 1;
275
+ continue;
276
+ }
277
+
278
+ if (arg.startsWith("--workspace=")) {
279
+ workspace = arg.slice("--workspace=".length) || null;
280
+ leadingOptions.push(arg);
281
+ i += 1;
282
+ continue;
283
+ }
284
+
285
+ if (arg.startsWith("-w=")) {
286
+ workspace = arg.slice(3) || null;
287
+ leadingOptions.push(arg);
288
+ i += 1;
289
+ continue;
290
+ }
291
+
292
+ if (arg === "--workspace" || arg === "-w") {
293
+ leadingOptions.push(arg);
294
+ const value = rest[i + 1] ?? null;
295
+ if (value !== null && !value.startsWith("-")) {
296
+ workspace = value;
297
+ leadingOptions.push(value);
298
+ i += 2;
299
+ } else {
300
+ workspace = value;
301
+ i += 1;
302
+ }
303
+ continue;
304
+ }
305
+
306
+ if (NPM_OPTIONS_WITH_VALUE.has(arg)) {
307
+ leadingOptions.push(arg);
308
+ const value = rest[i + 1] ?? null;
309
+ if (value !== null && !value.startsWith("-")) {
310
+ leadingOptions.push(value);
311
+ i += 2;
312
+ } else {
313
+ i += 1;
314
+ }
315
+ continue;
316
+ }
317
+
318
+ if (/^--[^=]+=/.test(arg)) {
319
+ leadingOptions.push(arg);
320
+ i += 1;
321
+ continue;
322
+ }
323
+
324
+ if (NPM_KNOWN_BOOLEAN_OPTIONS.has(arg)) {
325
+ leadingOptions.push(arg);
326
+ i += 1;
327
+ continue;
328
+ }
329
+
330
+ if (/^--/.test(arg)) {
331
+ const next = rest[i + 1];
332
+ if (next && !next.startsWith("-")) {
333
+ return {
334
+ subcommand: null,
335
+ subcommandIndex: -1,
336
+ args: [],
337
+ leadingOptions,
338
+ workspace,
339
+ workspaces,
340
+ ambiguous: true,
341
+ reason: "NPM_OPTION_VALUE_AMBIGUOUS",
342
+ };
343
+ }
344
+ leadingOptions.push(arg);
345
+ i += 1;
346
+ continue;
347
+ }
348
+
349
+ if (arg.startsWith("-")) {
350
+ leadingOptions.push(arg);
351
+ i += 1;
352
+ continue;
353
+ }
354
+ }
355
+
356
+ const subcommand = rest[i] ? rest[i].toLowerCase() : null;
357
+ const trailingArgs = subcommand ? rest.slice(i + 1) : [];
358
+
359
+ for (let j = 0; j < trailingArgs.length; j++) {
360
+ const tArg = trailingArgs[j];
361
+ if (tArg === "--") break;
362
+ if (tArg === "--workspaces" || tArg === "--ws") {
363
+ workspaces = true;
364
+ } else if (tArg.startsWith("--workspace=")) {
365
+ workspace = tArg.slice("--workspace=".length) || null;
366
+ } else if (tArg.startsWith("-w=")) {
367
+ workspace = tArg.slice(3) || null;
368
+ } else if (tArg === "--workspace" || tArg === "-w") {
369
+ const val = trailingArgs[j + 1];
370
+ if (val && !val.startsWith("-")) {
371
+ workspace = val;
372
+ j += 1;
373
+ }
374
+ }
375
+ }
376
+
377
+ const ambiguous = subcommand === null;
378
+ return {
379
+ subcommand,
380
+ subcommandIndex: subcommand ? i : -1,
381
+ args: trailingArgs,
382
+ leadingOptions,
383
+ workspace,
384
+ workspaces,
385
+ ambiguous,
386
+ ...(ambiguous ? { reason: "NPM_SUBCOMMAND_AMBIGUOUS" } : {}),
387
+ };
388
+ }
389
+
390
+ export function parseNpmInvocation(argv) {
391
+ const tokens = unwrapCommandArgv(Array.isArray(argv) ? argv : tokenizeCommand(argv));
392
+ if (!tokens || tokens.length === 0) {
393
+ return {
394
+ subcommand: null,
395
+ subcommandIndex: -1,
396
+ args: [],
397
+ leadingOptions: [],
398
+ workspace: null,
399
+ workspaces: false,
400
+ ambiguous: true,
401
+ };
402
+ }
403
+ const binary = normalizeExecutableName(tokens[0]);
404
+ if (binary !== "npm") {
405
+ return {
406
+ subcommand: null,
407
+ subcommandIndex: -1,
408
+ args: [],
409
+ leadingOptions: [],
410
+ workspace: null,
411
+ workspaces: false,
412
+ ambiguous: true,
413
+ };
414
+ }
415
+ return parseNpmInvocationArgs(tokens.slice(1));
416
+ }
417
+
418
+ export function npmWorkspaceSelection(npmInvocation) {
419
+ return {
420
+ scoped: Boolean(npmInvocation?.workspace || npmInvocation?.workspaces),
421
+ workspace: npmInvocation?.workspace ?? null,
422
+ allWorkspaces: npmInvocation?.workspaces === true,
423
+ };
424
+ }
425
+
426
+ export function classifyNpmInvocation(npm) {
427
+ const sub = npm.subcommand;
428
+
429
+ if (npm.ambiguous) {
430
+ return {
431
+ resolutionMode: "UNKNOWN",
432
+ mayInstall: true,
433
+ installer: "npm",
434
+ tool: null,
435
+ reason: npm.reason || "NPM_SUBCOMMAND_AMBIGUOUS",
436
+ };
437
+ }
438
+
439
+ if (NPM_EXEC_COMMANDS.has(sub)) {
440
+ return {
441
+ resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
442
+ mayInstall: true,
443
+ installer: `npm ${sub}`,
444
+ tool: extractNpmExecTool(npm.args),
445
+ };
446
+ }
447
+
448
+ if (NPM_INSTALL_COMMANDS.has(sub) || NPM_CI_COMMANDS.has(sub) || NPM_INSTALL_TEST_COMMANDS.has(sub) || NPM_INSTALL_CI_TEST_COMMANDS.has(sub)) {
449
+ let tool = null;
450
+ let installer = `npm ${sub}`;
451
+ if (NPM_INSTALL_COMMANDS.has(sub)) {
452
+ tool = extractToolFromArgs(npm.args);
453
+ installer = "npm";
454
+ }
455
+ return {
456
+ resolutionMode: "EXPLICIT_INSTALLATION",
457
+ mayInstall: true,
458
+ installer,
459
+ tool,
460
+ };
461
+ }
462
+
463
+ if (NPM_INIT_COMMANDS.has(sub)) {
464
+ let firstMeaningful = null;
465
+ for (let i = 0; i < npm.args.length; i++) {
466
+ const arg = npm.args[i];
467
+ if (arg === "--") {
468
+ const next = npm.args[i + 1];
469
+ firstMeaningful = next && !next.startsWith("-") ? next : null;
470
+ break;
471
+ }
472
+ if (!arg.startsWith("-")) {
473
+ firstMeaningful = arg;
474
+ break;
475
+ }
476
+ }
477
+ if (firstMeaningful) {
478
+ return {
479
+ resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
480
+ mayInstall: true,
481
+ installer: `npm ${sub}`,
482
+ tool: firstMeaningful,
483
+ };
484
+ }
485
+ return {
486
+ resolutionMode: "LOCAL_PACKAGE_BINARY",
487
+ mayInstall: false,
488
+ installer: null,
489
+ tool: null,
490
+ };
491
+ }
492
+
493
+ if (sub === "update" || sub === "up" || sub === "upgrade") {
494
+ return {
495
+ resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
496
+ mayInstall: true,
497
+ installer: `npm ${sub}`,
498
+ tool: extractToolFromArgs(npm.args),
499
+ };
500
+ }
501
+
502
+ if (sub === "audit") {
503
+ if (npm.args.includes("fix")) {
504
+ return {
505
+ resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
506
+ mayInstall: true,
507
+ installer: "npm audit fix",
508
+ tool: null,
509
+ };
510
+ }
511
+ return {
512
+ resolutionMode: "LOCAL_PACKAGE_BINARY",
513
+ mayInstall: false,
514
+ installer: null,
515
+ tool: null,
516
+ };
517
+ }
518
+
519
+ if (NPM_SCRIPT_COMMANDS.has(sub)) {
520
+ return {
521
+ resolutionMode: "LOCAL_PACKAGE_BINARY",
522
+ mayInstall: false,
523
+ installer: null,
524
+ tool: null,
525
+ dispatch: {
526
+ kind: "npm-script-command",
527
+ },
528
+ };
529
+ }
530
+
531
+ if (NPM_KNOWN_NON_INSTALLING_COMMANDS.has(sub)) {
532
+ return {
533
+ resolutionMode: "LOCAL_PACKAGE_BINARY",
534
+ mayInstall: false,
535
+ installer: null,
536
+ tool: null,
537
+ };
538
+ }
539
+
540
+ return {
541
+ resolutionMode: "UNKNOWN",
542
+ mayInstall: true,
543
+ installer: "npm",
544
+ tool: null,
545
+ reason: "NPM_COMMAND_UNCLASSIFIED",
546
+ };
547
+ }
548
+
549
+ function classifySingleCommand(commandInput) {
550
+ const tokens = Array.isArray(commandInput) ? [...commandInput] : tokenizeCommand(commandInput);
160
551
  if (tokens.length === 0) {
161
552
  return { resolutionMode: "UNKNOWN", mayInstall: false, installer: null, tool: null };
162
553
  }
@@ -170,9 +561,13 @@ function classifySingleCommand(commandString) {
170
561
  }
171
562
 
172
563
  const binaryToken = tokens[i];
173
- const binary = path.basename(binaryToken).toLowerCase();
564
+ const binary = normalizeExecutableName(binaryToken);
174
565
  const rest = tokens.slice(i + 1);
175
566
 
567
+ if (binary === "call") {
568
+ return classifyCommandResolution(rest);
569
+ }
570
+
176
571
  if (binaryToken.includes("node_modules/.bin/") || binaryToken.startsWith("./node_modules/")) {
177
572
  return { resolutionMode: "LOCAL_PACKAGE_BINARY", mayInstall: false, installer: null, tool: binary };
178
573
  }
@@ -300,15 +695,8 @@ function classifySingleCommand(commandString) {
300
695
 
301
696
  // Check npm
302
697
  if (binary === "npm") {
303
- if (["install", "i", "add"].includes(rest[0])) {
304
- return {
305
- resolutionMode: "EXPLICIT_INSTALLATION",
306
- mayInstall: true,
307
- installer: "npm",
308
- tool: extractToolFromArgs(rest.slice(1)),
309
- };
310
- }
311
- return { resolutionMode: "LOCAL_PACKAGE_BINARY", mayInstall: false, installer: null, tool: null };
698
+ const npm = parseNpmInvocationArgs(rest);
699
+ return classifyNpmInvocation(npm);
312
700
  }
313
701
 
314
702
  // Check python/pip
@@ -360,12 +748,30 @@ function classifySingleCommand(commandString) {
360
748
  return { resolutionMode: "LOCAL_EXECUTABLE", mayInstall: false, installer: null, tool: null };
361
749
  }
362
750
 
363
- export function classifyCommandResolution(commandString) {
364
- if (typeof commandString !== "string" || commandString.trim() === "") {
751
+ export function classifyCommandResolution(commandInput) {
752
+ if (Array.isArray(commandInput)) {
753
+ if (commandInput.length === 0 || commandInput.some((item) => typeof item !== "string" || item.trim() === "")) {
754
+ return { resolutionMode: "UNKNOWN", mayInstall: false, installer: null, tool: null };
755
+ }
756
+ const binary = normalizeExecutableName(commandInput[0]);
757
+ if (["sh", "bash", "zsh", "dash", "ksh"].includes(binary)) {
758
+ const shellFlagIndex = commandInput.findIndex((item, index) => index > 0 && /^-.*c/.test(item));
759
+ const shellCommand = shellFlagIndex >= 0 ? commandInput[shellFlagIndex + 1] : null;
760
+ if (shellCommand) return classifyCommandResolution(shellCommand);
761
+ }
762
+ if (binary === "cmd") {
763
+ const shellFlagIndex = commandInput.findIndex((item, index) => index > 0 && /^\/c$/iu.test(item));
764
+ const shellCommand = shellFlagIndex >= 0 ? commandInput.slice(shellFlagIndex + 1).join(" ") : null;
765
+ if (shellCommand) return classifyCommandResolution(shellCommand);
766
+ }
767
+ return classifySingleCommand(commandInput);
768
+ }
769
+
770
+ if (typeof commandInput !== "string" || commandInput.trim() === "") {
365
771
  return { resolutionMode: "UNKNOWN", mayInstall: false, installer: null, tool: null };
366
772
  }
367
773
 
368
- const subcommands = splitCommandPipeline(commandString);
774
+ const subcommands = splitCommandPipeline(commandInput);
369
775
  for (const subcommand of subcommands) {
370
776
  const res = classifySingleCommand(subcommand);
371
777
  if (res.mayInstall) {
@@ -373,7 +779,211 @@ export function classifyCommandResolution(commandString) {
373
779
  }
374
780
  }
375
781
 
376
- return classifySingleCommand(subcommands[0] || commandString);
782
+ return classifySingleCommand(subcommands[0] || commandInput);
783
+ }
784
+
785
+ function unwrapCommandArgv(argv) {
786
+ if (!Array.isArray(argv) || argv.length === 0) return null;
787
+ let i = 0;
788
+ while (i < argv.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(argv[i])) {
789
+ i++;
790
+ }
791
+ if (i >= argv.length) return null;
792
+ const binary = normalizeExecutableName(argv[i]);
793
+ if (["sh", "bash", "zsh", "dash", "ksh"].includes(binary)) {
794
+ const shellFlagIndex = argv.findIndex((item, index) => index > i && /^-.*c/.test(item));
795
+ const shellCommand = shellFlagIndex >= 0 ? argv[shellFlagIndex + 1] : null;
796
+ if (shellCommand) return tokenizeCommand(shellCommand);
797
+ }
798
+ if (binary === "cmd") {
799
+ const shellFlagIndex = argv.findIndex((item, index) => index > i && /^\/c$/iu.test(item));
800
+ const shellCommand = shellFlagIndex >= 0 ? argv.slice(shellFlagIndex + 1).join(" ") : null;
801
+ if (shellCommand) return tokenizeCommand(shellCommand);
802
+ }
803
+ if (binary === "call") {
804
+ return argv.slice(i + 1);
805
+ }
806
+ return argv.slice(i);
807
+ }
808
+
809
+ export function getNpmScriptName(argv) {
810
+ const npm = parseNpmInvocation(argv);
811
+ if (npm.ambiguous || !npm.subcommand) {
812
+ return null;
813
+ }
814
+
815
+ const sub = npm.subcommand;
816
+ const args = npm.args;
817
+
818
+ if (["test", "t", "tst"].includes(sub)) return "test";
819
+ if (["start", "stop", "restart"].includes(sub)) return sub;
820
+ if (["run", "run-script", "rum", "urn"].includes(sub)) {
821
+ let afterDoubleDash = false;
822
+ for (let idx = 0; idx < args.length; idx++) {
823
+ const arg = args[idx];
824
+ if (arg === "--") {
825
+ afterDoubleDash = true;
826
+ if (args[idx + 1] && !args[idx + 1].startsWith("-")) {
827
+ return args[idx + 1];
828
+ }
829
+ continue;
830
+ }
831
+ if (!afterDoubleDash && !arg.startsWith("-")) {
832
+ if (idx > 0 && (args[idx - 1] === "-w" || args[idx - 1] === "--workspace" || NPM_OPTIONS_WITH_VALUE.has(args[idx - 1]))) {
833
+ continue;
834
+ }
835
+ return arg;
836
+ }
837
+ }
838
+ }
839
+ return null;
840
+ }
841
+
842
+ export function getNpmLifecycleCandidates({ scriptName, scripts = {} } = {}) {
843
+ if (scriptName === "restart") {
844
+ if (typeof scripts?.restart === "string" && scripts.restart.trim() !== "") {
845
+ return ["prerestart", "restart", "postrestart"];
846
+ }
847
+ return [
848
+ "prerestart",
849
+ "prestop",
850
+ "stop",
851
+ "poststop",
852
+ "prestart",
853
+ "start",
854
+ "poststart",
855
+ "postrestart",
856
+ ];
857
+ }
858
+ return [`pre${scriptName}`, scriptName, `post${scriptName}`];
859
+ }
860
+
861
+ export const MAX_NPM_SCRIPT_DEPTH = 16;
862
+
863
+ async function resolveNpmScriptRisk({
864
+ scriptName,
865
+ packageJson,
866
+ visited = new Set(),
867
+ depth = 0,
868
+ } = {}) {
869
+ if (depth > MAX_NPM_SCRIPT_DEPTH) {
870
+ return {
871
+ resolutionMode: "UNKNOWN",
872
+ mayInstall: true,
873
+ installer: "npm-script",
874
+ tool: null,
875
+ reason: "MAX_SCRIPT_DEPTH",
876
+ };
877
+ }
878
+
879
+ if (visited.has(scriptName)) {
880
+ return {
881
+ resolutionMode: "UNKNOWN",
882
+ mayInstall: true,
883
+ installer: "npm-script",
884
+ tool: null,
885
+ reason: "SCRIPT_CYCLE",
886
+ };
887
+ }
888
+
889
+ const nextVisited = new Set(visited);
890
+ nextVisited.add(scriptName);
891
+
892
+ const candidates = getNpmLifecycleCandidates({
893
+ scriptName,
894
+ scripts: packageJson?.scripts ?? {},
895
+ });
896
+
897
+ for (const candidate of candidates) {
898
+ const scriptBody = packageJson?.scripts?.[candidate];
899
+ if (typeof scriptBody !== "string" || scriptBody.trim() === "") {
900
+ continue;
901
+ }
902
+
903
+ const direct = classifyCommandResolution(scriptBody);
904
+ if (direct.mayInstall) {
905
+ return {
906
+ resolutionMode: direct.resolutionMode,
907
+ mayInstall: true,
908
+ installer: direct.installer ?? "npm-script",
909
+ tool: direct.tool,
910
+ dispatch: {
911
+ kind: "npm-script",
912
+ scriptName: candidate,
913
+ },
914
+ };
915
+ }
916
+
917
+ const subcommands = splitCommandPipeline(scriptBody);
918
+ for (const subcommand of subcommands) {
919
+ const nestedScriptName = getNpmScriptName(subcommand);
920
+ if (nestedScriptName) {
921
+ const nested = await resolveNpmScriptRisk({
922
+ scriptName: nestedScriptName,
923
+ packageJson,
924
+ visited: nextVisited,
925
+ depth: depth + 1,
926
+ });
927
+ if (nested?.mayInstall) {
928
+ return nested;
929
+ }
930
+ }
931
+ }
932
+ }
933
+
934
+ return null;
935
+ }
936
+
937
+ async function readPackageJsonIfPresent(cwd) {
938
+ if (!cwd || typeof cwd !== "string") return null;
939
+ try {
940
+ const pkgPath = path.resolve(cwd, "package.json");
941
+ const raw = await fs.readFile(pkgPath, "utf8");
942
+ return JSON.parse(raw);
943
+ } catch {
944
+ return null;
945
+ }
946
+ }
947
+
948
+ export async function resolveExecutionResolution({ argv, cwd } = {}) {
949
+ const direct = classifyCommandResolution(argv);
950
+ if (direct.mayInstall && direct.resolutionMode !== "UNKNOWN") return direct;
951
+
952
+ const npmInvocation = parseNpmInvocation(argv);
953
+ const scriptName = getNpmScriptName(argv);
954
+
955
+ if (scriptName && (npmInvocation.workspace || npmInvocation.workspaces)) {
956
+ return {
957
+ resolutionMode: "UNKNOWN",
958
+ mayInstall: true,
959
+ installer: "npm-workspace",
960
+ tool: null,
961
+ reason: "NPM_WORKSPACE_SCRIPT_UNRESOLVED",
962
+ dispatch: {
963
+ kind: "npm-workspace-script",
964
+ scriptName,
965
+ },
966
+ };
967
+ }
968
+
969
+ if (direct.mayInstall) return direct;
970
+ if (!scriptName) return direct;
971
+
972
+ const packageJson = await readPackageJsonIfPresent(cwd);
973
+ if (!packageJson || typeof packageJson.scripts !== "object" || packageJson.scripts === null) {
974
+ return direct;
975
+ }
976
+
977
+ const nestedRisk = await resolveNpmScriptRisk({
978
+ scriptName,
979
+ packageJson,
980
+ });
981
+
982
+ if (nestedRisk?.mayInstall) {
983
+ return nestedRisk;
984
+ }
985
+
986
+ return direct;
377
987
  }
378
988
 
379
989
  export function getInstallationAuthorityRef(check) {
@@ -489,16 +1099,17 @@ export function validateAuthorityGrant({ authority, taskId, type = "SOFTWARE_INS
489
1099
  }
490
1100
 
491
1101
  export function validateVerificationAuthority(check, options = {}) {
1102
+ const canonicalResolution = check?.execution?.resolution ?? check?.details?.execution?.resolution;
492
1103
  const command = check?.details?.command
493
1104
  ?? (check?.kind === "command" && typeof check?.source === "string" && !check.source.startsWith("check:")
494
1105
  ? check.source
495
1106
  : null);
496
1107
 
497
- if (!command || typeof command !== "string") {
1108
+ if (!canonicalResolution && (!command || typeof command !== "string")) {
498
1109
  return { valid: true, error: null };
499
1110
  }
500
1111
 
501
- const classification = classifyCommandResolution(command);
1112
+ const classification = canonicalResolution ?? classifyCommandResolution(command);
502
1113
  if (!classification.mayInstall) {
503
1114
  return { valid: true, error: null };
504
1115
  }