@vodmal/vdx-cli 0.25.0 → 0.27.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/src/publish.ts CHANGED
@@ -3,6 +3,7 @@ import * as path from 'node:path';
3
3
  import { execFileSync } from 'node:child_process';
4
4
  import type { AuditResult } from './audit.ts';
5
5
  import { stackForDir, type Ctx } from './facts.ts';
6
+ import { at, dict, errorMessage, stderrOf, str } from './narrow.ts';
6
7
 
7
8
  export type BumpKind = 'patch' | 'minor' | 'major';
8
9
 
@@ -115,7 +116,7 @@ const runGit: GitRunner = (args, cwd) =>
115
116
  * since the last fetch; nothing is fetched.
116
117
  */
117
118
  export function planPush(projectRoot: string, enabled: boolean, git: GitRunner = runGit): PushPlan {
118
- let upstream: string | null = null;
119
+ let upstream: string | null;
119
120
  try {
120
121
  upstream = git(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], projectRoot).trim() || null;
121
122
  } catch {
@@ -144,10 +145,10 @@ function fetchPublishedVersion(packageName: string, npm: NpmRunner): string | nu
144
145
  try {
145
146
  const out = npm(['view', packageName, 'version']);
146
147
  return out.trim() || null;
147
- } catch (e: any) {
148
- const stderr = String(e?.stderr ?? e?.message ?? '');
148
+ } catch (e) {
149
+ const stderr = stderrOf(e);
149
150
  if (stderr.includes('E404') || stderr.includes('404 Not Found')) return null;
150
- throw new Error(`npm view failed: ${stderr.split('\n')[0] ?? stderr}`);
151
+ throw new Error(`npm view failed: ${stderr.split('\n')[0] ?? stderr}`, { cause: e });
151
152
  }
152
153
  }
153
154
 
@@ -155,12 +156,10 @@ function fetchPublishedVersion(packageName: string, npm: NpmRunner): string | nu
155
156
  * The registry `npm publish` sends this package to, when it is not npm's
156
157
  * default: `publishConfig.registry`, else the scope's registry from npm config.
157
158
  */
158
- export function publishRegistry(
159
- pkg: { name?: string; publishConfig?: { registry?: string } },
160
- npm: NpmRunner,
161
- ): string | null {
162
- if (pkg.publishConfig?.registry) return pkg.publishConfig.registry;
163
- const scope = pkg.name?.match(/^(@[^/]+)\//)?.[1];
159
+ export function publishRegistry(pkg: unknown, npm: NpmRunner): string | null {
160
+ const registry = str(at(pkg, 'publishConfig', 'registry'));
161
+ if (registry) return registry;
162
+ const scope = str(at(pkg, 'name'))?.match(/^(@[^/]+)\//)?.[1];
164
163
  if (!scope) return null;
165
164
  try {
166
165
  const value = npm(['config', 'get', `${scope}:registry`]).trim();
@@ -184,8 +183,8 @@ export function checkNpmAuth(registry: string | null, npm: NpmRunner): Preflight
184
183
  const who = npm(registry ? ['whoami', '--registry', registry] : ['whoami']).trim();
185
184
  if (who) return { name: 'npm-auth', ok: true, message: `OK (logged in to ${where} as ${who})` };
186
185
  return { name: 'npm-auth', ok: false, message: `npm whoami answered nothing for ${where}. Run: ${login}`, fix };
187
- } catch (e: any) {
188
- const stderr = String(e?.stderr ?? e?.message ?? '');
186
+ } catch (e) {
187
+ const stderr = stderrOf(e);
189
188
  if (/E401|ENEEDAUTH/.test(stderr)) {
190
189
  return {
191
190
  name: 'npm-auth',
@@ -268,9 +267,9 @@ export function planPublish(
268
267
  throw new Error(`vdx publish: package.json not found at ${pkgJsonPath}`);
269
268
  }
270
269
 
271
- const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
272
- const packageName: string = pkg.name ?? '(unnamed)';
273
- const currentVersion: string = pkg.version ?? '0.0.0';
270
+ const pkg: unknown = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
271
+ const packageName = str(at(pkg, 'name')) ?? '(unnamed)';
272
+ const currentVersion = str(at(pkg, 'version')) ?? '0.0.0';
274
273
  const newVersion = bumpSemver(currentVersion, opts.bump);
275
274
 
276
275
  const preflight: PreflightCheck[] = [];
@@ -312,8 +311,8 @@ export function planPublish(
312
311
  let registryError: string | null = null;
313
312
  try {
314
313
  published = fetchPublishedVersion(packageName, npm);
315
- } catch (e: any) {
316
- registryError = e?.message ?? String(e);
314
+ } catch (e) {
315
+ registryError = errorMessage(e);
317
316
  }
318
317
  if (registryError) {
319
318
  preflight.push({
@@ -384,7 +383,7 @@ export function preflightFixable(plan: PublishPlan): boolean {
384
383
  }
385
384
 
386
385
  /** Runs a command on this terminal (stdio inherited); throws on a non-zero exit. */
387
- export type CommandRunner = (cmd: string, args: string[], cwd: string) => void;
386
+ type CommandRunner = (cmd: string, args: string[], cwd: string) => void;
388
387
 
389
388
  export interface PublishDeps {
390
389
  run: CommandRunner;
@@ -416,9 +415,10 @@ export function defaultPublishDeps(): PublishDeps {
416
415
 
417
416
  /** Set `version` in package-lock.json — the root and its own entry under `packages`. */
418
417
  export function bumpLockText(text: string, version: string): string {
419
- const lock = JSON.parse(text);
420
- lock.version = version;
421
- if (lock.packages?.['']) lock.packages[''].version = version;
418
+ const lock = dict(JSON.parse(text)) ?? {};
419
+ lock['version'] = version;
420
+ const self = dict(at(lock, 'packages', ''));
421
+ if (self) self['version'] = version;
422
422
  return JSON.stringify(lock, null, 2) + '\n';
423
423
  }
424
424
 
@@ -502,8 +502,8 @@ export function executePublish(plan: PublishPlan, deps: PublishDeps = defaultPub
502
502
  fs.writeFileSync(file, next(text), 'utf8');
503
503
  };
504
504
  rewrite(plan.packageJsonPath, (text) => {
505
- const pkg = JSON.parse(text);
506
- pkg.version = plan.newVersion;
505
+ const pkg = dict(JSON.parse(text)) ?? {};
506
+ pkg['version'] = plan.newVersion;
507
507
  return JSON.stringify(pkg, null, 2) + '\n';
508
508
  });
509
509
  if (plan.lockPath) rewrite(plan.lockPath, (text) => bumpLockText(text, plan.newVersion));
@@ -521,11 +521,13 @@ export function executePublish(plan: PublishPlan, deps: PublishDeps = defaultPub
521
521
  for (const s of signals) process.on(s, holdOut);
522
522
  try {
523
523
  deps.run('npm', ['publish'], path.dirname(plan.packageJsonPath));
524
- } catch (e: any) {
524
+ } catch (e) {
525
525
  for (const [file, text] of originals) fs.writeFileSync(file, text, 'utf8');
526
- const what = e?.signal ? `interrupted (${e.signal})` : `failed (exit ${e?.status ?? '?'})`;
526
+ const signal = str(at(e, 'signal'));
527
+ const status = at(e, 'status');
528
+ const what = signal ? `interrupted (${signal})` : `failed (exit ${typeof status === 'number' ? status : '?'})`;
527
529
  deps.log(`✗ npm publish ${what}; reverted ${bumped.map((f) => path.basename(f)).join(' and ')}`);
528
- throw new Error(`npm publish ${e?.signal ? 'interrupted' : 'failed'}`);
530
+ throw new Error(`npm publish ${signal ? 'interrupted' : 'failed'}`, { cause: e });
529
531
  } finally {
530
532
  for (const s of signals) process.off(s, holdOut);
531
533
  }
package/src/rubric.ts CHANGED
@@ -4,20 +4,20 @@ import { COMBINATORS, predicateNames } from './evaluator.ts';
4
4
  import { REGISTRY, REMOVED_IN_0_3 } from './predicates.ts';
5
5
 
6
6
  export type LevelName = 'L0' | 'L1' | 'L2' | 'L3' | 'L4';
7
- export type AxisClass = 'critical' | 'supporting';
8
- export type Storage = 'level' | 'flags';
7
+ type AxisClass = 'critical' | 'supporting';
8
+ type Storage = 'level' | 'flags';
9
9
 
10
10
  // Predicate структура динамическая — оставляем any для дешёвого парсинга
11
11
  // со сахаром. Валидация — отдельная история v0.2.
12
12
  export type Predicate = unknown;
13
13
 
14
- export interface Flag {
14
+ interface Flag {
15
15
  id: string;
16
16
  weight: number;
17
17
  predicate: Predicate;
18
18
  }
19
19
 
20
- export interface StackImpl {
20
+ interface StackImpl {
21
21
  flags?: Flag[];
22
22
  level_thresholds?: Partial<Record<LevelName, number>>;
23
23
  }
@@ -124,8 +124,8 @@ export function parseRubric(text: string, source: string): Rubric {
124
124
  }
125
125
  // нормализуем default_target (если кто-то напишет "L4" со spaces)
126
126
  for (const ax of data.axes) {
127
- ax.default_target = (ax.default_target ?? 'L4') as LevelName;
128
- ax.storage = (ax.storage ?? 'level') as Storage;
127
+ ax.default_target = ax.default_target ?? 'L4';
128
+ ax.storage = ax.storage ?? 'level';
129
129
  }
130
130
  if (compareSchema(schema, '0.3')! >= 0) {
131
131
  const bad = invalidPredicates(data);
package/src/run.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import * as TOML from 'smol-toml';
4
+ import { errorMessage } from './narrow.ts';
4
5
 
5
6
  export const LIFECYCLE_VERBS = ['up', 'down', 'build', 'test', 'check', 'fix'] as const;
6
7
  export type LifecycleVerb = (typeof LIFECYCLE_VERBS)[number];
@@ -21,8 +22,8 @@ export function resolveLifecycleVerb(projectRoot: string, verb: string): VerbRes
21
22
  let parsed: unknown;
22
23
  try {
23
24
  parsed = TOML.parse(fs.readFileSync(miseTomlPath, 'utf8'));
24
- } catch (e: any) {
25
- return { ok: false, reason: 'parse-error', detail: e?.message ?? String(e) };
25
+ } catch (e) {
26
+ return { ok: false, reason: 'parse-error', detail: errorMessage(e) };
26
27
  }
27
28
  const tasks = (parsed as { tasks?: Record<string, unknown> })?.tasks;
28
29
  if (!tasks || typeof tasks !== 'object' || !(verb in tasks)) {
package/src/vocabulary.ts CHANGED
@@ -5,8 +5,9 @@
5
5
  * predicates (what a hook runs), so both read "the project's task" the same way.
6
6
  */
7
7
  import { listAllTasks, readJson, readToml, type Ctx } from './facts.ts';
8
+ import { at, dict, list } from './narrow.ts';
8
9
 
9
- export const LIFECYCLE_VOCABULARY = ['up', 'down', 'build', 'test', 'check', 'fix'] as const;
10
+ const LIFECYCLE_VOCABULARY = ['up', 'down', 'build', 'test', 'check', 'fix'] as const;
10
11
 
11
12
  /** npm-family options that take a value; their value is not the script name. */
12
13
  const VALUE_OPTIONS = new Set(['--prefix', '-C', '--dir', '-w', '--workspace', '--filter', '--cwd']);
@@ -97,13 +98,11 @@ export function isVocabularyTask(name: string, words: readonly string[] = ['test
97
98
  }
98
99
 
99
100
  /** Tasks a task's definition calls: composer `@refs`, `npm run X` inside a script, mise `depends`/`run`. */
100
- export function taskChildren(ctx: Ctx, name: string): string[] {
101
+ function taskChildren(ctx: Ctx, name: string): string[] {
101
102
  const out: string[] = [];
102
- const composer = readJson(ctx, 'composer.json');
103
- const entry = composer?.scripts?.[name];
103
+ const entry = at(readJson(ctx, 'composer.json'), 'scripts', name);
104
104
  if (entry !== undefined) {
105
- const list = Array.isArray(entry) ? entry : [entry];
106
- for (const e of list) {
105
+ for (const e of Array.isArray(entry) ? (entry as unknown[]) : [entry]) {
107
106
  if (typeof e !== 'string') continue;
108
107
  if (e.startsWith('@')) {
109
108
  const [ref, ...rest] = e.slice(1).trim().split(/\s+/);
@@ -114,15 +113,12 @@ export function taskChildren(ctx: Ctx, name: string): string[] {
114
113
  }
115
114
  }
116
115
  }
117
- const pkg = readJson(ctx, 'package.json');
118
- const script = pkg?.scripts?.[name];
116
+ const script = at(readJson(ctx, 'package.json'), 'scripts', name);
119
117
  if (typeof script === 'string') out.push(...runnerCalls(script));
120
- const mise = readToml(ctx, 'mise.toml');
121
- const task = mise?.tasks?.[name];
122
- if (task && typeof task === 'object') {
123
- if (Array.isArray(task.depends))
124
- out.push(...task.depends.filter((d: unknown): d is string => typeof d === 'string'));
125
- const runs = Array.isArray(task.run) ? task.run : [task.run];
118
+ const task = dict(at(readToml(ctx, 'mise.toml'), 'tasks', name));
119
+ if (task) {
120
+ out.push(...list(task['depends']).filter((d): d is string => typeof d === 'string'));
121
+ const runs = Array.isArray(task['run']) ? (task['run'] as unknown[]) : [task['run']];
126
122
  for (const r of runs) if (typeof r === 'string') out.push(...runnerCalls(r));
127
123
  }
128
124
  const tasks = listAllTasks(ctx);