vouchington-tooling 0.20.0 → 0.22.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/README.md +73 -10
- package/dist/agent-blackboard/snapshot-partition-format.mjs +2 -72
- package/dist/agent-blackboard/snapshot-partition-guards.d.mts +3 -0
- package/dist/agent-blackboard/snapshot-partition-guards.mjs +9 -0
- package/dist/agent-blackboard/snapshot-partition-predicates.d.mts +3 -0
- package/dist/agent-blackboard/snapshot-partition-predicates.mjs +65 -0
- package/dist/api-contract-discovery/header-contract-parser.d.mts +3 -0
- package/dist/api-contract-discovery/header-contract-parser.mjs +93 -0
- package/dist/api-contract-discovery/header-contract-registry.d.mts +3 -0
- package/dist/api-contract-discovery/header-contract-registry.mjs +38 -0
- package/dist/api-contract-discovery/header-contract-types.d.mts +19 -0
- package/dist/api-contract-discovery/header-contract-types.mjs +1 -0
- package/dist/api-contract-discovery/index.d.mts +31 -0
- package/dist/api-contract-discovery/index.mjs +26 -0
- package/dist/api-contract-discovery/query-contract-extraction.d.mts +3 -0
- package/dist/api-contract-discovery/query-contract-extraction.mjs +65 -0
- package/dist/api-contract-discovery/query-contract-literals.d.mts +8 -0
- package/dist/api-contract-discovery/query-contract-literals.mjs +54 -0
- package/dist/api-contract-discovery/query-contract-registry.d.mts +3 -0
- package/dist/api-contract-discovery/query-contract-registry.mjs +68 -0
- package/dist/api-contract-discovery/query-contract-types.d.mts +8 -0
- package/dist/api-contract-discovery/query-contract-types.mjs +1 -0
- package/dist/api-contract-discovery/registered-route-catalog.d.mts +10 -0
- package/dist/api-contract-discovery/registered-route-catalog.mjs +81 -0
- package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +2 -0
- package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +74 -0
- package/dist/api-contract-discovery/request-contract-implicit.d.mts +17 -0
- package/dist/api-contract-discovery/request-contract-implicit.mjs +73 -0
- package/dist/api-contract-discovery/request-contract-lenient.d.mts +28 -0
- package/dist/api-contract-discovery/request-contract-lenient.mjs +59 -0
- package/dist/api-contract-discovery/request-contract-registry.d.mts +14 -0
- package/dist/api-contract-discovery/request-contract-registry.mjs +61 -0
- package/dist/api-contract-discovery/request-contract-route-analysis.d.mts +25 -0
- package/dist/api-contract-discovery/request-contract-route-analysis.mjs +68 -0
- package/dist/api-contract-discovery/request-contract-types.d.mts +12 -0
- package/dist/api-contract-discovery/request-contract-types.mjs +1 -0
- package/dist/api-contract-discovery/response-contract-ctx-analysis.d.mts +7 -0
- package/dist/api-contract-discovery/response-contract-ctx-analysis.mjs +28 -0
- package/dist/api-contract-discovery/response-contract-error-branch.d.mts +15 -0
- package/dist/api-contract-discovery/response-contract-error-branch.mjs +47 -0
- package/dist/api-contract-discovery/response-contract-implicit-sources.test-helpers.d.mts +145 -0
- package/dist/api-contract-discovery/response-contract-implicit-sources.test-helpers.mjs +145 -0
- package/dist/api-contract-discovery/response-contract-implicit.d.mts +5 -0
- package/dist/api-contract-discovery/response-contract-implicit.mjs +110 -0
- package/dist/api-contract-discovery/response-contract-lenient.d.mts +23 -0
- package/dist/api-contract-discovery/response-contract-lenient.mjs +52 -0
- package/dist/api-contract-discovery/response-contract-media.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-media.mjs +46 -0
- package/dist/api-contract-discovery/response-contract-registration.d.mts +11 -0
- package/dist/api-contract-discovery/response-contract-registration.mjs +61 -0
- package/dist/api-contract-discovery/response-contract-registry.d.mts +7 -0
- package/dist/api-contract-discovery/response-contract-registry.mjs +76 -0
- package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +13 -0
- package/dist/api-contract-discovery/response-contract-route-analysis.mjs +144 -0
- package/dist/api-contract-discovery/response-contract-status.d.mts +27 -0
- package/dist/api-contract-discovery/response-contract-status.mjs +80 -0
- package/dist/api-contract-discovery/response-contract-types.d.mts +16 -0
- package/dist/api-contract-discovery/response-contract-types.mjs +1 -0
- package/dist/api-contract-discovery/test-setup.test-helpers.d.mts +3 -0
- package/dist/api-contract-discovery/test-setup.test-helpers.mjs +2 -0
- package/dist/ast-grep-examples/companion-parity-compare.d.mts +3 -0
- package/dist/ast-grep-examples/companion-parity-compare.mjs +58 -0
- package/dist/ast-grep-examples/companion-parity.d.mts +2 -1
- package/dist/ast-grep-examples/companion-parity.mjs +2 -58
- package/dist/browser-session-runner/attempt.mjs +29 -48
- package/dist/browser-session-runner/termination.d.mts +11 -0
- package/dist/browser-session-runner/termination.mjs +34 -0
- package/dist/cli/parse-direct-options.d.mts +26 -0
- package/dist/cli/parse-direct-options.mjs +72 -0
- package/dist/cli/parse-forwarded-commands.d.mts +42 -0
- package/dist/cli/parse-forwarded-commands.mjs +29 -0
- package/dist/cli/parse-options.d.mts +1 -2
- package/dist/cli/parse-options.mjs +2 -72
- package/dist/cli/parse.mjs +4 -26
- package/dist/compiler-build/freshness.d.mts +27 -0
- package/dist/compiler-build/freshness.mjs +116 -0
- package/dist/compiler-build/index.d.mts +2 -0
- package/dist/compiler-build/index.mjs +2 -0
- package/dist/compiler-build/settlement.d.mts +10 -0
- package/dist/compiler-build/settlement.mjs +14 -0
- package/dist/config-inventory/call-arguments.d.mts +1 -0
- package/dist/config-inventory/call-arguments.mjs +30 -0
- package/dist/config-inventory/collect.d.mts +3 -0
- package/dist/config-inventory/collect.mjs +43 -0
- package/dist/config-inventory/dynamic-configs.d.mts +2 -0
- package/dist/config-inventory/dynamic-configs.mjs +9 -0
- package/dist/config-inventory/env-expression-readers.d.mts +3 -0
- package/dist/config-inventory/env-expression-readers.mjs +117 -0
- package/dist/config-inventory/env-prefix-readers.d.mts +2 -0
- package/dist/config-inventory/env-prefix-readers.mjs +14 -0
- package/dist/config-inventory/env-reference-matchers.d.mts +3 -0
- package/dist/config-inventory/env-reference-matchers.mjs +9 -0
- package/dist/config-inventory/env-var-accumulator.d.mts +6 -0
- package/dist/config-inventory/env-var-accumulator.mjs +82 -0
- package/dist/config-inventory/env-vars.d.mts +4 -0
- package/dist/config-inventory/env-vars.mjs +108 -0
- package/dist/config-inventory/format.d.mts +2 -0
- package/dist/config-inventory/format.mjs +69 -0
- package/dist/config-inventory/index.d.mts +3 -0
- package/dist/config-inventory/index.mjs +2 -0
- package/dist/config-inventory/package-gates.d.mts +2 -0
- package/dist/config-inventory/package-gates.mjs +54 -0
- package/dist/config-inventory/shared.d.mts +3 -0
- package/dist/config-inventory/shared.mjs +7 -0
- package/dist/config-inventory/types.d.mts +96 -0
- package/dist/config-inventory/types.mjs +1 -0
- package/dist/contract-schema/contract-schema-bounded-array.d.mts +15 -0
- package/dist/contract-schema/contract-schema-bounded-array.mjs +32 -0
- package/dist/contract-schema/contract-schema-type-extractor.d.mts +2 -8
- package/dist/contract-schema/contract-schema-type-extractor.mjs +2 -31
- package/dist/coverage-transport/control-v2-parse-object.d.mts +12 -0
- package/dist/coverage-transport/control-v2-parse-object.mjs +28 -0
- package/dist/coverage-transport/control-v2.mjs +2 -27
- package/dist/dependency-license-policy/audit-directory.d.mts +28 -0
- package/dist/dependency-license-policy/audit-directory.mjs +115 -0
- package/dist/dependency-license-policy/audit-signals.d.mts +12 -0
- package/dist/dependency-license-policy/audit-signals.mjs +44 -0
- package/dist/dependency-license-policy/audit-store.d.mts +12 -0
- package/dist/dependency-license-policy/audit-store.mjs +32 -0
- package/dist/dependency-license-policy/collect.d.mts +3 -1
- package/dist/dependency-license-policy/collect.mjs +16 -21
- package/dist/dependency-license-policy/execute-pnpm.d.mts +10 -0
- package/dist/dependency-license-policy/execute-pnpm.mjs +83 -0
- package/dist/dependency-license-policy/types.d.mts +11 -9
- package/dist/dependency-license-policy/workspace.d.mts +3 -1
- package/dist/dependency-license-policy/workspace.mjs +12 -5
- package/dist/gh-api-shell-quoting/argument-scan.d.mts +7 -0
- package/dist/gh-api-shell-quoting/argument-scan.mjs +35 -0
- package/dist/gh-api-shell-quoting/logical-line.d.mts +3 -0
- package/dist/gh-api-shell-quoting/logical-line.mjs +90 -0
- package/dist/gh-api-shell-quoting/scan.d.mts +2 -4
- package/dist/gh-api-shell-quoting/scan.mjs +1 -119
- package/dist/gh-cli/diff.d.mts +24 -4
- package/dist/gh-cli/diff.mjs +111 -4
- package/dist/gh-cli/index.d.mts +2 -1
- package/dist/gh-cli/index.mjs +1 -1
- package/dist/gha-workspace-policy/docker-options.d.mts +4 -0
- package/dist/gha-workspace-policy/docker-options.mjs +93 -0
- package/dist/gha-workspace-policy/docker-workspace-user.mjs +1 -168
- package/dist/gha-workspace-policy/shell-token-modes.d.mts +10 -0
- package/dist/gha-workspace-policy/shell-token-modes.mjs +31 -0
- package/dist/gha-workspace-policy/shell-tokens.d.mts +1 -0
- package/dist/gha-workspace-policy/shell-tokens.mjs +65 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +1 -1
- package/dist/openapi-document/contract-schema-types.d.mts +1 -1
- package/dist/openapi-document/openapi-types.d.mts +1 -1
- package/dist/pnpm-install/native-family.d.mts +3 -0
- package/dist/pnpm-install/native-family.mjs +31 -0
- package/dist/pnpm-install/native-health.d.mts +1 -4
- package/dist/pnpm-install/native-health.mjs +2 -31
- package/dist/retrospective-facts/local-merge.d.mts +2 -0
- package/dist/retrospective-facts/local-merge.mjs +21 -0
- package/dist/retrospective-facts/local.mjs +1 -21
- package/dist/retrospective-transcript/claude-blocks.d.mts +3 -0
- package/dist/retrospective-transcript/claude-blocks.mjs +38 -0
- package/dist/retrospective-transcript/claude.mjs +4 -34
- package/dist/retrospective-transcript/codex-payload.d.mts +2 -0
- package/dist/retrospective-transcript/codex-payload.mjs +51 -0
- package/dist/retrospective-transcript/codex-records.d.mts +2 -0
- package/dist/retrospective-transcript/codex-records.mjs +95 -0
- package/dist/retrospective-transcript/codex.mjs +2 -144
- package/dist/retrospective-transcript/index.d.mts +3 -16
- package/dist/retrospective-transcript/index.mjs +4 -48
- package/dist/retrospective-transcript/resolve-transcript-file.d.mts +19 -0
- package/dist/retrospective-transcript/resolve-transcript-file.mjs +50 -0
- package/dist/session-friction/command-segments.d.mts +3 -0
- package/dist/session-friction/command-segments.mjs +94 -0
- package/dist/session-friction/log-file.d.mts +7 -0
- package/dist/session-friction/log-file.mjs +83 -0
- package/dist/session-friction/log.mjs +3 -83
- package/dist/session-friction/normalize.mjs +1 -94
- package/dist/sql-scanner/comments.d.mts +1 -0
- package/dist/sql-scanner/comments.mjs +101 -0
- package/dist/sql-scanner/index.d.mts +1 -1
- package/dist/sql-scanner/index.mjs +1 -100
- package/dist/sql-scanner/literals.mjs +1 -35
- package/dist/sql-scanner/quote-decode.d.mts +2 -0
- package/dist/sql-scanner/quote-decode.mjs +35 -0
- package/dist/vitest-diagnostics/index.d.mts +7 -0
- package/dist/vitest-diagnostics/index.mjs +4 -0
- package/dist/vitest-diagnostics/vitest-fork-exit-error-detail.d.mts +7 -0
- package/dist/vitest-diagnostics/vitest-fork-exit-error-detail.mjs +26 -0
- package/dist/vitest-diagnostics/vitest-fork-exit-records.d.mts +26 -0
- package/dist/vitest-diagnostics/vitest-fork-exit-records.mjs +161 -0
- package/dist/vitest-diagnostics/vitest-fork-exit-sentinel.d.mts +14 -0
- package/dist/vitest-diagnostics/vitest-fork-exit-sentinel.mjs +131 -0
- package/dist/vitest-diagnostics/vitest-fork-leak-detection.d.mts +26 -0
- package/dist/vitest-diagnostics/vitest-fork-leak-detection.mjs +96 -0
- package/dist/vitest-diagnostics/vitest-fork-leak-diagnostics.d.mts +3 -0
- package/dist/vitest-diagnostics/vitest-fork-leak-diagnostics.mjs +14 -0
- package/dist/vitest-diagnostics/vitest-fork-leak-episode.d.mts +37 -0
- package/dist/vitest-diagnostics/vitest-fork-leak-episode.mjs +118 -0
- package/dist/vitest-diagnostics/vitest-worker-exit-diagnostics-errors.d.mts +2 -0
- package/dist/vitest-diagnostics/vitest-worker-exit-diagnostics-errors.mjs +42 -0
- package/dist/vitest-diagnostics/vitest-worker-exit-diagnostics-reporter.d.mts +12 -0
- package/dist/vitest-diagnostics/vitest-worker-exit-diagnostics-reporter.mjs +142 -0
- package/dist/workflow-policy/concurrency-scope.d.mts +4 -0
- package/dist/workflow-policy/concurrency-scope.mjs +66 -0
- package/dist/workflow-policy/concurrency.d.mts +10 -0
- package/dist/workflow-policy/concurrency.mjs +77 -0
- package/dist/workflow-policy/condition-entailment.d.mts +20 -0
- package/dist/workflow-policy/condition-entailment.mjs +132 -0
- package/dist/workflow-policy/condition-split.d.mts +15 -0
- package/dist/workflow-policy/condition-split.mjs +71 -0
- package/dist/workflow-policy/graph.d.mts +2 -0
- package/dist/workflow-policy/graph.mjs +138 -0
- package/dist/workflow-policy/index.d.mts +7 -0
- package/dist/workflow-policy/index.mjs +6 -0
- package/dist/workflow-policy/permission-mismatches.d.mts +3 -0
- package/dist/workflow-policy/permission-mismatches.mjs +52 -0
- package/dist/workflow-policy/permissions.d.mts +16 -0
- package/dist/workflow-policy/permissions.mjs +83 -0
- package/dist/workflow-policy/secret-readiness.d.mts +18 -0
- package/dist/workflow-policy/secret-readiness.mjs +163 -0
- package/dist/workflow-policy/shell-comments.d.mts +7 -0
- package/dist/workflow-policy/shell-comments.mjs +28 -0
- package/dist/workflow-policy/types.d.mts +90 -0
- package/dist/workflow-policy/types.mjs +1 -0
- package/dist/workspace-gates/manifest-version-parser.mjs +1 -52
- package/dist/workspace-gates/manifest-version-skips.d.mts +1 -0
- package/dist/workspace-gates/manifest-version-skips.mjs +52 -0
- package/docs/workflow-and-compiler.md +74 -0
- package/package.json +26 -2
- package/skills/agent-workflow/SKILL.md +4 -4
- package/skills/agent-workflow/references/review-response.md +47 -44
- package/skills/backend-vitest-test-authoring/SKILL.md +9 -6
- package/skills/blackboard/SKILL.md +8 -8
- package/skills/dependabot/SKILL.md +68 -67
- package/skills/dotnet-test-authoring/SKILL.md +11 -12
- package/skills/git-commit-checklist/SKILL.md +3 -3
- package/skills/github-actions-authoring/SKILL.md +40 -38
- package/skills/github-actions-checklist/SKILL.md +93 -85
- package/skills/github-issue/SKILL.md +82 -78
- package/skills/nextjs-vitest-test-authoring/SKILL.md +8 -6
- package/skills/npm-publishing/SKILL.md +42 -38
- package/skills/organize-github-issues/SKILL.md +18 -22
- package/skills/package-json-checklist/SKILL.md +5 -5
- package/skills/planning/SKILL.md +3 -3
- package/skills/playwright-authoring/SKILL.md +10 -9
- package/skills/postgres-node-performance-tuning/SKILL.md +10 -6
- package/skills/postgres-partitioning-uuid-v7/SKILL.md +10 -7
- package/skills/pr-description/SKILL.md +4 -3
- package/skills/retrospective/SKILL.md +14 -15
- package/skills/retrospective-distill/SKILL.md +14 -14
- package/skills/review-ci-logs/SKILL.md +18 -19
- package/skills/review-github-issue-taxonomy/SKILL.md +21 -18
- package/skills/revisit-followups/SKILL.md +3 -3
- package/skills/stacked-prs/SKILL.md +30 -61
- package/skills/static-analysis-checklist/SKILL.md +6 -4
- package/skills/storybook-authoring/SKILL.md +9 -9
- package/skills/swift-test-authoring/SKILL.md +10 -9
- package/skills/test-authoring/SKILL.md +14 -14
- package/skills/test-authoring/references/core-practice.md +12 -11
- package/skills/test-authoring/references/dependency-boundaries.md +36 -35
- package/skills/test-authoring/references/tautological-tests.md +45 -44
- package/skills/vitest-test-authoring/SKILL.md +9 -6
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export type EnvClassification = string;
|
|
2
|
+
export interface EnvVarInventoryRow {
|
|
3
|
+
name: string;
|
|
4
|
+
classifications: EnvClassification[];
|
|
5
|
+
reviewReason: string | null;
|
|
6
|
+
contractKey: string | null;
|
|
7
|
+
contractKeys: string[];
|
|
8
|
+
sourceOfTruth: string | null;
|
|
9
|
+
sensitivity: string | null;
|
|
10
|
+
runtimeSurfaces: string[];
|
|
11
|
+
readers: string[];
|
|
12
|
+
localSetup: string[];
|
|
13
|
+
docs: string[];
|
|
14
|
+
deployment: string[];
|
|
15
|
+
dockerBuildArgs: string[];
|
|
16
|
+
workflows: string[];
|
|
17
|
+
packageGates: string[];
|
|
18
|
+
}
|
|
19
|
+
export interface DynamicConfigInventoryRow {
|
|
20
|
+
namespace: string;
|
|
21
|
+
definitionFiles: string[];
|
|
22
|
+
registryFiles: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface PackageGateInventoryRow {
|
|
25
|
+
name: string;
|
|
26
|
+
values: string[];
|
|
27
|
+
files: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface ConfigInventory {
|
|
30
|
+
envVars: EnvVarInventoryRow[];
|
|
31
|
+
dynamicConfigs: DynamicConfigInventoryRow[];
|
|
32
|
+
packageGates: PackageGateInventoryRow[];
|
|
33
|
+
}
|
|
34
|
+
export interface EnvVarAccumulator {
|
|
35
|
+
name: string;
|
|
36
|
+
contractKey: string | null;
|
|
37
|
+
contractKeys: Set<string>;
|
|
38
|
+
sourceOfTruth: string | null;
|
|
39
|
+
sensitivity: string | null;
|
|
40
|
+
runtimeSurfaces: Set<string>;
|
|
41
|
+
readers: Set<string>;
|
|
42
|
+
localSetup: Set<string>;
|
|
43
|
+
docs: Set<string>;
|
|
44
|
+
deployment: Set<string>;
|
|
45
|
+
dockerBuildArgs: Set<string>;
|
|
46
|
+
workflows: Set<string>;
|
|
47
|
+
packageGates: Set<string>;
|
|
48
|
+
}
|
|
49
|
+
export type SourceBucket = keyof Omit<EnvVarAccumulator, 'name' | 'contractKey' | 'contractKeys' | 'sourceOfTruth' | 'sensitivity' | 'runtimeSurfaces'>;
|
|
50
|
+
export interface EnvVarReferenceMatcher {
|
|
51
|
+
name: string;
|
|
52
|
+
pattern: RegExp;
|
|
53
|
+
}
|
|
54
|
+
export interface TypedEnvContractEntry {
|
|
55
|
+
name: string;
|
|
56
|
+
contractKey?: string;
|
|
57
|
+
sourceOfTruth?: string;
|
|
58
|
+
sensitivity?: string;
|
|
59
|
+
runtimeSurfaces?: string[];
|
|
60
|
+
}
|
|
61
|
+
/** Discovery is opt-in by file role; the caller owns paths and exclusions. */
|
|
62
|
+
export interface ConfigSourceRules {
|
|
63
|
+
workerBindings?: boolean;
|
|
64
|
+
localAssignments?: boolean;
|
|
65
|
+
shellExports?: boolean;
|
|
66
|
+
jsonBindings?: boolean;
|
|
67
|
+
docker?: boolean;
|
|
68
|
+
workflow?: boolean;
|
|
69
|
+
markdown?: boolean;
|
|
70
|
+
packageManifest?: boolean;
|
|
71
|
+
packageManagerConfig?: boolean;
|
|
72
|
+
referenceBuckets?: readonly SourceBucket[];
|
|
73
|
+
}
|
|
74
|
+
export interface ConfigInventoryContext {
|
|
75
|
+
trackedFiles: readonly string[];
|
|
76
|
+
/** Return null for a missing/non-text file. Reader failures propagate. */
|
|
77
|
+
readTrackedFile(file: string): string | null;
|
|
78
|
+
}
|
|
79
|
+
export interface DynamicConfigReference {
|
|
80
|
+
namespace: string;
|
|
81
|
+
kind: 'definition' | 'registry';
|
|
82
|
+
}
|
|
83
|
+
export interface ConfigInventoryOptions {
|
|
84
|
+
/** Return null to exclude a file before it is read. */
|
|
85
|
+
describeFile(file: string): ConfigSourceRules | null;
|
|
86
|
+
envContract?: readonly TypedEnvContractEntry[];
|
|
87
|
+
envConstants?: ReadonlyMap<string, string>;
|
|
88
|
+
envHelperNames?: readonly string[];
|
|
89
|
+
/** Sensitivity labels from least to most sensitive. Defaults to internal, public, secret. */
|
|
90
|
+
sensitivityOrder?: readonly string[];
|
|
91
|
+
collectDynamicConfigs?(file: string, source: string): readonly DynamicConfigReference[];
|
|
92
|
+
annotateEnv?(row: Readonly<EnvVarInventoryRow>): {
|
|
93
|
+
classifications: string[];
|
|
94
|
+
reviewReason: string | null;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import ts from './typescript-api.mts';
|
|
2
|
+
import type { ContractSchemaNode } from '../openapi-document/contract-schema-types.mts';
|
|
3
|
+
import type { ExtractContractSchemaOptions } from './types.mts';
|
|
4
|
+
export type ExtractionContext = {
|
|
5
|
+
checker: ts.TypeChecker;
|
|
6
|
+
definitions: Map<string, ContractSchemaNode>;
|
|
7
|
+
definitionTypes: Map<string, ts.Type>;
|
|
8
|
+
activeTypes: Map<ts.Type, string>;
|
|
9
|
+
options: ExtractContractSchemaOptions;
|
|
10
|
+
};
|
|
11
|
+
type SchemaForType = (type: ts.Type, context: ExtractionContext) => ContractSchemaNode;
|
|
12
|
+
export declare function boundedArraySchema(type: ts.Type, context: ExtractionContext, schemaForType: SchemaForType): Extract<ContractSchemaNode, {
|
|
13
|
+
type: 'array';
|
|
14
|
+
}> | undefined;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import ts from './typescript-api.mjs';
|
|
2
|
+
import { unsupportedType } from './contract-schema-type-utils.mjs';
|
|
3
|
+
export function boundedArraySchema(type, context, schemaForType) {
|
|
4
|
+
const alias = context.options.boundedArrayAlias;
|
|
5
|
+
if (!alias || type.aliasSymbol?.name !== alias)
|
|
6
|
+
return undefined;
|
|
7
|
+
const [itemType, minItemsType, maxItemsType, uniqueItemsType] = type.aliasTypeArguments ?? [];
|
|
8
|
+
if (!itemType || !minItemsType || !maxItemsType || !uniqueItemsType) {
|
|
9
|
+
throw unsupportedType(type, context.checker, `${alias} requires four type arguments`);
|
|
10
|
+
}
|
|
11
|
+
const minItems = numberLiteralValue(minItemsType, type, context.checker, alias);
|
|
12
|
+
const maxItems = numberLiteralValue(maxItemsType, type, context.checker, alias);
|
|
13
|
+
if (minItems < 0 || maxItems < minItems) {
|
|
14
|
+
throw unsupportedType(type, context.checker, `${alias} has invalid item bounds`);
|
|
15
|
+
}
|
|
16
|
+
if (!(uniqueItemsType.flags & ts.TypeFlags.BooleanLiteral)) {
|
|
17
|
+
throw unsupportedType(type, context.checker, `${alias} uniqueness must be literal`);
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
type: 'array',
|
|
21
|
+
items: schemaForType(itemType, context),
|
|
22
|
+
minItems,
|
|
23
|
+
maxItems,
|
|
24
|
+
uniqueItems: uniqueItemsType.intrinsicName === 'true',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function numberLiteralValue(type, owner, checker, alias) {
|
|
28
|
+
if (!(type.flags & ts.TypeFlags.NumberLiteral)) {
|
|
29
|
+
throw unsupportedType(owner, checker, `${alias} bounds must be numeric literals`);
|
|
30
|
+
}
|
|
31
|
+
return type.value;
|
|
32
|
+
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import ts from './typescript-api.mts';
|
|
2
|
-
import type
|
|
2
|
+
import { type ExtractionContext } from './contract-schema-bounded-array.mts';
|
|
3
3
|
import type { ExtractContractSchemaOptions, ExtractedResponseContract } from './types.mts';
|
|
4
|
-
export type ExtractionContext
|
|
5
|
-
checker: ts.TypeChecker;
|
|
6
|
-
definitions: Map<string, ContractSchemaNode>;
|
|
7
|
-
definitionTypes: Map<string, ts.Type>;
|
|
8
|
-
activeTypes: Map<ts.Type, string>;
|
|
9
|
-
options: ExtractContractSchemaOptions;
|
|
10
|
-
};
|
|
4
|
+
export type { ExtractionContext };
|
|
11
5
|
export declare function extractContractSchema(type: ts.Type, checker: ts.TypeChecker, source: string, options?: ExtractContractSchemaOptions): ExtractedResponseContract;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ts from './typescript-api.mjs';
|
|
2
2
|
import { hashContractSchema } from '../openapi-document/contract-schema-canonical.mjs';
|
|
3
|
+
import { boundedArraySchema } from './contract-schema-bounded-array.mjs';
|
|
3
4
|
import { objectSchema, tupleSchema } from './contract-schema-object-tuple.mjs';
|
|
4
5
|
import { assertNotClass, distinctNodes, jsonPromiseType, jsonSerializedType, namedObjectDefinition, unsupportedType, } from './contract-schema-type-utils.mjs';
|
|
5
6
|
export function extractContractSchema(type, checker, source, options = {}) {
|
|
@@ -95,7 +96,7 @@ function schemaForType(type, context) {
|
|
|
95
96
|
return schemaForType(promisedType, context);
|
|
96
97
|
if (checker.isTupleType(type))
|
|
97
98
|
return tupleSchema(type, context, schemaForType);
|
|
98
|
-
const constrainedArray = boundedArraySchema(type, context);
|
|
99
|
+
const constrainedArray = boundedArraySchema(type, context, schemaForType);
|
|
99
100
|
if (constrainedArray)
|
|
100
101
|
return constrainedArray;
|
|
101
102
|
if (checker.isArrayType(type) || checker.isArrayLikeType(type)) {
|
|
@@ -118,36 +119,6 @@ function schemaForType(type, context) {
|
|
|
118
119
|
}
|
|
119
120
|
return objectSchema(type, context, schemaForType);
|
|
120
121
|
}
|
|
121
|
-
function boundedArraySchema(type, context) {
|
|
122
|
-
const alias = context.options.boundedArrayAlias;
|
|
123
|
-
if (!alias || type.aliasSymbol?.name !== alias)
|
|
124
|
-
return undefined;
|
|
125
|
-
const [itemType, minItemsType, maxItemsType, uniqueItemsType] = type.aliasTypeArguments ?? [];
|
|
126
|
-
if (!itemType || !minItemsType || !maxItemsType || !uniqueItemsType) {
|
|
127
|
-
throw unsupportedType(type, context.checker, `${alias} requires four type arguments`);
|
|
128
|
-
}
|
|
129
|
-
const minItems = numberLiteralValue(minItemsType, type, context.checker, alias);
|
|
130
|
-
const maxItems = numberLiteralValue(maxItemsType, type, context.checker, alias);
|
|
131
|
-
if (minItems < 0 || maxItems < minItems) {
|
|
132
|
-
throw unsupportedType(type, context.checker, `${alias} has invalid item bounds`);
|
|
133
|
-
}
|
|
134
|
-
if (!(uniqueItemsType.flags & ts.TypeFlags.BooleanLiteral)) {
|
|
135
|
-
throw unsupportedType(type, context.checker, `${alias} uniqueness must be literal`);
|
|
136
|
-
}
|
|
137
|
-
return {
|
|
138
|
-
type: 'array',
|
|
139
|
-
items: schemaForType(itemType, context),
|
|
140
|
-
minItems,
|
|
141
|
-
maxItems,
|
|
142
|
-
uniqueItems: uniqueItemsType.intrinsicName === 'true',
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
function numberLiteralValue(type, owner, checker, alias) {
|
|
146
|
-
if (!(type.flags & ts.TypeFlags.NumberLiteral)) {
|
|
147
|
-
throw unsupportedType(owner, checker, `${alias} bounds must be numeric literals`);
|
|
148
|
-
}
|
|
149
|
-
return type.value;
|
|
150
|
-
}
|
|
151
122
|
function schemaForNamedType(type, name, context, build) {
|
|
152
123
|
const existingType = context.definitionTypes.get(name);
|
|
153
124
|
/* v8 ignore start -- distinct checker types sharing a definition name */
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type PrefixTransportIdentity } from './keys.mts';
|
|
2
|
+
type DownloadedTransportObject = {
|
|
3
|
+
readonly key: string;
|
|
4
|
+
readonly url: string;
|
|
5
|
+
readonly attempt: number;
|
|
6
|
+
readonly byteLength: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
9
|
+
export declare function exactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean;
|
|
10
|
+
export declare function validUrl(value: unknown): value is string;
|
|
11
|
+
export declare function parseObject(raw: unknown, expected: PrefixTransportIdentity, suite: string, kind: 'lcov' | 'manifest' | 'blob'): DownloadedTransportObject;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { DEFAULT_MAX_BODY_BYTES } from './constants.mjs';
|
|
2
|
+
import { parseTransportObjectKey } from './keys.mjs';
|
|
3
|
+
export function isRecord(value) {
|
|
4
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
export function exactKeys(value, keys) {
|
|
7
|
+
return Object.keys(value).toSorted().join('\0') === keys.toSorted().join('\0');
|
|
8
|
+
}
|
|
9
|
+
export function validUrl(value) {
|
|
10
|
+
return typeof value === 'string' && URL.canParse(value) && /^https?:/i.test(value);
|
|
11
|
+
}
|
|
12
|
+
export function parseObject(raw, expected, suite, kind) {
|
|
13
|
+
if (!isRecord(raw) ||
|
|
14
|
+
!exactKeys(raw, ['attempt', 'byteLength', 'key', 'url']) ||
|
|
15
|
+
!validUrl(raw.url) ||
|
|
16
|
+
typeof raw.key !== 'string' ||
|
|
17
|
+
typeof raw.attempt !== 'number' ||
|
|
18
|
+
typeof raw.byteLength !== 'number' ||
|
|
19
|
+
!Number.isSafeInteger(raw.attempt) ||
|
|
20
|
+
!Number.isSafeInteger(raw.byteLength) ||
|
|
21
|
+
raw.byteLength < 0 ||
|
|
22
|
+
raw.byteLength > DEFAULT_MAX_BODY_BYTES)
|
|
23
|
+
throw new Error('Discovered transport object is invalid');
|
|
24
|
+
const parsed = parseTransportObjectKey(raw.key, expected);
|
|
25
|
+
if (!parsed || parsed.suite !== suite || parsed.kind !== kind || parsed.attempt !== raw.attempt)
|
|
26
|
+
throw new Error('Discovered transport object key is invalid');
|
|
27
|
+
return raw;
|
|
28
|
+
}
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { VITEST_SUITE_PATTERN } from '../vitest-blob-manifest/index.mjs';
|
|
2
2
|
import { DEFAULT_MAX_BODY_BYTES } from './constants.mjs';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
|
-
}
|
|
7
|
-
function exactKeys(value, keys) {
|
|
8
|
-
return Object.keys(value).toSorted().join('\0') === keys.toSorted().join('\0');
|
|
9
|
-
}
|
|
10
|
-
function validUrl(value) {
|
|
11
|
-
return typeof value === 'string' && URL.canParse(value) && /^https?:/i.test(value);
|
|
12
|
-
}
|
|
3
|
+
import { exactKeys, isRecord, parseObject, validUrl } from './control-v2-parse-object.mjs';
|
|
4
|
+
import { assertPrefixTransportIdentity, transportPrefix, } from './keys.mjs';
|
|
13
5
|
function identity(raw) {
|
|
14
6
|
if (!isRecord(raw.run) || !exactKeys(raw.run, ['id', 'controlAttempt']))
|
|
15
7
|
return null;
|
|
@@ -32,23 +24,6 @@ function identity(raw) {
|
|
|
32
24
|
}
|
|
33
25
|
return result;
|
|
34
26
|
}
|
|
35
|
-
function parseObject(raw, expected, suite, kind) {
|
|
36
|
-
if (!isRecord(raw) ||
|
|
37
|
-
!exactKeys(raw, ['attempt', 'byteLength', 'key', 'url']) ||
|
|
38
|
-
!validUrl(raw.url) ||
|
|
39
|
-
typeof raw.key !== 'string' ||
|
|
40
|
-
typeof raw.attempt !== 'number' ||
|
|
41
|
-
typeof raw.byteLength !== 'number' ||
|
|
42
|
-
!Number.isSafeInteger(raw.attempt) ||
|
|
43
|
-
!Number.isSafeInteger(raw.byteLength) ||
|
|
44
|
-
raw.byteLength < 0 ||
|
|
45
|
-
raw.byteLength > DEFAULT_MAX_BODY_BYTES)
|
|
46
|
-
throw new Error('Discovered transport object is invalid');
|
|
47
|
-
const parsed = parseTransportObjectKey(raw.key, expected);
|
|
48
|
-
if (!parsed || parsed.suite !== suite || parsed.kind !== kind || parsed.attempt !== raw.attempt)
|
|
49
|
-
throw new Error('Discovered transport object key is invalid');
|
|
50
|
-
return raw;
|
|
51
|
-
}
|
|
52
27
|
function parseDownloadMap(raw, expected, kind) {
|
|
53
28
|
if (!isRecord(raw))
|
|
54
29
|
throw new Error('Discovered transport object map is invalid');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const LICENSE_AUDIT_DIRECTORY_PREFIX = "dependency-license-audit-";
|
|
2
|
+
export interface LicenseAuditReclaimOptions {
|
|
3
|
+
readonly graceMs?: number;
|
|
4
|
+
readonly isOwnerAlive?: (pid: number, directoryMtimeMs: number) => boolean;
|
|
5
|
+
readonly now?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface LicenseAuditWorkspaceOptions extends LicenseAuditReclaimOptions {
|
|
8
|
+
readonly directory?: string;
|
|
9
|
+
readonly pid?: number;
|
|
10
|
+
}
|
|
11
|
+
interface OwnerAliveDependencies {
|
|
12
|
+
readonly isProcessAlive?: (pid: number) => boolean;
|
|
13
|
+
readonly readProcessStartMs?: (pid: number) => number | undefined;
|
|
14
|
+
}
|
|
15
|
+
/** Parses `ps -o lstart=` output. Exported for tests of unparseable dates. */
|
|
16
|
+
export declare function parseProcessStart(stdout: string): number | undefined;
|
|
17
|
+
export declare function readProcessStartMs(pid: number): number | undefined;
|
|
18
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* A directory still belongs to its creator when that PID is alive and the process started
|
|
21
|
+
* before the directory. A recycled PID belongs to a newer process and is not the owner.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isAuditOwnerAlive(pid: number, directoryMtimeMs: number, dependencies?: OwnerAliveDependencies): boolean;
|
|
24
|
+
export declare function writeAuditPid(directory: string, pid?: number): void;
|
|
25
|
+
export declare function removeAuditDirectory(directory: string): void;
|
|
26
|
+
/** Deletes leftover audit directories whose owner process is gone, including after SIGKILL. */
|
|
27
|
+
export declare function reclaimStaleLicenseAuditDirectories(directory: string, options?: LicenseAuditReclaimOptions): void;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { chmodSync, lstatSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
export const LICENSE_AUDIT_DIRECTORY_PREFIX = 'dependency-license-audit-';
|
|
5
|
+
const LICENSE_AUDIT_PID_FILE = 'audit.pid';
|
|
6
|
+
const STALE_AUDIT_GRACE_MS = 60_000;
|
|
7
|
+
const OWNER_START_SLACK_MS = 1_000;
|
|
8
|
+
const MAX_PID = 2_147_483_647;
|
|
9
|
+
function hasCode(error, code) {
|
|
10
|
+
return Boolean(error && typeof error === 'object' && 'code' in error && error.code === code);
|
|
11
|
+
}
|
|
12
|
+
/** Parses `ps -o lstart=` output. Exported for tests of unparseable dates. */
|
|
13
|
+
export function parseProcessStart(stdout) {
|
|
14
|
+
const parsed = Date.parse(stdout.trim());
|
|
15
|
+
if (Number.isNaN(parsed))
|
|
16
|
+
return undefined;
|
|
17
|
+
return parsed;
|
|
18
|
+
}
|
|
19
|
+
export function readProcessStartMs(pid) {
|
|
20
|
+
const result = spawnSync('ps', ['-p', String(pid), '-o', 'lstart='], { encoding: 'utf8' });
|
|
21
|
+
if (result.status !== 0)
|
|
22
|
+
return undefined;
|
|
23
|
+
return parseProcessStart(result.stdout);
|
|
24
|
+
}
|
|
25
|
+
export function isProcessAlive(pid) {
|
|
26
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || pid > MAX_PID)
|
|
27
|
+
return false;
|
|
28
|
+
try {
|
|
29
|
+
process.kill(pid, 0);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
return !hasCode(error, 'ESRCH');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A directory still belongs to its creator when that PID is alive and the process started
|
|
38
|
+
* before the directory. A recycled PID belongs to a newer process and is not the owner.
|
|
39
|
+
*/
|
|
40
|
+
export function isAuditOwnerAlive(pid, directoryMtimeMs, dependencies = {}) {
|
|
41
|
+
if (!(dependencies.isProcessAlive ?? isProcessAlive)(pid))
|
|
42
|
+
return false;
|
|
43
|
+
const started = (dependencies.readProcessStartMs ?? readProcessStartMs)(pid);
|
|
44
|
+
if (started === undefined)
|
|
45
|
+
return true;
|
|
46
|
+
return started <= directoryMtimeMs + OWNER_START_SLACK_MS;
|
|
47
|
+
}
|
|
48
|
+
export function writeAuditPid(directory, pid = process.pid) {
|
|
49
|
+
const path = join(directory, LICENSE_AUDIT_PID_FILE);
|
|
50
|
+
writeFileSync(path, `${String(pid)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
51
|
+
chmodSync(path, 0o600);
|
|
52
|
+
}
|
|
53
|
+
function readAuditPid(directory) {
|
|
54
|
+
try {
|
|
55
|
+
const text = readFileSync(join(directory, LICENSE_AUDIT_PID_FILE), 'utf8').trim();
|
|
56
|
+
if (!/^[1-9]\d*$/.test(text))
|
|
57
|
+
return undefined;
|
|
58
|
+
const pid = Number.parseInt(text, 10);
|
|
59
|
+
return Number.isSafeInteger(pid) && pid <= MAX_PID ? pid : undefined;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (hasCode(error, 'ENOENT'))
|
|
63
|
+
return undefined;
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function removeAuditDirectory(directory) {
|
|
68
|
+
const stat = lstatSync(directory, { throwIfNoEntry: false });
|
|
69
|
+
if (!stat)
|
|
70
|
+
return;
|
|
71
|
+
if (stat.isSymbolicLink()) {
|
|
72
|
+
unlinkSync(directory);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!stat.isDirectory())
|
|
76
|
+
return;
|
|
77
|
+
rmSync(directory, { force: true, recursive: true });
|
|
78
|
+
}
|
|
79
|
+
function reclaimNames(directory) {
|
|
80
|
+
try {
|
|
81
|
+
return readdirSync(directory, { encoding: 'utf8' });
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (hasCode(error, 'ENOENT'))
|
|
85
|
+
return [];
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function shouldRemoveAuditDirectory(directory, mtimeMs, now, graceMs, isOwnerAlive) {
|
|
90
|
+
const pid = readAuditPid(directory);
|
|
91
|
+
if (pid === undefined)
|
|
92
|
+
return now - mtimeMs >= graceMs;
|
|
93
|
+
return !isOwnerAlive(pid, mtimeMs);
|
|
94
|
+
}
|
|
95
|
+
/** Deletes leftover audit directories whose owner process is gone, including after SIGKILL. */
|
|
96
|
+
export function reclaimStaleLicenseAuditDirectories(directory, options = {}) {
|
|
97
|
+
const now = options.now ?? Date.now();
|
|
98
|
+
const graceMs = options.graceMs ?? STALE_AUDIT_GRACE_MS;
|
|
99
|
+
const isOwnerAlive = options.isOwnerAlive ?? isAuditOwnerAlive;
|
|
100
|
+
for (const name of reclaimNames(directory)) {
|
|
101
|
+
if (!name.startsWith(LICENSE_AUDIT_DIRECTORY_PREFIX))
|
|
102
|
+
continue;
|
|
103
|
+
const path = join(directory, name);
|
|
104
|
+
const stat = lstatSync(path);
|
|
105
|
+
if (stat.isSymbolicLink()) {
|
|
106
|
+
unlinkSync(path);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (!stat.isDirectory())
|
|
110
|
+
continue;
|
|
111
|
+
if (shouldRemoveAuditDirectory(path, stat.mtimeMs, now, graceMs, isOwnerAlive)) {
|
|
112
|
+
rmSync(path, { force: true, recursive: true });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type SignalListener = () => void;
|
|
2
|
+
export interface AuditSignalDependencies {
|
|
3
|
+
readonly raiseSignal?: (signal: NodeJS.Signals) => void;
|
|
4
|
+
readonly subscribe?: (signal: NodeJS.Signals, listener: SignalListener) => void;
|
|
5
|
+
readonly unsubscribe?: (signal: NodeJS.Signals, listener: SignalListener) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Runs an audit while SIGINT, SIGTERM, and SIGHUP abort it, remove its workspace, and are raised
|
|
9
|
+
* again so the process still exits from that signal.
|
|
10
|
+
*/
|
|
11
|
+
export declare function withAuditSignalCleanup<T>(cleanup: () => void, run: (signal: AbortSignal) => Promise<T>, dependencies?: AuditSignalDependencies): Promise<T>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const AUDIT_SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP'];
|
|
2
|
+
/**
|
|
3
|
+
* Runs an audit while SIGINT, SIGTERM, and SIGHUP abort it, remove its workspace, and are raised
|
|
4
|
+
* again so the process still exits from that signal.
|
|
5
|
+
*/
|
|
6
|
+
export async function withAuditSignalCleanup(cleanup, run, dependencies = {}) {
|
|
7
|
+
const controller = new AbortController();
|
|
8
|
+
let raised;
|
|
9
|
+
const handlers = AUDIT_SIGNALS.map((signal) => {
|
|
10
|
+
const listener = () => {
|
|
11
|
+
if (raised !== undefined)
|
|
12
|
+
return;
|
|
13
|
+
raised = signal;
|
|
14
|
+
controller.abort(signal);
|
|
15
|
+
};
|
|
16
|
+
if (dependencies.subscribe)
|
|
17
|
+
dependencies.subscribe(signal, listener);
|
|
18
|
+
else
|
|
19
|
+
process.on(signal, listener);
|
|
20
|
+
return { listener, signal };
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
return await run(controller.signal);
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
for (const { listener, signal } of handlers) {
|
|
27
|
+
if (dependencies.unsubscribe)
|
|
28
|
+
dependencies.unsubscribe(signal, listener);
|
|
29
|
+
else
|
|
30
|
+
process.removeListener(signal, listener);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
cleanup();
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
if (raised !== undefined) {
|
|
37
|
+
if (dependencies.raiseSignal)
|
|
38
|
+
dependencies.raiseSignal(raised);
|
|
39
|
+
else
|
|
40
|
+
process.kill(process.pid, raised);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface DirectoryIdentity {
|
|
2
|
+
readonly isDirectory: () => boolean;
|
|
3
|
+
readonly isSymbolicLink: () => boolean;
|
|
4
|
+
readonly mode: number;
|
|
5
|
+
readonly uid: number;
|
|
6
|
+
}
|
|
7
|
+
/** pnpm's cache directory. The audit store stays beside it so it is not the developer store. */
|
|
8
|
+
export declare function pnpmCacheDirectory(env?: NodeJS.ProcessEnv, home?: string, hostPlatform?: NodeJS.Platform): string;
|
|
9
|
+
export declare function licenseAuditStoreDirectory(env?: NodeJS.ProcessEnv, home?: string, hostPlatform?: NodeJS.Platform): string;
|
|
10
|
+
export declare function assertOwnedDirectory(stat: DirectoryIdentity, uid: number | undefined, directory: string): void;
|
|
11
|
+
/** Creates the dedicated content-addressed store used by repeat license audits. */
|
|
12
|
+
export declare function ensurePrivateLicenseAuditStore(directory: string): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { chmodSync, lstatSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const STORE_DIRECTORY_NAME = 'dependency-license-audit-store';
|
|
5
|
+
/** pnpm's cache directory. The audit store stays beside it so it is not the developer store. */
|
|
6
|
+
export function pnpmCacheDirectory(env = process.env, home = homedir(), hostPlatform = process.platform) {
|
|
7
|
+
if (hostPlatform === 'darwin')
|
|
8
|
+
return join(home, 'Library', 'Caches', 'pnpm');
|
|
9
|
+
if (hostPlatform === 'win32') {
|
|
10
|
+
return join(env.LOCALAPPDATA ?? join(home, 'AppData', 'Local'), 'pnpm-cache');
|
|
11
|
+
}
|
|
12
|
+
return join(env.XDG_CACHE_HOME ?? join(home, '.cache'), 'pnpm');
|
|
13
|
+
}
|
|
14
|
+
export function licenseAuditStoreDirectory(env = process.env, home = homedir(), hostPlatform = process.platform) {
|
|
15
|
+
return join(pnpmCacheDirectory(env, home, hostPlatform), STORE_DIRECTORY_NAME);
|
|
16
|
+
}
|
|
17
|
+
export function assertOwnedDirectory(stat, uid, directory) {
|
|
18
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
19
|
+
throw new Error(`dependency license audit store is not a real directory: ${directory}`);
|
|
20
|
+
}
|
|
21
|
+
if (uid !== undefined && stat.uid !== uid) {
|
|
22
|
+
throw new Error(`dependency license audit store is not owned by the current user: ${directory}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Creates the dedicated content-addressed store used by repeat license audits. */
|
|
26
|
+
export function ensurePrivateLicenseAuditStore(directory) {
|
|
27
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
28
|
+
const stat = lstatSync(directory);
|
|
29
|
+
assertOwnedDirectory(stat, process.getuid?.(), directory);
|
|
30
|
+
if ((stat.mode & 0o077) !== 0)
|
|
31
|
+
chmodSync(directory, 0o700);
|
|
32
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { PnpmExecutor, PnpmLicenseReport } from './types.mts';
|
|
2
2
|
import { type PnpmLicenseAuditWorkspace } from './workspace.mts';
|
|
3
3
|
export interface CollectPnpmLicenseReportOptions {
|
|
4
|
+
readonly ensureStore?: (directory: string) => void;
|
|
4
5
|
readonly execute?: PnpmExecutor;
|
|
5
6
|
readonly prepareWorkspace?: (repoRoot: string, lockfileSource: string, workspaceSource: string) => PnpmLicenseAuditWorkspace;
|
|
6
7
|
readonly readFile?: (path: string, encoding: 'utf8') => string;
|
|
8
|
+
readonly storeDir?: string;
|
|
7
9
|
}
|
|
8
10
|
/** Collects licenses for every platform represented in a pnpm lockfile. */
|
|
9
|
-
export declare function collectPnpmLicenseReport(repoRoot: string, options?: CollectPnpmLicenseReportOptions): PnpmLicenseReport
|
|
11
|
+
export declare function collectPnpmLicenseReport(repoRoot: string, options?: CollectPnpmLicenseReportOptions): Promise<PnpmLicenseReport>;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
1
|
import { readFileSync } from 'node:fs';
|
|
3
2
|
import { join } from 'node:path';
|
|
3
|
+
import { withAuditSignalCleanup } from './audit-signals.mjs';
|
|
4
|
+
import { ensurePrivateLicenseAuditStore, licenseAuditStoreDirectory } from './audit-store.mjs';
|
|
5
|
+
import { executePnpm } from './execute-pnpm.mjs';
|
|
4
6
|
import { parsePnpmLicenseReport } from './report.mjs';
|
|
5
7
|
import { preparePnpmLicenseAuditWorkspace } from './workspace.mjs';
|
|
6
8
|
const DEFAULT_OPTIONS = {
|
|
7
|
-
|
|
9
|
+
ensureStore: ensurePrivateLicenseAuditStore,
|
|
10
|
+
execute: executePnpm,
|
|
8
11
|
prepareWorkspace: preparePnpmLicenseAuditWorkspace,
|
|
9
12
|
readFile: readFileSync,
|
|
10
13
|
};
|
|
@@ -19,22 +22,17 @@ function assertCommandSucceeded(label, result) {
|
|
|
19
22
|
}
|
|
20
23
|
}
|
|
21
24
|
/** Collects licenses for every platform represented in a pnpm lockfile. */
|
|
22
|
-
export function collectPnpmLicenseReport(repoRoot, options = {}) {
|
|
23
|
-
const { execute, prepareWorkspace, readFile } = { ...DEFAULT_OPTIONS, ...options };
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const storeConfig = `--config.store-dir=${
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
encoding: 'utf8',
|
|
32
|
-
});
|
|
25
|
+
export async function collectPnpmLicenseReport(repoRoot, options = {}) {
|
|
26
|
+
const { ensureStore, execute, prepareWorkspace, readFile } = { ...DEFAULT_OPTIONS, ...options };
|
|
27
|
+
const auditWorkspace = prepareWorkspace(repoRoot, readFile(join(repoRoot, 'pnpm-lock.yaml'), 'utf8'), readFile(join(repoRoot, 'pnpm-workspace.yaml'), 'utf8'));
|
|
28
|
+
const storeDir = options.storeDir ?? licenseAuditStoreDirectory();
|
|
29
|
+
return withAuditSignalCleanup(auditWorkspace.cleanup, async (signal) => {
|
|
30
|
+
ensureStore(storeDir);
|
|
31
|
+
const storeConfig = `--config.store-dir=${storeDir}`;
|
|
32
|
+
const commandOptions = { cwd: auditWorkspace.cwd, encoding: 'utf8', signal };
|
|
33
|
+
const fetchResult = await execute('pnpm', [storeConfig, 'fetch', '--ignore-scripts'], commandOptions);
|
|
33
34
|
assertCommandSucceeded('pnpm fetch', fetchResult);
|
|
34
|
-
const result = execute('pnpm', [storeConfig, 'licenses', 'list', '--json'],
|
|
35
|
-
cwd: auditWorkspace.cwd,
|
|
36
|
-
encoding: 'utf8',
|
|
37
|
-
});
|
|
35
|
+
const result = await execute('pnpm', [storeConfig, 'licenses', 'list', '--json'], commandOptions);
|
|
38
36
|
assertCommandSucceeded('pnpm licenses list --json', result);
|
|
39
37
|
try {
|
|
40
38
|
return parsePnpmLicenseReport(JSON.parse(result.stdout));
|
|
@@ -44,8 +42,5 @@ export function collectPnpmLicenseReport(repoRoot, options = {}) {
|
|
|
44
42
|
cause: error,
|
|
45
43
|
});
|
|
46
44
|
}
|
|
47
|
-
}
|
|
48
|
-
finally {
|
|
49
|
-
auditWorkspace.cleanup();
|
|
50
|
-
}
|
|
45
|
+
});
|
|
51
46
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
import type { PnpmExecutor } from './types.mts';
|
|
3
|
+
export declare function signalFromAbort(signal: AbortSignal): NodeJS.Signals;
|
|
4
|
+
export declare function stopChildProcess(child: {
|
|
5
|
+
kill: (signal?: NodeJS.Signals) => boolean;
|
|
6
|
+
pid?: number | undefined;
|
|
7
|
+
}, signal: NodeJS.Signals): void;
|
|
8
|
+
export declare function collectChildStream(stream: Readable | null, chunks: string[], onOverflow: () => void): void;
|
|
9
|
+
/** Runs pnpm in its own process group so a signal can reach it and its children. */
|
|
10
|
+
export declare const executePnpm: PnpmExecutor;
|