Skip to content
This repository was archived by the owner on Oct 26, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changeset/25723-workspace-root-readers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Tooling-only change (objectstack#25723): the scripts, gate tests and e2e specs that read `pnpm-workspace.yaml`, `pnpm-lock.yaml`, the root manifest's `pnpm` block or `turbo.json` resolve the file through `scripts/workspace-root.mjs`, which answers the same path in this repository and one level up when the tree is the objectstack monorepo's `ui/`. The two package test files touched (`@object-ui/cli`, `@object-ui/i18n`) read this repository's own workspace manifest; no published behaviour changes.
22 changes: 7 additions & 15 deletions e2e/dashboard-metric-tile-height.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { pathToFileURL } from 'node:url';
import { uiRoot } from '../scripts/ui-root.mjs';

/**
* GEOMETRY pin for objectui#12704: a one-row dashboard metric tile is as tall
Expand Down Expand Up @@ -60,20 +61,11 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
* workspace `dist/`, and their `exports` point there.
*/

const REPO_ROOT = (() => {
// Rooted on this file, never on `process.cwd()` (AGENTS.md: tests read the
// filesystem relative to their own file). Bare `import.meta.url`, walked up.
let dir = path.dirname(fileURLToPath(import.meta.url));
for (let i = 0; i < 10; i++) {
try {
readFileSync(path.join(dir, 'pnpm-workspace.yaml'), 'utf8');
return dir;
} catch {
dir = path.dirname(dir);
}
}
throw new Error('could not locate the repo root from ' + import.meta.url);
})();
// The objectui tree, resolved from `scripts/ui-root.mjs`'s own location -- the
// one fact true standalone and under the objectstack monorepo's `ui/`. Walking up
// to `pnpm-workspace.yaml` found the OBJECTSTACK root there (objectstack#25723),
// and the sources this spec reads live under this tree's `packages/`.
const REPO_ROOT = uiRoot();

const source = (rel: string) => path.join(REPO_ROOT, rel);

Expand Down
21 changes: 6 additions & 15 deletions e2e/record-header-title-width.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { compile } from 'tailwindcss';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import { uiRoot } from '../scripts/ui-root.mjs';

/**
* GEOMETRY pin for the record-header title/action width arbitration on the two
Expand Down Expand Up @@ -46,21 +47,11 @@ import path from 'node:path';
* produces, not a worst case invented to make a floor look necessary.
*/

const REPO_ROOT = (() => {
// Rooted on this file, never on `process.cwd()` — AGENTS.md's
// "tests read the filesystem relative to their own file" rule. Bare
// `import.meta.url`, walked up; never `new URL(<relative>, import.meta.url)`.
let dir = path.dirname(fileURLToPath(import.meta.url));
for (let i = 0; i < 10; i++) {
try {
readFileSync(path.join(dir, 'pnpm-workspace.yaml'), 'utf8');
return dir;
} catch {
dir = path.dirname(dir);
}
}
throw new Error('could not locate the repo root from ' + import.meta.url);
})();
// The objectui tree, resolved from `scripts/ui-root.mjs`'s own location -- the
// one fact true standalone and under the objectstack monorepo's `ui/`. Walking up
// to `pnpm-workspace.yaml` found the OBJECTSTACK root there (objectstack#25723),
// and the sources this spec reads live under this tree's `packages/`.
const REPO_ROOT = uiRoot();

const read = (rel: string) => readFileSync(path.join(REPO_ROOT, rel), 'utf8');

Expand Down
20 changes: 6 additions & 14 deletions e2e/sidebar-offcanvas-geometry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { readFileSync } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { uiRoot } from '../scripts/ui-root.mjs';

/**
* GEOMETRY pin for objectui#11464: the shadcn sidebar's `offcanvas` form drew
Expand Down Expand Up @@ -71,20 +72,11 @@ import { fileURLToPath } from 'node:url';
* below `md` the desktop form is `hidden` anyway.
*/

const REPO_ROOT = (() => {
// Rooted on this file, never on `process.cwd()` (AGENTS.md: tests read the
// filesystem relative to their own file). Bare `import.meta.url`, walked up.
let dir = path.dirname(fileURLToPath(import.meta.url));
for (let i = 0; i < 10; i++) {
try {
readFileSync(path.join(dir, 'pnpm-workspace.yaml'), 'utf8');
return dir;
} catch {
dir = path.dirname(dir);
}
}
throw new Error('could not locate the repo root from ' + import.meta.url);
})();
// The objectui tree, resolved from `scripts/ui-root.mjs`'s own location -- the
// one fact true standalone and under the objectstack monorepo's `ui/`. Walking up
// to `pnpm-workspace.yaml` found the OBJECTSTACK root there (objectstack#25723),
// and the sources this spec reads live under this tree's `packages/`.
const REPO_ROOT = uiRoot();

const source = (rel: string) => path.join(REPO_ROOT, rel);

Expand Down
22 changes: 7 additions & 15 deletions e2e/studio-item-header-title-width.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { pathToFileURL } from 'node:url';
import { uiRoot } from '../scripts/ui-root.mjs';

/**
* GEOMETRY pin for objectui#12791: in Studio's item header a long api-name
Expand Down Expand Up @@ -59,20 +60,11 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
* font draws the title about 17% wider than a local one.
*/

const REPO_ROOT = (() => {
// Rooted on this file, never on `process.cwd()` (AGENTS.md: tests read the
// filesystem relative to their own file). Bare `import.meta.url`, walked up.
let dir = path.dirname(fileURLToPath(import.meta.url));
for (let i = 0; i < 10; i++) {
try {
readFileSync(path.join(dir, 'pnpm-workspace.yaml'), 'utf8');
return dir;
} catch {
dir = path.dirname(dir);
}
}
throw new Error('could not locate the repo root from ' + import.meta.url);
})();
// The objectui tree, resolved from `scripts/ui-root.mjs`'s own location -- the
// one fact true standalone and under the objectstack monorepo's `ui/`. Walking up
// to `pnpm-workspace.yaml` found the OBJECTSTACK root there (objectstack#25723),
// and the sources this spec reads live under this tree's `packages/`.
const REPO_ROOT = uiRoot();

const source = (rel: string) => path.join(REPO_ROOT, rel);

Expand Down
20 changes: 6 additions & 14 deletions e2e/toast-under-modal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { readFileSync } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { uiRoot } from '../scripts/ui-root.mjs';

/**
* objectui#11723: a toast's Undo and close buttons were dead while the record
Expand Down Expand Up @@ -71,20 +72,11 @@ import { fileURLToPath } from 'node:url';
* under `e2e/` imports one.
*/

const REPO_ROOT = (() => {
// Rooted on this file, never on `process.cwd()` (AGENTS.md: tests read the
// filesystem relative to their own file). Bare `import.meta.url`, walked up.
let dir = path.dirname(fileURLToPath(import.meta.url));
for (let i = 0; i < 10; i++) {
try {
readFileSync(path.join(dir, 'pnpm-workspace.yaml'), 'utf8');
return dir;
} catch {
dir = path.dirname(dir);
}
}
throw new Error('could not locate the repo root from ' + import.meta.url);
})();
// The objectui tree, resolved from `scripts/ui-root.mjs`'s own location -- the
// one fact true standalone and under the objectstack monorepo's `ui/`. Walking up
// to `pnpm-workspace.yaml` found the OBJECTSTACK root there (objectstack#25723),
// and the sources this spec reads live under this tree's `packages/`.
const REPO_ROOT = uiRoot();

const source = (rel: string) => path.join(REPO_ROOT, rel);

Expand Down
25 changes: 14 additions & 11 deletions packages/cli/src/__tests__/workspace-vite.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
workspacePackageDirs,
workspaceSourceAliases
} from '../utils/workspace-vite.js';
import { workspaceGlobs, workspaceRoot } from '../../../../scripts/workspace-root.mjs';

const __dirname = dirname(fileURLToPath(import.meta.url));
/** packages/cli/src/__tests__ -> repo root */
Expand All @@ -58,19 +59,16 @@ const BARREL_EXTENSIONS = ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx'];
/**
* The workspace's package directories, expanded WITHOUT the helper under test.
*
* Deliberately hand-rolled and deliberately narrow: it understands the two
* pattern shapes this repo's manifest actually uses (a `dir/*` fan-out and a
* literal directory) and throws on anything else, so a manifest that grows a
* The globs come from `scripts/workspace-root.mjs`'s text parser -- a different
* reader from the YAML-and-glob one under test, and one that understands the
* two pattern shapes this repo's manifest actually uses (a `dir/*` fan-out and
* a literal directory) and throws on anything else, so a manifest that grows a
* shape this expectation cannot read fails loudly here instead of quietly
* agreeing with whatever the helper returned.
* agreeing with whatever the helper returned. They are relative to the objectui
* tree in both layouts (objectstack#25723).
*/
function independentWorkspaceDirs(): string[] {
const manifest = readFileSync(join(REPO_ROOT, 'pnpm-workspace.yaml'), 'utf-8');
const patterns: string[] = [];
for (const line of manifest.split('\n')) {
const match = /^\s*-\s*['"]?([^'"#\s]+)['"]?\s*$/.exec(line);
if (match) patterns.push(match[1]);
}
const patterns = workspaceGlobs(REPO_ROOT);
expect(patterns.length).toBeGreaterThan(0);

const dirs: string[] = [];
Expand Down Expand Up @@ -109,7 +107,12 @@ function independentAliasablePackages(): Map<string, string> {
return found;
}

const aliases = workspaceSourceAliases(REPO_ROOT);
// The helper under test asks about a pnpm workspace ROOT. Standalone that is
// this repository's root; under the objectstack monorepo it is the parent, whose
// `pnpm-workspace.yaml` declares this tree's packages as `ui/packages/*` and so
// on, and the `@object-ui/*` table it derives is the same set of `src` directories
// the expectation above expands relative to this tree (objectstack#25723).
const aliases = workspaceSourceAliases(workspaceRoot(REPO_ROOT));

describe('workspace alias table (objectui#3890)', () => {
it('covers every @object-ui package the workspace manifest declares', () => {
Expand Down
17 changes: 12 additions & 5 deletions packages/i18n/src/__tests__/machineLocaleCensus-9909.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
*/

import { describe, it, expect } from 'vitest';
import { workspaceGlobs as readWorkspaceGlobs } from '../../../../scripts/workspace-root.mjs';
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
Expand All @@ -86,12 +87,18 @@ const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'opti
/* The population */
/* -------------------------------------------------------------------------- */

/** The workspace globs, read from `pnpm-workspace.yaml` rather than restated. */
/**
* The `packages:` globs that describe this tree, relative to `repoRoot`, from
* the one parser in `scripts/workspace-root.mjs` (objectstack#25723): read rather
* than hardcoded so a new workspace root is covered the day it is added, and
* throwing on a shape it does not know rather than skipping it, because a guard
* that silently stops looking at part of the workspace goes on reporting
* success over a shrinking surface. Where the file is -- the objectui root, or
* the monorepo root one level up with its `ui/` prefix stripped -- is the
* helper's question, not this file's.
*/
function workspaceGlobs(): string[] {
const yaml = readFileSync(path.join(repoRoot, 'pnpm-workspace.yaml'), 'utf8');
const block = /^packages:\s*\n((?:[ \t]+-[^\n]*\n?)+)/m.exec(yaml);
if (!block) return [];
return [...block[1].matchAll(/-\s*['"]?([^'"\s#]+)['"]?/g)].map((m) => m[1]);
return readWorkspaceGlobs(repoRoot);
}

function workspacePackageDirs(): string[] {
Expand Down
3 changes: 2 additions & 1 deletion scripts/__tests__/check-dist-completeness.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, it } from 'vitest';
import { uiTurboConfigPath } from '../workspace-root.mjs';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
Expand Down Expand Up @@ -306,7 +307,7 @@ describe('this repository', () => {
);

it('turbo treats the gate as an input of the builds that run it', () => {
const turbo = JSON.parse(fs.readFileSync(path.join(repoRoot, 'turbo.json'), 'utf8'));
const turbo = JSON.parse(fs.readFileSync(uiTurboConfigPath(), 'utf8'));
// Otherwise editing the gate does not invalidate a single cached build, and
// the new rule is enforced only where nothing was cached.
expect(turbo.tasks.build.inputs).toContain('$TURBO_ROOT$/scripts/check-dist-completeness.mjs');
Expand Down
3 changes: 2 additions & 1 deletion scripts/__tests__/check-installed-spec-pin-claims.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, it } from 'vitest';
import { lockfilePath } from '../workspace-root.mjs';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
Expand Down Expand Up @@ -181,7 +182,7 @@ describe('attribution — a version on the line is not automatically the anchor'
});

describe('the pin, read from two faces', () => {
const lock = pinsFromLockfile(fs.readFileSync(path.join(repoRoot, 'pnpm-lock.yaml'), 'utf8'));
const lock = pinsFromLockfile(fs.readFileSync(lockfilePath(), 'utf8'));
const workspaces = execFileSync('git', ['-C', repoRoot, 'ls-files', '--', '*/package.json'], { encoding: 'utf8' })
.split('\n')
.filter((f) => /^(packages|apps|examples)\/[^/]+\/package\.json$/.test(f))
Expand Down
13 changes: 7 additions & 6 deletions scripts/__tests__/check-lint-rule-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,13 +586,14 @@ describe('this repository', () => {
expect(row.vacuousMatches.length, `ledger row '${row.glob}' declares nothing any more`).toBeGreaterThan(0);
expect(row.ruleBearingMatches, `ledger row '${row.glob}' over-claims`).toEqual([]);
}
// Predicate 2, same two properties. Its population is two files today --
// the vitest config objectui#8337 was filed about, and the declaration twin
// `scripts/ui-root.d.mts` that package tests (compiled with `allowJs: false`)
// need to import `scripts/ui-root.mjs` typed (objectstack#25685, objectui#13472)
// -- and a green here means both are DECLARED rather than invisible.
// Predicate 2, same two properties. Its population is three files today --
// the vitest config objectui#8337 was filed about, and the declaration twins
// `scripts/ui-root.d.mts` and `scripts/workspace-root.d.mts` that package
// tests (compiled with `allowJs: false`) need to import those helpers typed
// (objectstack#25685, objectui#13472, objectstack#25723) -- and a green here
// means all three are DECLARED rather than invisible.
expect(extensionProbeCollapse(result.reach)).toBeNull();
expect(result.unreached).toEqual(['scripts/ui-root.d.mts', 'vitest.config.mts']);
expect(result.unreached).toEqual(['scripts/ui-root.d.mts', 'scripts/workspace-root.d.mts', 'vitest.config.mts']);
// objectui#8369: this run is only a reading of the tree git tracks if the
// filter was there and could answer both ways.
expect(result.git.available).toBe(true);
Expand Down
19 changes: 16 additions & 3 deletions scripts/__tests__/check-lockfile-dedupe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,9 @@ import { fileURLToPath } from 'node:url';

import { describe, expect, it } from 'vitest';

import { DEDUPE_SENTINEL, pinnedBuildMissing } from '../check-lockfile-dedupe.mjs';
import { CACHE_DIR, STAMP_FILE } from '../objectstack-pin.mjs';
import { DEDUPE_SENTINEL, pinDeclared, pinnedBuildMissing } from '../check-lockfile-dedupe.mjs';
import { CACHE_DIR, OVERRIDE_PREFIX, STAMP_FILE } from '../objectstack-pin.mjs';
import { isNestedWorkspace } from '../workspace-root.mjs';
import { FLOORS, TEST_FILE } from '../check-test-path-roots.mjs';
import { NOT_A_GATE, OPTIONAL_CONTEXTS, REQUIRED_CONTEXTS } from '../dependabot-merge-gate.mjs';
import { maskComments } from '../js-comment-mask.mjs';
Expand Down Expand Up @@ -344,9 +345,21 @@ describe('the pinned @objectstack/* build is the checker\'s precondition (object
}
});

it('applies only where the manifest pnpm reads overrides from declares the pin (objectstack#25723)', () => {
// Standalone the root manifest declares `@objectstack/*: file:.objectstack-pin/...`
// and the stamp is a precondition; under the monorepo nothing does, the
// `@objectstack/*` packages are workspace members, and there is no build
// whose absence could send `pnpm dedupe --check` to the registry.
expect(pinDeclared({ pnpm: { overrides: { '@objectstack/spec': `${OVERRIDE_PREFIX}spec`, react: '19.3.0' } } })).toBe(true);
expect(pinDeclared({ pnpm: { overrides: { react: '19.3.0' } } })).toBe(false);
expect(pinDeclared({ pnpm: {} })).toBe(false);
expect(pinDeclared({})).toBe(false);
expect(pinDeclared()).toBe(!isNestedWorkspace());
});

it('checks before it spawns pnpm, and says so in a could-not-run verdict', () => {
const source = fs.readFileSync(path.join(repoRoot, SCRIPT), 'utf8');
const guard = source.indexOf('if (pinnedBuildMissing())');
const guard = source.indexOf('if (pinDeclared() && pinnedBuildMissing())');
expect(guard, 'the precondition is gone from main()').toBeGreaterThan(0);
expect(guard).toBeLessThan(source.indexOf("spawnSync('pnpm', ['dedupe', '--check']"));
expect(source.slice(guard, source.indexOf("spawnSync('pnpm', ['dedupe', '--check']"))).toContain("outcome: 'cannot-run'");
Expand Down
3 changes: 2 additions & 1 deletion scripts/__tests__/check-lockfile-integrity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
import { lockfilePath } from '../workspace-root.mjs';

import { readWorkflows, repoRoot } from './workflow-checks';
import { NOT_A_GATE, OPTIONAL_CONTEXTS, REQUIRED_CONTEXTS } from '../dependabot-merge-gate.mjs';
Expand All @@ -33,7 +34,7 @@ import {
} from '../check-lockfile-integrity.mjs';

const GATE = path.join(repoRoot, 'scripts/check-lockfile-integrity.mjs');
const LOCKFILE = path.join(repoRoot, 'pnpm-lock.yaml');
const LOCKFILE = lockfilePath();
const realLock = fs.readFileSync(LOCKFILE, 'utf8');

/** The check-run name the workflow's job produces. */
Expand Down
8 changes: 8 additions & 0 deletions scripts/__tests__/check-ui-root-lookups.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ describe('check-ui-root-lookups (objectstack ruling D1)', () => {
expect(silent).toEqual([]);
});

it('reads a marker read inside a dirname walk as the lookup it is, and a data read in a plain loop as data (objectstack#25723)', () => {
const head = "import { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n";
const walk = lookupsIn('e2e/x.spec.ts', `${head}let dir = __dirname;\nfor (let i = 0; i < 10; i++) {\n try { readFileSync(join(dir, 'pnpm-workspace.yaml'), 'utf8'); break; } catch { dir = dirname(dir); }\n}`);
expect(walk.map((h) => h.shape)).toEqual(['marker-walk']);
const data = lookupsIn('x.ts', `${head}for (const root of roots) globs.push(readFileSync(join(root, 'pnpm-workspace.yaml'), 'utf8'));`);
expect(data).toEqual([]);
});

it('finds no lookup outside the helper in this tree, and every allowlist entry is live', () => {
const result = scan(repoRoot);
expect(result.scanned).toBeGreaterThan(100);
Expand Down
3 changes: 2 additions & 1 deletion scripts/__tests__/ci-cd-pipeline-doc.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, it } from 'vitest';
import { lockfilePath as workspaceLockfilePath } from '../workspace-root.mjs';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
Expand Down Expand Up @@ -1795,7 +1796,7 @@ describe('ci-cd-pipeline.md — contexts that can never be required (#4170)', ()
*/
const backendEnvPath = path.join(repoRoot, 'e2e/live/ci/backend.env');
const startBackendPath = path.join(repoRoot, 'e2e/live/ci/start-backend.sh');
const lockfilePath = path.join(repoRoot, 'pnpm-lock.yaml');
const lockfilePath = workspaceLockfilePath();
const backendEnv = fs.readFileSync(backendEnvPath, 'utf8');
const startBackend = fs.readFileSync(startBackendPath, 'utf8');

Expand Down
Loading
Loading