@vodmal/vdx-cli 0.27.0 → 0.28.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vodmal/vdx-cli",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "vdx — unified lifecycle interface (up/down/build/test/check/fix) + versioned maturity audit with drift detection. Pairs with the vdx Claude Code plugin.",
5
5
  "keywords": [
6
6
  "vdx",
@@ -11,7 +11,7 @@ schema_version: "0.3"
11
11
 
12
12
  metadata:
13
13
  name: vdx-rubric-vodmal
14
- version: "1.4.2"
14
+ version: "1.5.0"
15
15
  description: "Personal maturity rubric for multi-stack projects (vodmal)"
16
16
  owner: vodmal
17
17
  homepage: "https://github.com/vodmal/vdx-rubric"
@@ -470,6 +470,10 @@ axes:
470
470
 
471
471
  - id: observability
472
472
  class: supporting
473
+ # v1.5.0: логгер, Sentry и релиз в Sentry — у процесса, который работает и
474
+ # падает без человека. Библиотека и CLI (lib-intent) пишут в stderr и код
475
+ # выхода — у них ось excluded. Определение lib-intent — в lifecycle-interface.L3.
476
+ applies_when: { not: *lib_intent }
473
477
  storage: level
474
478
  default_target: L4
475
479
  fact_sources: [composer.json, package.json, .github/workflows/]
@@ -526,6 +530,9 @@ axes:
526
530
  - id: mock-infra
527
531
  class: supporting
528
532
  applies_to: [php, node, go, python]
533
+ # v1.5.0: mock-сервер и проверка, что моков нет в проде, — у сервиса с
534
+ # зависимостями. У библиотеки и CLI (lib-intent) ось excluded.
535
+ applies_when: { not: *lib_intent }
529
536
  storage: level
530
537
  default_target: L4
531
538
  fact_sources: [composer.json, package.json, docker-compose.yml, mock-server/]
package/src/facts.ts CHANGED
@@ -16,12 +16,43 @@ export interface Ctx {
16
16
  fallbackRoot?: string;
17
17
  }
18
18
 
19
- /** Absolute path of `rel`: here, else in `fallbackRoot` when it is there. */
20
- function locate(ctx: Ctx, rel: string): string {
21
- const here = path.join(ctx.projectRoot, rel);
22
- if (ctx.fallbackRoot === undefined || fs.existsSync(here)) return here;
23
- const there = path.join(ctx.fallbackRoot, rel);
24
- return fs.existsSync(there) ? there : here;
19
+ /** Names in `dir`, read once per context; none when it cannot be read. */
20
+ function entries(ctx: Ctx, dir: string): string[] {
21
+ const key = `dir:${dir}`;
22
+ let names = ctx.cache.get(key) as string[] | undefined;
23
+ if (!names) {
24
+ try {
25
+ names = fs.readdirSync(dir);
26
+ } catch {
27
+ names = [];
28
+ }
29
+ ctx.cache.set(key, names);
30
+ }
31
+ return names;
32
+ }
33
+
34
+ /**
35
+ * `rel` under `root`, each segment in the case it is written. APFS and NTFS find a file
36
+ * under any case and ext4 does not: `tests/Fixtures` was there on a Mac that has only
37
+ * `tests/fixtures`, and the same project got another level on Linux.
38
+ */
39
+ function existsAsWritten(ctx: Ctx, root: string, rel: string): boolean {
40
+ let dir = root;
41
+ for (const name of path.normalize(rel).split(path.sep)) {
42
+ if (name === '' || name === '.') continue;
43
+ if (name !== '..' && !entries(ctx, dir).includes(name)) return false;
44
+ dir = path.join(dir, name);
45
+ }
46
+ return fs.existsSync(dir);
47
+ }
48
+
49
+ /** Absolute path of `rel`: here, else in `fallbackRoot`; null when in neither. */
50
+ function locate(ctx: Ctx, rel: string): string | null {
51
+ if (existsAsWritten(ctx, ctx.projectRoot, rel)) return path.join(ctx.projectRoot, rel);
52
+ if (ctx.fallbackRoot !== undefined && existsAsWritten(ctx, ctx.fallbackRoot, rel)) {
53
+ return path.join(ctx.fallbackRoot, rel);
54
+ }
55
+ return null;
25
56
  }
26
57
 
27
58
  /** The same directory without the fallback: a project's tasks are the ones its own runner sees. */
@@ -36,13 +67,17 @@ function ownCtx(ctx: Ctx): Ctx {
36
67
  }
37
68
 
38
69
  export function fileExists(ctx: Ctx, rel: string): boolean {
39
- return fs.existsSync(locate(ctx, rel));
70
+ return locate(ctx, rel) !== null;
40
71
  }
41
72
 
42
73
  export function readText(ctx: Ctx, rel: string): string | null {
43
74
  const key = `file:${rel}`;
44
75
  if (ctx.cache.has(key)) return ctx.cache.get(key) as string | null;
45
76
  const abs = locate(ctx, rel);
77
+ if (abs === null) {
78
+ ctx.cache.set(key, null);
79
+ return null;
80
+ }
46
81
  try {
47
82
  const t = fs.readFileSync(abs, 'utf8');
48
83
  ctx.cache.set(key, t);
@@ -183,13 +218,9 @@ export function expandPaths(paths: string[], ctx: Ctx): string[] {
183
218
  const dir = m[1];
184
219
  const ext = m[2];
185
220
  const abs = locate(ctx, dir);
186
- try {
187
- const files = fs.readdirSync(abs);
188
- for (const f of files) {
189
- if (f.endsWith('.' + ext)) out.push(path.join(dir, f));
190
- }
191
- } catch {
192
- /* dir missing — silent */
221
+ // A missing dir lists nothing.
222
+ for (const f of abs === null ? [] : entries(ctx, abs)) {
223
+ if (f.endsWith('.' + ext)) out.push(path.join(dir, f));
193
224
  }
194
225
  }
195
226
  } else {
package/src/init.ts CHANGED
@@ -6,7 +6,7 @@ import { at } from './narrow.ts';
6
6
  export const STANDARD_VERBS = ['up', 'down', 'build', 'test', 'check', 'fix'] as const;
7
7
  export type Verb = (typeof STANDARD_VERBS)[number];
8
8
 
9
- export const DEFAULT_BASELINE = 'github.com/VoDmAl/vdx-rubric-vodmal@v1.4.2';
9
+ export const DEFAULT_BASELINE = 'github.com/VoDmAl/vdx-rubric-vodmal@v1.5.0';
10
10
 
11
11
  const VERB_ALIASES: Record<Verb, string[]> = {
12
12
  up: ['docker:up', 'docker-up', 'docker:up:detached', 'start', 'dev', 'serve'],