Skip to content
This repository was archived by the owner on Nov 4, 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/25733-workspace-root-test-assumptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Tooling-only change (objectstack#25733): three tests no longer assume this tree is the pnpm workspace root. `check-doc-snippet-types` finds installed copies in the workspace root's `node_modules/.pnpm` (`virtualStorePath()` in `scripts/workspace-root.mjs`), the Console vendor-chunk tests read their standalone and merged arms off fixture layouts, and the CLI generator test runs its in-workspace case from a fixture workspace. `@object-ui/cli`'s `currentContext()` now asks `isWorkspaceRoot(cwd)`, the predicate `dev`, `serve` and `build` already use: the same expression, so no published behaviour changes.
47 changes: 39 additions & 8 deletions packages/cli/src/__tests__/app-generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1380,15 +1380,46 @@ describe('generation onto disk', () => {
}
}

/**
* A workspace root this file builds itself: a directory holding its own
* `pnpm-workspace.yaml`, with one member below it. Not `REPO_ROOT`: the
* objectui tree is a workspace root only standalone, and under the
* objectstack monorepo it is `ui/`, a member whose workspace file is one
* level up (objectstack#25733). A fixture names the branch in both layouts.
*/
function withWorkspace(run: (root: string, member: string) => void): void {
withTempDir((root) => {
writeFileSync(join(root, 'pnpm-workspace.yaml'), "packages:\n - 'packages/*'\n");
const member = join(root, 'packages', 'app');
mkdirSync(member, { recursive: true });
run(root, member);
});
}

it('writes the IN-WORKSPACE map when the generator runs at a workspace root', () => {
// `REPO_ROOT` walks up from `import.meta.url`, so this pins the same branch
// whatever directory the run was launched from — and `IN_WORKSPACE` is that
// same file-derived root, which is what keeps the expectation independent
// too. Put either side back on `process.cwd()` and this case silently
// becomes a third copy of the ambient mirror.
withTempDir((dir) => {
writingFrom(REPO_ROOT, () => createTempApp(dir, SCHEMA));
expect(filesOnDisk(dir)).toEqual(buildAppFiles(SCHEMA, IN_WORKSPACE));
// The fixture root is derived here, not from `process.cwd()`, so this pins
// the same branch whatever directory the run was launched from, and the
// expectation names that same root. Put either side back on
// `process.cwd()` and this case silently becomes a third copy of the
// ambient mirror.
withWorkspace((root) => {
withTempDir((dir) => {
writingFrom(root, () => createTempApp(dir, SCHEMA));
expect(filesOnDisk(dir)).toEqual(buildAppFiles(SCHEMA, { cwd: root, isMonorepo: true }));
});
});
});

it('writes the STANDALONE map from a workspace MEMBER — the generator does not walk up', () => {
// `currentContext()` asks `isWorkspaceRoot(cwd)`, the predicate `dev` and
// `serve` alias by: a `pnpm-workspace.yaml` ABOVE cwd makes no alias table,
// so it must not make empty dependency maps either. This is a user's
// package inside their own monorepo, and the objectui tree under `ui/`.
withWorkspace((_root, member) => {
withTempDir((dir) => {
writingFrom(member, () => createTempApp(dir, SCHEMA));
expect(filesOnDisk(dir)).toEqual(buildAppFiles(SCHEMA, { cwd: member, isMonorepo: false }));
});
});
});

Expand Down
22 changes: 19 additions & 3 deletions packages/cli/src/utils/app-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/

import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, existsSync } from 'fs';
import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
import { dirname, join } from 'path';
import chalk from 'chalk';
import * as yaml from 'js-yaml';
Expand All @@ -16,6 +16,7 @@ import {
REACT_RANGE,
SCAFFOLD_DEV_DEPENDENCIES
} from './scaffold-dependencies.js';
import { isWorkspaceRoot } from './workspace-vite.js';

export interface RouteInfo {
path: string;
Expand Down Expand Up @@ -281,10 +282,25 @@ export interface AppGeneratorContext {
isMonorepo: boolean;
}

/** The context the CLI commands themselves generate under. */
/**
* The context the CLI commands themselves generate under.
*
* "Runs at a workspace root" means `cwd` ITSELF holds a `pnpm-workspace.yaml`,
* and nothing wider: the answer is `isWorkspaceRoot(cwd)`, the same predicate
* `dev`, `serve` and `build` use to skip `npm install` and to build the alias
* table from that file's globs (`utils/workspace-vite.ts`). The two must agree,
* because this branch writes both dependency maps EMPTY on the promise that the
* command will alias the platform packages instead. So a directory that is only
* a workspace MEMBER is not a workspace root, whatever lies above it: a user's
* package inside their own monorepo, and equally the objectui tree when it is
* `ui/` of the objectstack monorepo (objectstack#25733), get the standalone map.
* Walking up to the nearest `pnpm-workspace.yaml` would hand them empty maps
* that no alias table backs. A user's project behaves as it always has: the
* file in `cwd` means in-workspace, no file means standalone.
*/
function currentContext(): AppGeneratorContext {
const cwd = process.cwd();
return { cwd, isMonorepo: existsSync(join(cwd, 'pnpm-workspace.yaml')) };
return { cwd, isMonorepo: isWorkspaceRoot(cwd) };
}

/**
Expand Down
4 changes: 3 additions & 1 deletion scripts/__tests__/check-doc-snippet-emitted-census.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,9 @@ describe("objectui#8397 — the manifest the emitted file's reader installs", ()
[`node_modules/${ROOT_DECLARED_CONTROL}/index.d.ts`]: 'export declare const control: number;\n',
// The gate reads "is it installed?" from pnpm's virtual store, so the
// fixture lays one out: an UNINSTALLED control produces TS2307 whether the
// bound ran or not, and would pin nothing.
// bound ran or not, and would pin nothing. The store is the WORKSPACE
// root's (objectstack#25733), so the fixture is one.
'pnpm-workspace.yaml': "packages:\n - 'packages/*'\n",
[`node_modules/.pnpm/${ROOT_DECLARED_CONTROL}@4.0.0/node_modules/${ROOT_DECLARED_CONTROL}/package.json`]:
JSON.stringify({ name: ROOT_DECLARED_CONTROL, version: '4.0.0', types: './index.d.ts' }),
[`node_modules/.pnpm/${ROOT_DECLARED_CONTROL}@4.0.0/node_modules/${ROOT_DECLARED_CONTROL}/index.d.ts`]:
Expand Down
28 changes: 28 additions & 0 deletions scripts/__tests__/check-doc-snippet-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,34 @@ describe('third-party resolution reaches exactly as far as the imported packages
).toBeTruthy();
});

it('finds an installed copy in the WORKSPACE root store, in both layouts (objectstack#25733)', () => {
// Standalone the tree is the workspace root; under the monorepo it is
// `ui/`, whose node_modules has no `.pnpm`, and the store is one level up.
// A tree no workspace governs has nothing installed in it.
const store = 'node_modules/.pnpm/@scope+pkg@1.0.0/node_modules/@scope/pkg/package.json';
const workspace = "packages:\n - 'packages/*'\n";
const root = tempTree({
'ws/pnpm-workspace.yaml': workspace,
[`ws/${store}`]: '{}',
'mono/pnpm-workspace.yaml': workspace,
[`mono/${store}`]: '{}',
'mono/ui/node_modules/other/package.json': '{}',
[`loose/${store}`]: '{}',
});
try {
expect(findInstalledCopy(path.join(root, 'ws'), '@scope/pkg')).toBe(
'node_modules/.pnpm/@scope+pkg@1.0.0/node_modules/@scope/pkg',
);
expect(findInstalledCopy(path.join(root, 'mono/ui'), '@scope/pkg')).toBe(
'../node_modules/.pnpm/@scope+pkg@1.0.0/node_modules/@scope/pkg',
);
expect(findInstalledCopy(path.join(root, 'mono/ui'), 'absent')).toBeNull();
expect(findInstalledCopy(path.join(root, 'loose'), '@scope/pkg')).toBeNull();
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});

it('neither control specifier is declared by any workspace package, in EITHER field the map reads', () => {
// Widened with the map (objectui#8919). A control that stays green only
// because the suite asks about one of two fields is a control that can be
Expand Down
10 changes: 10 additions & 0 deletions scripts/__tests__/check-ui-root-lookups.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ describe('check-ui-root-lookups (objectstack ruling D1)', () => {
expect(data).toEqual([]);
});

it('reads the store joined to the tree once, where the path first names it, and the root from the file it is in (objectstack#25733)', () => {
const head = "import { join, resolve } from 'node:path';\nimport { uiRoot } from './ui-root.mjs';\n";
const nested = lookupsIn('scripts/x.mjs', `${head}const store = join(uiRoot(), 'node_modules', '.pnpm');\nconst entry = join(store, 'x@1.0.0', 'node_modules');`);
expect(nested.map((h) => [h.shape, h.text])).toEqual([['workspace-file-at-ui-root', "join(uiRoot(), 'node_modules', '.pnpm')"]]);
// The same climb lands on the tree from one file and on `scripts/` from another.
const climb = "const root = resolve(__dirname, '../..');\nconst store = join(root, 'node_modules', '.pnpm');";
expect(lookupsIn('scripts/__tests__/x.test.ts', `${head}${climb}`).map((h) => h.shape)).toEqual(['workspace-file-at-ui-root']);
expect(lookupsIn('scripts/__tests__/deeper/x.test.ts', `${head}${climb}`)).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
61 changes: 47 additions & 14 deletions scripts/__tests__/vite-objectstack-client-dist.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,46 @@ function makeFixtureRoot(): string {
* A copy of the installed client placed outside the workspace — manifest and
* `dist/` intact, no reachable `node_modules` for its two dependencies.
*
* This is the card's reproduction, reduced to a fixture.
* This is the card's reproduction, reduced to a fixture. The package's OWN
* `node_modules` stays behind: a store copy has none (its dependencies are its
* siblings in the store), but under the objectstack monorepo the installed
* client is a workspace link to a package that carries one, and copying that
* would carry the dependencies along (objectstack#25733).
*/
function outOfTreeClientCopy(): string {
const dest = path.join(makeFixtureRoot(), 'client');
fs.cpSync(fs.realpathSync(installedClientDir), dest, { recursive: true, dereference: true });
const source = fs.realpathSync(installedClientDir);
const ownNodeModules = path.join(source, 'node_modules');
fs.cpSync(source, dest, { recursive: true, dereference: true, filter: (src) => src !== ownNodeModules });
return dest;
}

/**
* pnpm's store layout, on disk: the client in
* `.pnpm/@objectstack+client@1.0.0/node_modules/@objectstack/client` with its
* dependency as a sibling there, and a console whose `node_modules` holds only
* a SYMLINK to it. That is how a standalone checkout installs the client; under
* the objectstack monorepo it is a workspace link instead, so the realpath
* measurement below builds this layout rather than reading the checkout's
* (objectstack#25733).
*/
function storeLayoutClient(): { linked: string; real: string } {
const root = makeFixtureRoot();
const storeModules = path.join(root, 'node_modules/.pnpm/@objectstack+client@1.0.0/node_modules');
const real = path.join(storeModules, '@objectstack/client');
fs.mkdirSync(real, { recursive: true });
fs.writeFileSync(
path.join(real, 'package.json'),
JSON.stringify({ name: CLIENT_PACKAGE_NAME, dependencies: { '@objectstack/core': '1.0.0' } })
);
fs.mkdirSync(path.join(storeModules, '@objectstack/core'), { recursive: true });
fs.writeFileSync(path.join(storeModules, '@objectstack/core/package.json'), '{"name":"@objectstack/core"}');
const linked = path.join(root, 'console/node_modules/@objectstack/client');
fs.mkdirSync(path.dirname(linked), { recursive: true });
fs.symlinkSync(real, linked, 'dir');
return { linked, real: fs.realpathSync(real) };
}

/**
* The walk `packageResolvesFrom` performs, re-stated here as a MEASUREMENT
* rather than an import: these cases exist to show what the production walk
Expand Down Expand Up @@ -190,22 +222,23 @@ describe('objectui#6094: a VALID override is still injected — all three spelli
});

it('resolves the dependency walk through the pnpm symlink, not around it', () => {
// Skipped only where the installed client is NOT a symlink (a hoisted,
// non-pnpm install), because then there is no symlink for the realpath to
// make a difference to and the counter-probe would be measuring nothing.
if (fs.realpathSync(installedClientDir) === installedClientDir) return;

// The measurement that makes `realpathSync` load-bearing rather than
// tidy: from the symlink path, the client's own dependency is NOT found —
// the walk climbs `apps/console/node_modules` → `apps/console` → the repo
// root, none of which carry `node_modules/@objectstack/core`. From the
// store path it is found, beside the store copy of the client.
expect(walkResolves(installedClientDir, '@objectstack/core')).toBe(false);
expect(walkResolves(fs.realpathSync(installedClientDir), '@objectstack/core')).toBe(true);
// tidy, on the store layout: from the symlink path, the client's own
// dependency is NOT found — the walk climbs `console/node_modules` →
// `console` → the fixture root, none of which carry
// `node_modules/@objectstack/core`. From the store path it is found,
// beside the store copy of the client.
const { linked, real } = storeLayoutClient();
expect(real).not.toBe(linked);
expect(walkResolves(linked, '@objectstack/core')).toBe(false);
expect(walkResolves(real, '@objectstack/core')).toBe(true);
// …so the injection, which walks from `packageDir`, must be reading the
// store path. Without this the check would reject the installed client —
// the one input it must accept.
expect(resolveClientDistInjection(installedClientDir)!.packageDir).not.toBe(installedClientDir);
expect(resolveClientDistInjection(linked)!.packageDir).toBe(real);
// And this checkout's own installed client, store copy or workspace link,
// is judged at its real path too.
expect(resolveClientDistInjection(installedClientDir)!.packageDir).toBe(realClientDir());
});

it('does not demand `peerDependencies` — they come from the consuming app', () => {
Expand Down
19 changes: 16 additions & 3 deletions scripts/__tests__/vite-objectstack-spec-dist.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
versionSatisfiesRange,
zodPackageDirOf,
} from '../vite-objectstack-spec-dist';
import { BASELINE_CHUNK_TESTS, frameworkPackagesDir, workspaceChunkTests } from '../vite-objectstack-workspace.ts';

/**
* objectui#4854 — `apps/console/vite.config.ts` honours `OBJECTSTACK_SPEC_DIST`,
Expand Down Expand Up @@ -93,6 +94,16 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../
const BASE_VENDOR_TEST =
/([\\/]node_modules[\\/]@objectstack[\\/](?!lint[\\/])|[\\/]@objectstack\+(?!lint@))/;

/**
* The vendor test the console config builds in THIS checkout's layout with no
* override set: `BASE_VENDOR_TEST` standalone, the same arms plus the
* framework's `packages/` when the tree is `ui/` of the objectstack monorepo
* (objectstack#25733). "Inert" means "unmoved from this", in either layout;
* that the standalone arm IS the baseline literal, byte for byte, is pinned on
* a fixture layout in `vite-objectstack-workspace.test.ts`.
*/
const LAYOUT_VENDOR_TEST = workspaceChunkTests(frameworkPackagesDir(path.join(repoRoot, 'apps/console'))).vendor;

/**
* The baseline "this module id is the spec" test, as the console config spells it.
*
Expand Down Expand Up @@ -930,11 +941,13 @@ describe('objectui#4854: the four flagged surfaces in the console config', () =>
'react-map-gl/maplibre',
]);

// 3. the vendor-objectstack chunk test — the literal, unwidened.
// 3. the vendor-objectstack chunk test — the layout's own, unwidened. The
// mirror above is still the helper's baseline, so it cannot drift alone.
const groups = config.build.rollupOptions.output.advancedChunks.groups as { name: string; test: RegExp }[];
const vendor = groups.find((g) => g.name === 'vendor-objectstack');
expect(vendor).toBeDefined();
expect(vendor!.test.source).toBe(BASE_VENDOR_TEST.source);
expect(BASE_VENDOR_TEST.source).toBe(BASELINE_CHUNK_TESTS.vendor.source);
expect(vendor!.test.source).toBe(LAYOUT_VENDOR_TEST.source);
// …and what that literal MEANS, so a future rewrite that keeps the shape
// but changes the membership cannot pass by pasting a new string in.
// Reads off the live config's own regex, never the mirror above.
Expand Down Expand Up @@ -990,7 +1003,7 @@ describe('objectui#4854: the four flagged surfaces in the console config', () =>
(config.build.rollupOptions.output.advancedChunks.groups as { name: string; test: RegExp }[]).find(
(g) => g.name === 'vendor-objectstack'
)!.test;
expect(vendorOf(injected).source.startsWith(BASE_VENDOR_TEST.source)).toBe(true);
expect(vendorOf(injected).source.startsWith(LAYOUT_VENDOR_TEST.source)).toBe(true);
expect(vendorOf(injected).test(`${fs.realpathSync(installedSpecDir)}/dist/ui/index.mjs`)).toBe(true);
// Widening appends an arm; it must not resurrect the lint exclusion the
// baseline arms carry, or a spec-dist build would silently re-eagerize the
Expand Down
Loading
Loading