utilium 2.5.2 → 2.5.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "utilium",
3
- "version": "2.5.2",
3
+ "version": "2.5.4",
4
4
  "description": "Typescript utilities",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -41,7 +41,7 @@
41
41
  "homepage": "https://github.com/james-pre/utilium#readme",
42
42
  "devDependencies": {
43
43
  "@eslint/js": "^9.12.0",
44
- "@types/node": "^20.12.7",
44
+ "@types/node": "^22.18.0",
45
45
  "eslint": "^9.12.0",
46
46
  "globals": "^15.10.0",
47
47
  "prettier": "^3.2.5",
@@ -55,5 +55,8 @@
55
55
  },
56
56
  "optionalDependencies": {
57
57
  "@xterm/xterm": "^5.5.0"
58
+ },
59
+ "engines": {
60
+ "node": ">=22.0.0"
58
61
  }
59
62
  }
package/scripts/lice.js CHANGED
@@ -2,37 +2,78 @@
2
2
  // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
  // Copyright (c) 2025 James Prevett
4
4
 
5
- import { readdirSync } from 'node:fs';
5
+ import { existsSync, globSync, readdirSync, readFileSync, statSync } from 'node:fs';
6
6
  import { readFile, writeFile } from 'node:fs/promises';
7
- import { join, matchesGlob, relative } from 'node:path';
7
+ import { dirname, join, matchesGlob, relative } from 'node:path';
8
8
  import { parseArgs, styleText } from 'node:util';
9
9
 
10
- const {
11
- positionals: dirs,
12
- values: { license: expectedLicense, write, verbose, force, exclude },
13
- } = parseArgs({
10
+ const { positionals: inputs, values: opts } = parseArgs({
14
11
  allowPositionals: true,
15
12
  options: {
16
- license: { type: 'string', short: 'L' },
17
- write: { type: 'boolean', short: 'w', default: false },
18
- verbose: { type: 'boolean', short: 'v', default: false },
13
+ auto: { type: 'boolean', short: 'a', default: false },
14
+ exclude: { type: 'string', short: 'x', default: [], multiple: true },
19
15
  force: { type: 'boolean', short: 'f', default: false },
20
- exclude: { type: 'string', short: 'x', default: '' },
16
+ help: { type: 'boolean', short: 'h', default: false },
17
+ license: { type: 'string', short: 'l' },
18
+ verbose: { type: 'boolean', short: 'v', default: false },
19
+ write: { type: 'boolean', short: 'w', default: false },
21
20
  },
22
21
  });
23
22
 
24
- if (write && !expectedLicense) {
25
- console.error(styleText('red', 'You must specify a license to write with --license/-L'));
23
+ if (opts.help) {
24
+ console.error(`Usage: lice [options] <inputs...>
25
+
26
+ Options:
27
+ -f, --force Force overwrite of existing license headers
28
+ -h, --help Show this help message
29
+ -a, --auto Detect the SPDX identifier in package.json
30
+ -l, --license Specify the SPDX license identifier to check for.
31
+ -v, --verbose Enable verbose output
32
+ -w, --write Write the license header if missing
33
+ -x, --exclude Glob pattern to exclude files
34
+ `);
35
+ process.exit(0);
36
+ }
37
+
38
+ /**
39
+ *
40
+ * @returns {string|null}
41
+ */
42
+ function get_license() {
43
+ for (let dir = process.cwd(); dir != '/'; dir = dirname(dir)) {
44
+ const pkgPath = join(dir, 'package.json');
45
+ if (!existsSync(pkgPath)) continue;
46
+
47
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
48
+ if (pkg.spdx) return pkg.spdx;
49
+ if (pkg.spdxLicense) return pkg.spdxLicense;
50
+ if (pkg.license) return pkg.license;
51
+ }
52
+
53
+ return null;
54
+ }
55
+
56
+ const expectedLicense = opts.license || (opts.auto && get_license());
57
+
58
+ if (opts.write && !expectedLicense) {
59
+ console.error(styleText('red', 'You must specify a license to write.'));
26
60
  process.exit(1);
27
61
  }
28
62
 
63
+ function should_exclude(path, display) {
64
+ for (const pattern of opts.exclude) {
65
+ if (!matchesGlob(path, pattern)) continue;
66
+ console.log(styleText('whiteBright', 'Skipped:'), display);
67
+ return true;
68
+ }
69
+
70
+ return false;
71
+ }
72
+
29
73
  const licenseSpec = /^\s*\/(?:\/|\*) SPDX-License-Identifier: (.+)/;
30
74
 
31
75
  async function check_file(path, display) {
32
- if (matchesGlob(path, exclude)) {
33
- console.log(styleText('whiteBright', 'Skipped:'), display);
34
- return 'skipped';
35
- }
76
+ if (should_exclude(path, display)) return 'skipped';
36
77
 
37
78
  const content = await readFile(path, 'utf-8');
38
79
 
@@ -44,14 +85,14 @@ async function check_file(path, display) {
44
85
  }
45
86
 
46
87
  if (!expectedLicense) {
47
- if (verbose) console.log(styleText(['dim'], 'Found:'), display);
88
+ if (opts.verbose) console.log(styleText(['dim'], 'Found:'), display);
48
89
  return 'with license';
49
90
  }
50
91
 
51
92
  const [, license] = match;
52
93
 
53
94
  if (license == expectedLicense) {
54
- if (verbose) console.log(styleText(['green', 'dim'], 'Correct:'), display);
95
+ if (opts.verbose) console.log(styleText(['green', 'dim'], 'Correct:'), display);
55
96
  return 'correct';
56
97
  }
57
98
 
@@ -60,10 +101,7 @@ async function check_file(path, display) {
60
101
  }
61
102
 
62
103
  async function write_file(path, display) {
63
- if (matchesGlob(path, exclude)) {
64
- console.log(styleText('whiteBright', 'Skipped:'), display);
65
- return 'skipped';
66
- }
104
+ if (should_exclude(path, display)) return 'skipped';
67
105
 
68
106
  const content = await readFile(path, 'utf-8');
69
107
 
@@ -79,13 +117,13 @@ async function write_file(path, display) {
79
117
  const [, license] = match;
80
118
 
81
119
  if (license == expectedLicense) {
82
- if (verbose) console.log(styleText(['green', 'dim'], 'Correct:'), display);
120
+ if (opts.verbose) console.log(styleText(['green', 'dim'], 'Correct:'), display);
83
121
  return 'correct';
84
122
  }
85
123
 
86
124
  process.stdout.write(styleText('yellow', 'Mismatch: ') + display);
87
125
 
88
- if (!force) {
126
+ if (!opts.force) {
89
127
  console.log(styleText('whiteBright', ' (skipped)'));
90
128
  return 'skipped';
91
129
  }
@@ -95,38 +133,50 @@ async function write_file(path, display) {
95
133
  return 'overwritten';
96
134
  }
97
135
 
98
- function check_dir(dir, display) {
99
- const entries = readdirSync(dir, { withFileTypes: true });
136
+ function* check_dir(dir, display) {
137
+ if (should_exclude(dir, display)) return 'skipped';
100
138
 
101
- const results = [];
139
+ const entries = readdirSync(dir, { withFileTypes: true });
102
140
 
103
141
  for (const entry of entries) {
104
142
  if (entry.isDirectory()) {
105
- results.push(...check_dir(join(dir, entry.name), join(display, entry.name)));
143
+ yield* check_dir(join(dir, entry.name), join(display, entry.name));
106
144
  continue;
107
145
  }
108
146
 
109
147
  if (!entry.isFile()) continue;
110
148
 
111
- const op = write ? write_file : check_file;
112
- results.push(op(join(dir, entry.name), join(display, entry.name)));
149
+ const op = opts.write ? write_file : check_file;
150
+ yield op(join(dir, entry.name), join(display, entry.name));
113
151
  }
114
-
115
- return results;
116
152
  }
117
153
 
118
- if (!dirs.length) {
119
- console.error(styleText('red', 'No directories specified'));
154
+ if (!inputs.length) {
155
+ console.error(styleText('red', 'No inputs specified'));
120
156
  process.exit(1);
121
157
  }
122
158
 
123
- if (verbose) console.log('Checking:', dirs.join(', '));
159
+ const globbed = globSync(inputs);
160
+
161
+ if (opts.verbose) console.log('Checking:', globbed.join(', '));
124
162
 
125
163
  const promises = [];
126
164
 
127
- for (const dir of dirs) {
128
- const rel = relative(process.cwd(), dir);
129
- promises.push(...check_dir(dir, rel.startsWith('..') ? dir : rel));
165
+ for (const input of globbed) {
166
+ const rel = relative(process.cwd(), input);
167
+ if (should_exclude(input, rel)) {
168
+ promises.push(Promise.resolve('skipped'));
169
+ continue;
170
+ }
171
+
172
+ const stat = statSync(input);
173
+
174
+ if (stat.isDirectory()) {
175
+ for (const result of check_dir(input, rel)) promises.push(result);
176
+ } else {
177
+ const op = opts.write ? write_file : check_file;
178
+ promises.push(op(input, rel));
179
+ }
130
180
  }
131
181
 
132
182
  const styles = Object.assign(Object.create(null), {
@@ -147,7 +197,7 @@ try {
147
197
  results[result]++;
148
198
  }
149
199
  console.log(
150
- write ? 'Wrote' : 'Checked',
200
+ opts.write ? 'Wrote' : 'Checked',
151
201
  styleText('blueBright', raw_results.length.toString()),
152
202
  'files:',
153
203
  Object.entries(results)
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "checkJs": true,
4
+ "noEmit": true,
5
+ "lib": ["ESNext"],
6
+ "module": "NodeNext",
7
+ "moduleResolution": "NodeNext"
8
+ },
9
+ "include": ["*.js"]
10
+ }