@blundergoat/gruff-ts 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ # Output Formats
2
+
3
+ `gruff-ts analyse --format <format>` renders the same analysis data for
4
+ different consumers. The combined legacy page remains at
5
+ [Reports And CI](reports-and-ci.md).
6
+
7
+ ## Text
8
+
9
+ Use `text` for local terminal scans:
10
+
11
+ ```sh
12
+ ./bin/gruff-ts analyse src --format text --fail-on=warning
13
+ ```
14
+
15
+ ## JSON
16
+
17
+ Use `json` for automation. JSON reports use `gruff.analysis.v1`.
18
+
19
+ ```sh
20
+ ./bin/gruff-ts analyse src --format=json --fail-on=none > gruff-ts.json
21
+ ```
22
+
23
+ ## HTML
24
+
25
+ Use `html` for archived human review or dashboard scan output:
26
+
27
+ ```sh
28
+ ./bin/gruff-ts report src --format=html --output gruff-ts.html
29
+ ```
30
+
31
+ ## Markdown
32
+
33
+ Use `markdown` for pull request comments and release notes.
34
+
35
+ ## GitHub
36
+
37
+ Use `github` inside GitHub Actions to emit workflow annotations.
38
+
39
+ ## Hotspot
40
+
41
+ Use `hotspot` for compact score and offender analysis.
42
+
43
+ ## SARIF
44
+
45
+ Use `sarif` for GitHub code scanning or other SARIF consumers:
46
+
47
+ ```sh
48
+ ./bin/gruff-ts analyse src --format=sarif --fail-on=none > gruff-ts.sarif
49
+ ```
50
+
51
+ ## Summary
52
+
53
+ `summary` has its own compact text/JSON contract:
54
+
55
+ ```sh
56
+ ./bin/gruff-ts summary src --format=json --top=5 --fail-on=none
57
+ ```
58
+
59
+ TypeScript keeps the existing `summary` analysis flags such as `--diff`,
60
+ `--baseline`, and `--generate-baseline` as extensions.
61
+
62
+ ## Exit Codes
63
+
64
+ `analyse` exits `1` when at least one finding meets `--fail-on`. Use
65
+ `--fail-on none` for report-only jobs.
@@ -5,11 +5,11 @@ subsequent `0.1.x` patch releases.
5
5
 
6
6
  ## Bump The Version
7
7
 
8
- `scripts/bump-version.sh <semver>` updates `package.json` and
9
- `src/constants.ts` together so the CLI `--version` output and the published
10
- `@blundergoat/gruff-ts` package version cannot drift apart. For the initial
11
- `0.1.0` release, the version should already be `0.1.0`; use `--check` instead
12
- of bumping unless the release version changes.
8
+ `scripts/bump-version.sh <semver>` updates `package.json`,
9
+ `package-lock.json`, and `src/constants.ts` together so the CLI `--version`
10
+ output and the published `@blundergoat/gruff-ts` package version cannot drift
11
+ apart. For the initial `0.1.0` release, the version should already be `0.1.0`;
12
+ use `--check` instead of bumping unless the release version changes.
13
13
 
14
14
  ```bash
15
15
  scripts/bump-version.sh --check
@@ -30,9 +30,10 @@ update `CHANGELOG.md` and run `npm run check`.
30
30
  explicitly accepted `human-verification-pending` milestones.
31
31
  - [ ] `LICENSE` is present and `package.json` `license` field matches.
32
32
  - [ ] `npm run check` passes.
33
- - [ ] `scripts/preflight-checks.sh` passes (runs `npm run check`, a full
34
- `gruff-ts` self-scan, and `shellcheck` on `scripts/*.sh` when
35
- `shellcheck` is installed).
33
+ - [ ] `scripts/preflight-checks.sh` passes (checks version lockstep and npm
34
+ publication status, runs `npm audit --audit-level=moderate`,
35
+ `npm run check`, a full `gruff-ts` self-scan, and `shellcheck` on
36
+ `scripts/*.sh` when `shellcheck` is installed).
36
37
  - [ ] `npm pack --dry-run` shows only publishable runtime, docs, scripts, and
37
38
  metadata files.
38
39
  - [ ] Local smoke scan succeeds:
@@ -58,8 +59,9 @@ The package should include:
58
59
  - `bin/gruff-ts`
59
60
  - `src/` (all runtime `.ts` files; `src/**/*.test.ts` files are excluded by
60
61
  `.npmignore`)
61
- - `scripts/` (`bump-version.sh`, `check.sh`, `preflight-checks.sh`,
62
- `npm-publish.sh`, `start-dev.sh`, `test-performance.sh`)
62
+ - `scripts/` (`bump-version.sh`, `check.sh`, `dependency-install.sh`,
63
+ `dependency-update.sh`, `preflight-checks.sh`, `npm-publish.sh`,
64
+ `start-dev.sh`, `test-performance.sh`)
63
65
  - `fixtures/sample.ts`
64
66
  - `README.md`
65
67
  - `CHANGELOG.md`
package/docs/rules.md ADDED
@@ -0,0 +1,177 @@
1
+ # Rules
2
+
3
+ `gruff-ts` exposes 119 rules across 11 pillars. This list is generated from the
4
+ public rule catalogue used by `gruff-ts list-rules`; severity, confidence,
5
+ thresholds, and option names are the defaults before project config overrides.
6
+
7
+ Use the CLI when you need machine-readable metadata:
8
+
9
+ ```bash
10
+ gruff-ts list-rules --format=json
11
+ ```
12
+
13
+ ## Pillar Counts
14
+
15
+ - complexity: 3
16
+ - dead-code: 1
17
+ - design: 6
18
+ - documentation: 17
19
+ - maintainability: 14
20
+ - modernisation: 14
21
+ - naming: 10
22
+ - security: 27
23
+ - sensitive-data: 8
24
+ - size: 4
25
+ - test-quality: 15
26
+
27
+ ## Complexity
28
+
29
+ - `complexity.cognitive` (warning; high confidence; threshold 15): Flags functions with high combined branch and nesting complexity.
30
+ - `complexity.cyclomatic` (warning; high confidence; threshold 15): Flags functions with many independent branch paths.
31
+ - `complexity.npath` (warning; medium confidence; threshold 200): Flags functions with high approximate NPath complexity.
32
+
33
+ ## Dead Code
34
+
35
+ - `dead-code.unused-private-method` (advisory; low confidence): Flags private methods without an apparent same-file call site.
36
+
37
+ ## Design
38
+
39
+ - `design.circular-import` (warning; medium confidence): Flags simple relative import cycles inside the discovered source set.
40
+ - `design.deep-relative-import` (advisory; medium confidence; threshold 2): Flags relative imports that climb too many parent directories.
41
+ - `design.god-function` (warning; high confidence): Flags functions that are both long and complex.
42
+ - `design.large-module-concentration` (advisory; medium confidence; threshold 55; options: minFiles, minLines): Flags a production module that dominates project source lines.
43
+ - `design.package-bin-missing` (warning; high confidence): Flags package bin entries that point at missing files.
44
+ - `design.package-bin-not-executable` (warning; high confidence): Flags package bin targets that are not executable.
45
+
46
+ ## Documentation
47
+
48
+ - `docs.fixture-purpose-missing` (advisory; medium confidence): Flags large or scanner-relevant fixtures without a nearby purpose comment.
49
+ - `docs.magic-threshold-without-rationale` (advisory; medium confidence): Flags threshold-like numeric values without a nearby rationale comment.
50
+ - `docs.missing-error-behavior-doc` (advisory; medium confidence): Flags commented functions whose error behavior is not described.
51
+ - `docs.missing-file-overview` (advisory; medium confidence): Flags source files without a top-of-file purpose comment.
52
+ - `docs.missing-function-doc` (advisory; medium confidence): Flags functions without a leading maintainer comment.
53
+ - `docs.missing-interface-doc` (advisory; medium confidence): Flags interfaces without a leading maintainer comment.
54
+ - `docs.missing-invariant-doc` (advisory; medium confidence): Flags commented declarations that own schema, fingerprint, baseline, or determinism contracts without saying so.
55
+ - `docs.missing-param-tag` (advisory; medium confidence): Flags documented exports with parameters missing @param tags.
56
+ - `docs.missing-public-doc` (advisory; medium confidence): Flags exported class, type, and enum APIs without a nearby doc comment.
57
+ - `docs.missing-return-tag` (advisory; medium confidence): Flags documented non-void exports without @returns.
58
+ - `docs.missing-side-effect-doc` (advisory; medium confidence): Flags commented functions that perform observable side effects without naming them.
59
+ - `docs.missing-why-for-complex-code` (advisory; medium confidence): Flags comments on complex functions that do not explain why the shape exists.
60
+ - `docs.stale-comment` (advisory; medium confidence): Flags comments that reference missing files, unknown rules, stale CLI flags, or the wrong declaration.
61
+ - `docs.stale-param-tag` (advisory; medium confidence): Flags @param tags for parameters no longer in the signature.
62
+ - `docs.suppression-without-rationale` (advisory; medium confidence): Flags lint, formatter, coverage, or tool suppressions without a maintainer rationale.
63
+ - `docs.todo-without-tracking` (advisory; high confidence): Flags TODO, FIXME, HACK, and XXX comments without tracking context.
64
+ - `docs.useless-docblock` (advisory; medium confidence): Flags comments or docblocks that only restate the symbol name.
65
+
66
+ ## Modernisation
67
+
68
+ - `modernisation.date-now-candidate` (advisory; high confidence): Flags verbose current-time expressions that can use Date.now().
69
+ - `modernisation.double-cast` (warning; medium confidence): Flags casts through unknown or any into another type.
70
+ - `modernisation.loose-equality` (advisory; medium confidence): Flags loose equality comparisons that may coerce values.
71
+ - `modernisation.non-null-assertion` (warning; medium confidence): Flags non-null assertions that bypass null checks.
72
+ - `modernisation.nullish-coalescing-candidate` (advisory; medium confidence): Flags || fallbacks that may erase valid falsy values.
73
+ - `modernisation.object-spread-candidate` (advisory; medium confidence): Flags Object.assign({}, ...) cloning that can use object spread.
74
+ - `modernisation.optional-chaining-candidate` (advisory; medium confidence): Flags repeated guard-and-property access patterns.
75
+ - `modernisation.public-property` (advisory; high confidence): Flags public class properties that expose representation.
76
+ - `modernisation.readonly-property-candidate` (advisory; medium confidence): Flags class properties that appear readonly-worthy.
77
+ - `modernisation.ts-comment-without-rationale` (warning; medium confidence): Flags TypeScript suppression comments without a rationale.
78
+ - `modernisation.tsconfig-exact-optional-disabled` (warning; high confidence): Flags tsconfig files without exactOptionalPropertyTypes enabled.
79
+ - `modernisation.tsconfig-index-safety-disabled` (warning; high confidence): Flags tsconfig files without noUncheckedIndexedAccess enabled.
80
+ - `modernisation.tsconfig-strict-disabled` (warning; high confidence): Flags tsconfig files without strict mode enabled.
81
+ - `modernisation.var-declaration` (advisory; high confidence): Flags var declarations.
82
+
83
+ ## Naming
84
+
85
+ - `naming.acronym-case` (advisory; medium confidence): Flags mixed casings of a known acronym in one file.
86
+ - `naming.boolean-prefix` (advisory; medium confidence): Flags boolean names without intent-revealing prefixes on declarations, function parameters (typed `: boolean` or with `= true|false` default), and interface/type-literal fields.
87
+ - `naming.class-file-mismatch` (advisory; medium confidence): Flags exported classes whose name differs from the file name.
88
+ - `naming.generic-function` (advisory; high confidence): Flags generic function names that hide intent.
89
+ - `naming.generic-parameter` (advisory; medium confidence; options: minCyclomatic, minLineCount, minParameters): Flags placeholder parameter names in multi-parameter, long, exported, or complex functions.
90
+ - `naming.hungarian-notation` (advisory; medium confidence): Flags identifiers named after storage type prefixes.
91
+ - `naming.identifier-quality` (advisory; medium confidence): Flags placeholder or numbered identifiers on declarations, function parameters, and destructured locals.
92
+ - `naming.inconsistent-casing` (advisory; medium confidence): Flags the same canonical identifier appearing in two different surface forms (for example CONSTANT_CASE and camelCase) in one file.
93
+ - `naming.negative-boolean` (advisory; medium confidence): Flags boolean identifiers framed as a negation on declarations, parameters, and interface fields.
94
+ - `naming.short-variable` (advisory; medium confidence): Flags very short variable names outside common loop counters; covers declarations, function parameters, and destructured locals.
95
+
96
+ ## Security
97
+
98
+ - `security.async-foreach` (warning; medium confidence): Flags async callbacks passed to forEach.
99
+ - `security.disabled-tls-verification` (error; high confidence): Flags code that disables TLS certificate verification.
100
+ - `security.document-write` (warning; high confidence): Flags document.write usage.
101
+ - `security.dynamic-regexp` (warning; medium confidence): Flags external input used to construct regular expressions.
102
+ - `security.eval-call` (error; high confidence): Flags eval() dynamic code execution.
103
+ - `security.floating-promise` (warning; medium confidence): Flags promise-like calls without await, return, or void.
104
+ - `security.github-actions-broad-permissions` (warning; medium confidence): Flags GitHub Actions workflows that grant broad write permissions.
105
+ - `security.github-actions-pull-request-target` (warning; medium confidence): Flags pull_request_target workflows paired with risky execution or trust context.
106
+ - `security.github-actions-remote-shell` (warning; medium confidence): Flags workflow run steps that pipe remote downloads to a shell.
107
+ - `security.github-actions-secrets-in-pr` (warning; medium confidence): Flags pull request workflows that reference GitHub secrets.
108
+ - `security.github-actions-unpinned-action` (warning; medium confidence): Flags third-party GitHub Actions that are not pinned to a full commit SHA.
109
+ - `security.inner-html` (warning; high confidence): Flags innerHTML assignment.
110
+ - `security.insecure-random` (warning; high confidence): Flags Math.random usage in source.
111
+ - `security.javascript-url` (error; high confidence): Flags javascript: URL literals that execute script.
112
+ - `security.new-function` (error; high confidence): Flags Function constructor dynamic code execution.
113
+ - `security.open-redirect-candidate` (warning; medium confidence): Flags external input sent to redirect or navigation sinks.
114
+ - `security.path-traversal-candidate` (warning; medium confidence): Flags external input sent to filesystem path sinks.
115
+ - `security.process-exec` (warning; high confidence): Flags child-process execution calls.
116
+ - `security.proto-access` (warning; medium confidence): Flags direct __proto__ access that can enable prototype pollution.
117
+ - `security.remote-install-script` (error; medium confidence): Flags package scripts that pipe remote content to a shell.
118
+ - `security.risky-lifecycle-script` (warning; medium confidence): Flags package lifecycle scripts that run automatically.
119
+ - `security.sql-concatenation` (warning; high confidence): Flags SQL text composed with runtime string interpolation.
120
+ - `security.ssrf-candidate` (warning; medium confidence): Flags external input sent to network request sinks.
121
+ - `security.string-timer` (warning; high confidence): Flags string callbacks passed to timers.
122
+ - `security.throw-non-error` (warning; medium confidence): Flags thrown non-Error values.
123
+ - `security.url-dependency` (warning; medium confidence): Flags dependencies installed from URL or git specs.
124
+ - `security.weak-crypto` (warning; high confidence): Flags weak crypto primitives such as md5, sha1, or createCipher.
125
+
126
+ ## Sensitive Data
127
+
128
+ - `sensitive-data.api-key-pattern` (error; high confidence): Flags vendor API key patterns.
129
+ - `sensitive-data.aws-access-key` (error; high confidence): Flags AWS access key looking values.
130
+ - `sensitive-data.database-url-password` (error; high confidence): Flags database URLs that include passwords.
131
+ - `sensitive-data.hardcoded-env-value` (error; medium confidence; threshold 16): Flags environment-style secret values committed in text.
132
+ - `sensitive-data.high-entropy-string` (error; medium confidence; threshold 32): Flags high-entropy string literals that may be secrets.
133
+ - `sensitive-data.jwt-token` (error; high confidence): Flags JWT-looking token literals.
134
+ - `sensitive-data.pii-pattern` (error; high confidence): Flags PII-like identifier patterns.
135
+ - `sensitive-data.private-key` (error; high confidence): Flags private key block markers.
136
+
137
+ ## Size
138
+
139
+ - `size.file-length` (warning; high confidence; threshold 750): Flags files longer than the configured threshold.
140
+ - `size.function-length` (warning; high confidence; threshold 200): Flags functions longer than the configured threshold.
141
+ - `size.parameter-count` (warning; high confidence; threshold 7): Flags functions with too many parameters.
142
+ - `size.stylesheet-length` (warning; high confidence; threshold 1500): Flags stylesheets longer than the configured threshold.
143
+
144
+ ## Test Quality
145
+
146
+ - `test-quality.conditional-logic` (advisory; high confidence): Flags tests with conditional logic.
147
+ - `test-quality.exception-type-only` (advisory; high confidence): Flags tests that only assert exception type.
148
+ - `test-quality.global-state-mutation` (warning; high confidence): Flags tests mutating process or global runtime state.
149
+ - `test-quality.loop-in-test` (advisory; high confidence): Flags loops inside test bodies.
150
+ - `test-quality.magic-number-assertion` (advisory; medium confidence): Flags assertions against unexplained numeric literals.
151
+ - `test-quality.missing-nearby-test` (advisory; medium confidence): Flags exported production files without nearby tests.
152
+ - `test-quality.mock-only-test` (advisory; high confidence): Flags tests that only verify mock interaction.
153
+ - `test-quality.no-assertions` (warning; high confidence): Flags tests without apparent assertions.
154
+ - `test-quality.no-throw-only-test` (advisory; high confidence): Flags tests that only assert code does not throw.
155
+ - `test-quality.only-skip` (advisory; high confidence): Flags focused or skipped test markers.
156
+ - `test-quality.setup-bloat` (advisory; medium confidence; threshold 12): Flags tests with too much setup before the first assertion.
157
+ - `test-quality.sleep-in-test` (advisory; high confidence): Flags sleeps in tests.
158
+ - `test-quality.snapshot-only-test` (advisory; high confidence): Flags tests that rely only on snapshots.
159
+ - `test-quality.trivial-assertion` (warning; high confidence): Flags tautological assertions.
160
+ - `test-quality.unused-mock` (advisory; medium confidence): Flags mocks created but not used.
161
+
162
+ ## Maintainability
163
+
164
+ - `waste.any-type` (warning; high confidence): Flags any type usage.
165
+ - `waste.broad-runtime-version` (advisory; medium confidence): Flags broad runtime dependency version ranges.
166
+ - `waste.commented-out-code` (advisory; high confidence): Flags comments that appear to contain disabled code.
167
+ - `waste.console-log` (advisory; high confidence): Flags console log/debug calls in source.
168
+ - `waste.empty-function` (advisory; high confidence): Flags functions with no executable body.
169
+ - `waste.exported-any` (warning; medium confidence): Flags exported APIs exposing any.
170
+ - `waste.redundant-boolean-cast` (advisory; medium confidence): Flags redundant boolean casts in condition expressions.
171
+ - `waste.redundant-variable` (advisory; medium confidence): Flags variables returned immediately after assignment.
172
+ - `waste.swallowed-catch` (warning; medium confidence): Flags empty catch blocks.
173
+ - `waste.unreachable-code` (warning; high confidence): Flags statements after terminating statements.
174
+ - `waste.unused-import` (advisory; medium confidence): Flags named imports with no apparent usage.
175
+ - `waste.unused-parameter` (advisory; medium confidence): Flags parameters with no apparent usage.
176
+ - `waste.useless-catch` (advisory; high confidence): Flags catch blocks that only rethrow the caught value.
177
+ - `waste.useless-return` (advisory; medium confidence): Flags terminal bare return statements in void functions.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blundergoat/gruff-ts",
3
- "version": "0.1.0",
4
- "description": "Static analyzer for TypeScript and JavaScript projects - 121 rules across 11 quality pillars, SARIF output, baselines, and a local dashboard.",
3
+ "version": "0.1.1",
4
+ "description": "Static analyzer for TypeScript and JavaScript projects - 119 rules across 11 quality pillars, SARIF output, baselines, and a local dashboard.",
5
5
  "license": "MIT",
6
6
  "author": "Matthew Hansen (https://www.blundergoat.com/about)",
7
7
  "homepage": "https://github.com/blundergoat/gruff-ts#readme",
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
- # Bump gruff-ts to a new semver in package.json and src/constants.ts in one step.
5
- # The CLI surfaces VERSION from src/constants.ts; package.json drives `npm publish`.
6
- # Keeping them in lockstep is a release invariant.
4
+ # Bump gruff-ts to a new semver in package.json, package-lock.json, and src/constants.ts.
5
+ # The CLI surfaces VERSION from src/constants.ts; package metadata drives `npm publish`.
6
+ # Keeping all release version surfaces in lockstep is a release invariant.
7
7
 
8
8
  usage() {
9
9
  cat <<'USAGE'
@@ -15,7 +15,7 @@ Arguments:
15
15
  <new-version> Target semver, e.g. 0.1.1, 0.2.0, 1.0.0-rc.1.
16
16
 
17
17
  Options:
18
- --check Verify package.json and src/constants.ts already agree.
18
+ --check Verify package.json, package-lock.json, and src/constants.ts already agree.
19
19
  --help, -h Show this help.
20
20
 
21
21
  Notes:
@@ -42,6 +42,14 @@ read_constants_version() {
42
42
  awk -F'"' '/^const VERSION = "/ { print $2; exit }' src/constants.ts
43
43
  }
44
44
 
45
+ read_package_lock_version() {
46
+ node -e 'const fs = require("node:fs"); const data = JSON.parse(fs.readFileSync("package-lock.json", "utf8")); process.stdout.write(String(data.version ?? ""));'
47
+ }
48
+
49
+ read_package_lock_package_version() {
50
+ node -e 'const fs = require("node:fs"); const data = JSON.parse(fs.readFileSync("package-lock.json", "utf8")); process.stdout.write(String(data.packages?.[""]?.version ?? ""));'
51
+ }
52
+
45
53
  write_package_version() {
46
54
  local next_version="$1"
47
55
  awk -v target="$next_version" '
@@ -69,13 +77,50 @@ write_constants_version() {
69
77
  mv src/constants.ts.tmp src/constants.ts
70
78
  }
71
79
 
80
+ write_package_lock_version() {
81
+ local next_version="$1"
82
+ node - "$next_version" <<'NODE'
83
+ const fs = require("node:fs");
84
+
85
+ const target = process.argv[2];
86
+ const lockPath = "package-lock.json";
87
+ const lock = JSON.parse(fs.readFileSync(lockPath, "utf8"));
88
+
89
+ lock.version = target;
90
+ if (!lock.packages || typeof lock.packages !== "object" || !lock.packages[""]) {
91
+ throw new Error('package-lock.json missing packages[""] root package entry');
92
+ }
93
+ lock.packages[""].version = target;
94
+
95
+ fs.writeFileSync(`${lockPath}.tmp`, `${JSON.stringify(lock, null, 2)}\n`);
96
+ NODE
97
+ mv package-lock.json.tmp package-lock.json
98
+ }
99
+
72
100
  validate_semver() {
73
101
  local value="$1"
74
102
  # Standard semver: MAJOR.MINOR.PATCH with optional prerelease/build metadata.
75
- local pattern='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'
103
+ local pattern='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*))*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$'
76
104
  [[ "$value" =~ $pattern ]] || die "not a valid semver: $value"
77
105
  }
78
106
 
107
+ check_version_lockstep() {
108
+ local pkg const_ lock lock_package
109
+ pkg="$(read_package_version)" || die "failed to read package.json version"
110
+ const_="$(read_constants_version)" || die "failed to read src/constants.ts VERSION"
111
+ lock="$(read_package_lock_version)" || die "failed to read package-lock.json root version"
112
+ lock_package="$(read_package_lock_package_version)" || die "failed to read package-lock.json package version"
113
+ [[ -n "$pkg" ]] || die "package.json has no \"version\" field"
114
+ [[ -n "$const_" ]] || die "src/constants.ts has no VERSION constant"
115
+ [[ -n "$lock" ]] || die "package-lock.json has no root \"version\" field"
116
+ [[ -n "$lock_package" ]] || die 'package-lock.json has no packages[""].version field'
117
+ if ! [[ "$pkg" == "$const_" && "$pkg" == "$lock" && "$pkg" == "$lock_package" ]]; then
118
+ printf 'version surfaces disagree: package.json=%s src/constants.ts=%s package-lock.json=%s package-lock.json packages[""]=%s\n' "$pkg" "$const_" "$lock" "$lock_package" >&2
119
+ exit 1
120
+ fi
121
+ printf 'package.json, package-lock.json, and src/constants.ts agree on %s\n' "$pkg"
122
+ }
123
+
79
124
  main() {
80
125
  if [[ "$#" -eq 0 ]]; then
81
126
  usage >&2
@@ -84,6 +129,7 @@ main() {
84
129
 
85
130
  cd "$(repo_root)"
86
131
  [[ -f package.json ]] || die "package.json not found"
132
+ [[ -f package-lock.json ]] || die "package-lock.json not found"
87
133
  [[ -f src/constants.ts ]] || die "src/constants.ts not found"
88
134
 
89
135
  case "$1" in
@@ -92,16 +138,7 @@ main() {
92
138
  exit 0
93
139
  ;;
94
140
  --check)
95
- local pkg const_
96
- pkg="$(read_package_version)" || die "failed to read package.json version"
97
- const_="$(read_constants_version)" || die "failed to read src/constants.ts VERSION"
98
- [[ -n "$pkg" ]] || die "package.json has no \"version\" field"
99
- [[ -n "$const_" ]] || die "src/constants.ts has no VERSION constant"
100
- if [[ "$pkg" != "$const_" ]]; then
101
- printf 'package.json (%s) and src/constants.ts (%s) disagree\n' "$pkg" "$const_" >&2
102
- exit 1
103
- fi
104
- printf 'package.json and src/constants.ts agree on %s\n' "$pkg"
141
+ check_version_lockstep
105
142
  exit 0
106
143
  ;;
107
144
  esac
@@ -117,8 +154,14 @@ main() {
117
154
  current_const="$(read_constants_version)" || die "failed to read src/constants.ts VERSION"
118
155
  [[ -n "$current_const" ]] || die "src/constants.ts has no VERSION constant"
119
156
 
120
- if [[ "$current" != "$current_const" ]]; then
121
- die "current versions diverge: package.json=$current src/constants.ts=$current_const (resolve manually first)"
157
+ local current_lock current_lock_package
158
+ current_lock="$(read_package_lock_version)" || die "failed to read package-lock.json root version"
159
+ current_lock_package="$(read_package_lock_package_version)" || die "failed to read package-lock.json package version"
160
+ [[ -n "$current_lock" ]] || die "package-lock.json has no root \"version\" field"
161
+ [[ -n "$current_lock_package" ]] || die 'package-lock.json has no packages[""].version field'
162
+
163
+ if ! [[ "$current" == "$current_const" && "$current" == "$current_lock" && "$current" == "$current_lock_package" ]]; then
164
+ die "current versions diverge: package.json=$current src/constants.ts=$current_const package-lock.json=$current_lock package-lock.json packages[\"\"]=$current_lock_package (resolve manually first)"
122
165
  fi
123
166
 
124
167
  if [[ "$current" == "$next" ]]; then
@@ -128,15 +171,21 @@ main() {
128
171
 
129
172
  write_package_version "$next"
130
173
  write_constants_version "$next"
174
+ write_package_lock_version "$next"
131
175
 
132
- local check_pkg check_const
176
+ local check_pkg check_const check_lock check_lock_package
133
177
  check_pkg="$(read_package_version)"
134
178
  check_const="$(read_constants_version)"
179
+ check_lock="$(read_package_lock_version)"
180
+ check_lock_package="$(read_package_lock_package_version)"
135
181
  [[ "$check_pkg" == "$next" ]] || die "package.json did not update cleanly (read back: ${check_pkg:-empty})"
136
182
  [[ "$check_const" == "$next" ]] || die "src/constants.ts did not update cleanly (read back: ${check_const:-empty})"
183
+ [[ "$check_lock" == "$next" ]] || die "package-lock.json did not update cleanly (read back: ${check_lock:-empty})"
184
+ [[ "$check_lock_package" == "$next" ]] || die "package-lock.json packages[\"\"] did not update cleanly (read back: ${check_lock_package:-empty})"
137
185
 
138
186
  printf 'bumped %s -> %s\n' "$current" "$next"
139
187
  printf ' package.json\n'
188
+ printf ' package-lock.json\n'
140
189
  printf ' src/constants.ts\n'
141
190
  # shellcheck disable=SC2016
142
191
  printf 'next: update CHANGELOG.md and run `npm run check`\n'
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ NPM_AUDIT_LEVEL="${NPM_AUDIT_LEVEL:-moderate}"
5
+ RUN_AUDIT=1
6
+ RUN_CHECK=0
7
+
8
+ usage() {
9
+ cat <<'USAGE'
10
+ Usage:
11
+ scripts/dependency-install.sh [options]
12
+
13
+ Installs dependencies from package-lock.json using npm ci.
14
+
15
+ Options:
16
+ --audit-level LEVEL npm audit threshold (default: moderate)
17
+ --no-audit Skip npm audit after install
18
+ --check Run npm run check after install and audit
19
+ --help, -h Show this help
20
+
21
+ Environment:
22
+ NPM_AUDIT_LEVEL Default audit threshold when --audit-level is omitted
23
+ USAGE
24
+ }
25
+
26
+ die() {
27
+ printf 'dependency-install: %s\n' "$*" >&2
28
+ exit 1
29
+ }
30
+
31
+ repo_root() {
32
+ local script_dir
33
+ script_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
34
+ CDPATH='' cd -- "$script_dir/.." && pwd
35
+ }
36
+
37
+ parse_args() {
38
+ while [[ "$#" -gt 0 ]]; do
39
+ case "$1" in
40
+ --audit-level)
41
+ [[ "$#" -ge 2 ]] || die "--audit-level requires a value"
42
+ NPM_AUDIT_LEVEL="$2"
43
+ shift
44
+ ;;
45
+ --audit-level=*)
46
+ NPM_AUDIT_LEVEL="${1#*=}"
47
+ ;;
48
+ --no-audit)
49
+ RUN_AUDIT=0
50
+ ;;
51
+ --check)
52
+ RUN_CHECK=1
53
+ ;;
54
+ --help|-h)
55
+ usage
56
+ exit 0
57
+ ;;
58
+ *)
59
+ usage >&2
60
+ die "unknown option: $1"
61
+ ;;
62
+ esac
63
+ shift
64
+ done
65
+ }
66
+
67
+ require_tool() {
68
+ command -v "$1" >/dev/null 2>&1 || die "$1 is required"
69
+ }
70
+
71
+ main() {
72
+ parse_args "$@"
73
+ cd "$(repo_root)"
74
+
75
+ require_tool node
76
+ require_tool npm
77
+ [[ -f package.json ]] || die "package.json not found"
78
+ [[ -f package-lock.json ]] || die "package-lock.json not found"
79
+
80
+ echo "--- Install dependencies ---"
81
+ npm ci
82
+
83
+ if [[ "$RUN_AUDIT" -eq 1 ]]; then
84
+ echo ""
85
+ echo "--- Dependency audit ---"
86
+ npm audit --audit-level="$NPM_AUDIT_LEVEL"
87
+ fi
88
+
89
+ if [[ "$RUN_CHECK" -eq 1 ]]; then
90
+ echo ""
91
+ echo "--- TypeScript + tests ---"
92
+ npm run check
93
+ fi
94
+ }
95
+
96
+ main "$@"