Skip to content
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
624 changes: 624 additions & 0 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions packages/spec/scripts/check-duration-unit-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,14 @@ export const ROOT_DIR_WATCH_HINTS = [
'packages/connectors/*/src/**',
'apps/*/src/**',
'examples/*/src/**',
// The objectui tree, imported under `ui/` (objectstack#25677 / #25697): four
// workspace roots pnpm-workspace.yaml declares ahead of the import. Each is a
// live workspace root (the self-test holds both directions), and each walks
// nothing until `ui/` arrives.
'ui/src/**',
'ui/packages/*/src/**',
'ui/apps/*/src/**',
'ui/examples/*/src/**',
];

/** Canonical unit → every spelling the describe prose or a key token may use. */
Expand Down
4 changes: 4 additions & 0 deletions packages/spec/scripts/check-llms-txt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ export const DECLARED_WATCH_HINTS = [
'packages/**/package.json',
'apps/*/package.json',
'examples/*/package.json',
// The objectui tree's workspace roots (`ui`, `ui/packages/*`, `ui/apps/*`,
// `ui/examples/*`, objectstack#25677). This gate opens every manifest under
// them once the tree is imported; until then the globs match nothing.
'ui/**/package.json',
];

/** A domain directory with no `*.zod.ts` under it is not a schema domain. */
Expand Down
16 changes: 16 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,23 @@ packages:
- packages/connectors/*
- apps/*
- examples/*
# The objectui tree, once it is imported under ui/ (objectstack#25677). Until
# then these globs match no directory and pnpm resolves exactly the workspace
# above: the lockfile, `pnpm install` and `pnpm -r ls` are unchanged. `ui`
# itself is a member because objectui's root package.json carries the scripts
# its CI runs (`lint:root`, `type-check:e2e`, the `check:*` gates). objectui's
# own `docs` workspace entry is not carried: it names no package.
# objectui's pnpm overrides and its patchedDependencies entry arrive with the
# import, not here: an unused patch fails `pnpm install` and an override
# rewrites this lockfile.
- ui
- ui/packages/*
- ui/apps/*
- ui/examples/*

# objectui declares no onlyBuiltDependencies of its own (neither in its
# pnpm-workspace.yaml nor in its package.json `pnpm` field), so the union of
# the two allowlists is this list unchanged.
onlyBuiltDependencies:
- better-sqlite3
- esbuild
Expand Down
40 changes: 38 additions & 2 deletions scripts/check-cross-package-test-inputs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
// turbo.json declares, one per recognised name -- so the pin moves when either
// set does. That is the point: a directory dropping out of turbo.json is a
// declaration disappearing, which is worth a red.
'the TOP-LEVEL WHITELIST, reconciled against turbo.json (#18342)': 26,
'the TOP-LEVEL WHITELIST, reconciled against turbo.json (#18342)': 30,
});

// DELETING an entry silences that battery's floor exactly as effectively as
Expand Down Expand Up @@ -1435,6 +1435,22 @@ export const TOP_LEVEL_DIRECTORIES_PENDING = Object.freeze({
'.github': 'over-collection census (#18342): two literals, both non-reads; disposition reserved to the seat',
});

/**
* Top-level directories a `$TURBO_ROOT$` input declares AHEAD of their arrival in
* the tree, each with the reason. A name here is excused from the "tracked as
* neither file nor directory" case ONLY while git tracks nothing under it; the
* moment it is tracked it lands in `directories` and owes a place in
* `RECOGNISED_TOP_LEVEL_DIRECTORIES` or `TOP_LEVEL_DIRECTORIES_PENDING` like any
* other, so the import commit decides how this collector treats it.
*
* `ui`: the objectui tree, imported under `ui/` (objectstack#25677). turbo.json's
* `type-check` and `lint` tasks carry objectui's repo-root inputs re-rooted there.
* Retire the entry with the import.
*/
export const TOP_LEVEL_DIRECTORIES_AWAITING_IMPORT = Object.freeze({
ui: 'the objectui import (objectstack#25677): turbo.json declares its type-check and lint inputs ahead of the tree',
});

/** Repo-root FILES declared as `$TURBO_ROOT$` inputs -- the fourth shape, pinned, not collected. */
export const TURBO_ROOT_FILE_INPUTS = Object.freeze(['sdui.manifest.json']);

Expand Down Expand Up @@ -4353,7 +4369,27 @@ function selfTest() {
// Non-vacuity first: a reading of zero would green every per-directory case
// below by having none of them run.
ok('turbo.json still declares $TURBO_ROOT$ inputs at all', live.directories.size > 0);
ok('no declared top-level segment is tracked as neither file nor directory', live.unknown.size === 0);
const awaitingImport = new Set(Object.keys(TOP_LEVEL_DIRECTORIES_AWAITING_IMPORT));
const unexcused = [...live.unknown].filter((name) => !awaitingImport.has(name));
ok(
`no declared top-level segment is tracked as neither file nor directory (unexcused: ${unexcused.join(', ') || 'none'})`,
unexcused.length === 0,
);
// The excuse is narrow in both directions: it covers only the names it lists,
// and only while git tracks nothing under them.
ok(
'an unknown segment the awaiting-import map does not list is still unexcused',
[...new Set(['zzqq-25697', ...live.unknown])].filter((name) => !awaitingImport.has(name)).includes('zzqq-25697'),
);
ok(
'an awaiting-import name git already tracks is NOT excused here (it must be recognised or pending)',
[...awaitingImport].every((name) => !live.directories.has(name) || recognised.has(name) || pending.has(name)),
);
ok('the awaiting-import map holds exactly the measured arrivals', [...awaitingImport].sort().join() === 'ui');
ok(
'every awaiting-import entry carries a reason',
Object.values(TOP_LEVEL_DIRECTORIES_AWAITING_IMPORT).every((r) => typeof r === 'string' && r.length > 0),
);
for (const dir of [...live.directories].sort()) {
ok(
`turbo.json declares "${dir}/" and the collector accounts for it (recognised, or pending with a reason)`,
Expand Down
82 changes: 80 additions & 2 deletions scripts/check-dispatcher-error-vocabulary.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,8 @@
* accept set, deliberately not taken here.
*/

import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
import { readFileSync, readdirSync, statSync, existsSync, mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
// [#15487] The SHARED half of the regex-literal recogniser: the 14-word
// `REGEX_AFTER_KEYWORD` set, `IDENT_CHAR`, and the literal WALK (character
// class, backslash sequences, line-terminator refusal, flags) live ONCE, in
Expand Down Expand Up @@ -493,12 +494,46 @@ function expandWorkspaceMembers(root, glob) {
* passes {@link expandWorkspaceMembers} — production and `--self-test` reading
* two different populations is the defect #17145 carded, not a spare knob.
*/
export function emptyWorkspaceGlobs({ readGlobs, expand }) {
export function emptyWorkspaceGlobs({ readGlobs, expand, awaiting = new Set() }) {
return readGlobs()
.filter((glob) => !isExclusionGlob(glob))
.filter((glob) => !awaiting.has(glob))
.filter((glob) => expand(glob).length === 0);
}

/**
* Workspace globs `pnpm-workspace.yaml` declares AHEAD of the tree they match:
* the objectui import under `ui/` (objectstack#25677, wired by #25697). Each
* is excused from the empty-glob refusal above ONLY while
* {@link AWAITING_IMPORT_MARKER} is absent; the moment the import lands, every
* one of them must expand to members or it is the finding it always was. The
* self-test holds the map against the live workspace (every key is a declared
* glob) and against both states of the marker. Retire the map with the import.
*/
export const WORKSPACE_GLOBS_AWAITING_IMPORT = Object.freeze({
ui: 'objectui import (objectstack#25677): the `ui` root manifest itself (`object-ui-monorepo`)',
'ui/packages/*': 'objectui import (objectstack#25677): the @object-ui/* packages',
'ui/apps/*': 'objectui import (objectstack#25677): the Console and the docs site',
'ui/examples/*': 'objectui import (objectstack#25677): objectui example apps',
});

/** The file whose presence ends the excuse: the imported tree's root manifest. */
export const AWAITING_IMPORT_MARKER = 'ui/package.json';

/**
* The globs excused from the empty-glob refusal on the tree at `root`: the
* whole {@link WORKSPACE_GLOBS_AWAITING_IMPORT} key set while the marker is
* absent, nothing once it exists.
*
* @param {string} root
* @returns {Set<string>}
*/
export function awaitingImportGlobs(root) {
return existsSync(join(root, AWAITING_IMPORT_MARKER))
? new Set()
: new Set(Object.keys(WORKSPACE_GLOBS_AWAITING_IMPORT));
}

/**
* [#16649] Every published workspace member's `src/`, as repo-relative prefixes.
*
Expand Down Expand Up @@ -6145,8 +6180,50 @@ function selfTest() {
ok(emptyWorkspaceGlobs({
readGlobs: () => readWorkspaceGlobs(ROOT),
expand: (g) => expandWorkspaceMembers(ROOT, g),
awaiting: awaitingImportGlobs(ROOT),
}).length === 0,
'the LIVE workspace has a glob that expands to nothing — the production run refuses on this too');
// [#25697] The awaiting-import excuse, narrow in every direction: only the
// globs it names, only while the marker is absent, and only globs the live
// workspace really declares (so the map cannot outlive the import unnoticed).
ok(emptyWorkspaceGlobs({
readGlobs: () => ['packages/*', 'ui/packages/*', 'zz-gone/*'],
expand: (g) => (g === 'packages/*' ? ['a'] : []),
awaiting: new Set(['ui/packages/*']),
}).join(',') === 'zz-gone/*',
'an empty glob the awaiting set names is excused; an empty glob it does not name is still reported by name');
ok(emptyWorkspaceGlobs({
readGlobs: () => ['ui/packages/*'],
expand: () => [],
}).join(',') === 'ui/packages/*',
'with no awaiting set the same empty glob is a finding — the excuse is opt-in at the call site, never a default');
{
const liveGlobs = new Set(readWorkspaceGlobs(ROOT));
const keys = Object.keys(WORKSPACE_GLOBS_AWAITING_IMPORT);
ok(keys.every((g) => liveGlobs.has(g)),
`every awaiting-import glob is one pnpm-workspace.yaml declares — stale keys: ` +
`${keys.filter((g) => !liveGlobs.has(g)).join(', ') || 'none'}`);
ok(keys.every((g) => typeof WORKSPACE_GLOBS_AWAITING_IMPORT[g] === 'string' && WORKSPACE_GLOBS_AWAITING_IMPORT[g].length > 0),
'every awaiting-import entry carries a reason');
ok(keys.sort().join() === 'ui,ui/apps/*,ui/examples/*,ui/packages/*',
'the awaiting-import map holds exactly the four ui roots and nothing else');
// Both states of the marker, on a scratch root: absent → the whole key
// set is excused; present → nothing is, and the import commit meets the
// refusal with no further edit here.
const scratch = mkdtempSync(join(tmpdir(), 'dispatcher-awaiting-'));
try {
ok(awaitingImportGlobs(scratch).size === keys.length,
'with the marker absent, every awaiting-import glob is excused');
mkdirSync(join(scratch, 'ui'), { recursive: true });
writeFileSync(join(scratch, 'ui', 'package.json'), '{"name":"object-ui-monorepo","private":true}\n');
ok(awaitingImportGlobs(scratch).size === 0,
'with the marker present, nothing is excused — the excuse voids itself at the import');
} finally {
rmSync(scratch, { recursive: true, force: true });
}
ok(existsSync(join(ROOT, AWAITING_IMPORT_MARKER)) === (awaitingImportGlobs(ROOT).size === 0),
'the live reading agrees with the live marker');
}
// [#17145] The same expansion, shown to be the MANIFEST-filtered one and not
// the raw directory listing. Without this the two could drift apart again
// silently, which is the whole defect: a glob resolving only to category
Expand Down Expand Up @@ -6516,6 +6593,7 @@ function main() {
const emptyGlobs = emptyWorkspaceGlobs({
readGlobs: () => readWorkspaceGlobs(ROOT),
expand: (glob) => expandWorkspaceMembers(ROOT, glob),
awaiting: awaitingImportGlobs(ROOT),
});
if (emptyGlobs.length > 0) {
throw new Error(
Expand Down
7 changes: 7 additions & 0 deletions scripts/check-published-files.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,13 @@ const ROOT_DIR_WATCH_HINTS = [
'packages/triggers/*',
'apps/*',
'examples/*',
// The objectui tree's roots (objectstack#25677). `ui/*` stands for the bare
// `ui` member (objectui's root manifest), spelled with a separator for the
// reason above; the other three match its own `<dir>/*` globs.
'ui/*',
'ui/packages/*',
'ui/apps/*',
'ui/examples/*',
];

/**
Expand Down
48 changes: 34 additions & 14 deletions scripts/check-test-source-alias.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -633,8 +633,21 @@ const WORKSPACE_PARENT_GLOBS = [
'packages/triggers/*',
'apps/*',
'examples/*',
// The objectui tree's package roots (objectstack#25677); they match nothing
// until it is imported under ui/.
'ui/packages/*',
'ui/apps/*',
'ui/examples/*',
];

/**
* Workspace members pnpm-workspace.yaml names as a BARE directory (no `/*`):
* the one package AT that directory, declared by its manifest path so the entry
* keeps the separator the dispatch deriver needs. `ui` is objectui's root
* manifest (objectstack#25677); absent until the import, like the globs above.
*/
const WORKSPACE_BARE_MEMBER_MANIFESTS = ['ui/package.json'];

/** The parent directories those globs enumerate — each glob minus its leaf. */
const WORKSPACE_PARENT_DIRS = WORKSPACE_PARENT_GLOBS.map((glob) => glob.replace(/\/\*$/, ''));

Expand All @@ -650,22 +663,24 @@ function readJson(file) {

function listWorkspacePackages(root) {
const out = [];
const candidates = [];
for (const parent of WORKSPACE_PARENT_DIRS) {
const abs = join(root, parent);
if (!existsSync(abs)) continue;
for (const name of readdirSync(abs)) {
const dir = join(abs, name);
const manifest = join(dir, 'package.json');
if (!existsSync(manifest)) continue;
let json;
try {
json = readJson(manifest);
} catch {
continue;
}
if (!json.name) continue;
out.push({ name: json.name, dir, rel: relative(root, dir), json });
for (const name of readdirSync(abs)) candidates.push(join(abs, name));
}
for (const manifestPath of WORKSPACE_BARE_MEMBER_MANIFESTS) candidates.push(dirname(join(root, manifestPath)));
for (const dir of candidates) {
const manifest = join(dir, 'package.json');
if (!existsSync(manifest)) continue;
let json;
try {
json = readJson(manifest);
} catch {
continue;
}
if (!json.name) continue;
out.push({ name: json.name, dir, rel: relative(root, dir), json });
}
return out;
}
Expand Down Expand Up @@ -3322,7 +3337,7 @@ function selfTest() {
// docblock (#9955). Asserted here rather than left to review because the
// regression is a tidy-up nobody would flag.
battery('the declared population must stay READABLE by the dispatch deriver');
for (const glob of WORKSPACE_PARENT_GLOBS) {
for (const glob of [...WORKSPACE_PARENT_GLOBS, ...WORKSPACE_BARE_MEMBER_MANIFESTS]) {
expect(
glob.replace(/^(?:\.\.?(?:\/|$))+/, '').includes('/'),
`workspace parent ${glob} carries no path separator, so scripts/pm/dispatch-gates.mjs refuses it as too generic and every package under it drops out of the derived gate list`,
Expand All @@ -3346,7 +3361,12 @@ function selfTest() {
// the declaration stays and the live parse becomes its CHECK, in both
// directions, the shape check-published-files.mjs already uses.
battery('the declaration must still BE the workspace (#11510)');
const declaredParents = WORKSPACE_PARENT_GLOBS.map((g) => g.replace(/\/\*+$/, ''));
// A bare-directory member is declared by its manifest path; both spellings
// reduce to the directory pnpm-workspace.yaml names.
const declaredParents = [
...WORKSPACE_PARENT_GLOBS.map((g) => g.replace(/\/\*+$/, '')),
...WORKSPACE_BARE_MEMBER_MANIFESTS.map((m) => m.replace(/\/package\.json$/, '')),
];
const liveParents = readWorkspaceGlobs(REPO_ROOT)
.filter((g) => !isExclusionGlob(g))
.map((g) => g.replace(/\/\*+$/, ''));
Expand Down
Loading
Loading