Skip to content

Commit 117c455

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-23950-checklist-stale-clauses
2 parents 92fd011 + 2e15620 commit 117c455

27 files changed

Lines changed: 978 additions & 44 deletions
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@objectstack/cli': patch
3+
'@objectstack/runtime': patch
4+
---
5+
6+
fix(cli,runtime): a state, output or cache directory that can never be made is refused at once, naming the path and the operating system's code, instead of hanging
7+
8+
Clause-②: no
9+
10+
**What was wrong.** These commands and runtime paths created their directories with Node's recursive `mkdir`. Node's recursive mkdir never returns when a level answers `ENOENT` although its parent exists — what `/proc` and `/sys` answer for a name they do not have. It looped `mkdir("/proc/x")` and `mkdir("/proc")` without end, holding the event loop, so the command printed nothing more and no timeout could fire:
11+
12+
- `os start` with `--home` / `OS_HOME` on such a path hung after its header.
13+
- `os serve` hung the same way on the empty-boot artifact stub under `OS_HOME`, and on the cache of an `http(s):` `OS_ARTIFACT_URL` under `OS_HOME`.
14+
- `os create --dir`, `os init <name>`, `os compile --output`, `os generate` (`--dir`, and `--output` for types, client and migration), `os i18n extract --out` and `os plugin build --out` hung on an output path there.
15+
- `os login` and `os cloud login` never finished writing credentials under a `HOME` there, and the package enable / disable state the packages routes write under `OS_HOME` held the event loop of the host serving them.
16+
17+
**What an operator sees now.** The command exits non-zero at once with the operating system's own error, which names the level that cannot be made:
18+
19+
```text
20+
✗ Cannot create home directory at /proc/os-x: ENOENT: no such file or directory, mkdir '/proc/os-x'
21+
```
22+
23+
Not covered yet: the runtime state file `os serve` writes into `OS_HOME` once it is listening.
24+
25+
Nothing else changes. Every directory these paths made before is still made, every missing level of it; every path that was refused before is still refused, with the operating system's code. The one difference is the path that was never answered at all.

‎.changeset/console-f8267df93aba.md‎

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
"@objectstack/console": minor
3+
---
4+
5+
Console (objectui) refreshed to `f8267df93aba`. Frontend changes in this range:
6+
7+
Derived from the changesets objectui declared over the range — 46 releasing of 46 changesets added across 40 non-merge commits; omitted: 3 commits carrying no changeset (they ship no package code).
8+
9+
- **minor** — **BREAKING** — The page renderer no longer compiles a `kind: 'jsx'` page node as `html`. This completes the retirement of the page kind `jsx` that `@objectstack/spec` 18 made (objectstack-ai/obj… (objectui `f8267df93`)
10+
- **minor** — The page schemas no longer import or attach `@objectstack/spec`'s page-parse judge of a view-data component's retired `api` binding, which objectstack#23784 removed from the spec… (objectui `15d8ca6da`)
11+
- **minor** — Home: "Recently accessed" brings you back to the records and list views you opened, beside the activity feed (objectui#12604). (objectui `f0e760afe`)
12+
- **minor** — The details grid passes a pointer field's `referenceDeletedWhen` to `resolveRecordPointer` (objectui#12489). (objectui `023087f63`)
13+
- **minor** — The record details grid draws a platform-deleted pointer target as a tombstone in the declared option's label (objectui#12489). (objectui `023087f63`)
14+
- **minor** — `resolveRecordPointer` reads `referenceDeletedWhen`, so a pointer pair can say the platform deleted its target (objectui#12489). (objectui `023087f63`)
15+
- **minor** — The console top bar is 48 px (`h-12`) in every frame, the height both approved console artboards draw (objectui#12639). It was 56 px (`h-14`). (objectui `dcd104102`)
16+
- **minor** — List grids show the record a pointer field names, and the Approvals Inbox says who else is waiting (objectui#12608). (objectui `f78efdd1e`)
17+
- **minor** — Locale keys for the console home's "My work" section (objectui#12602), in all ten packs. (objectui `db8ce2f41`)
18+
- **minor** — The console home's "Needs your attention" card becomes the "My work" section of the approved home artboard (objectui#12602), and the pending-drafts banner above the greeting moves… (objectui `db8ce2f41`)
19+
- **minor** — Resolve `@objectstack/*` `18.0.0-next.2` (objectui#12626). (objectui `f62b6ff4e`)
20+
- **minor** — A kanban under a list view puts every record of the view in its column, not the list's first page, and its cards carry the view's first columns (objectui#12619). (objectui `8614705c1`)
21+
- **minor** — **BREAKING** — `page:header` no longer reads `showStar`, ahead of `@objectstack/spec` 18 retiring the key (objectstack#23650). (objectui `608033b82`)
22+
- **minor** — The console home's top half follows the approved home artboard (objectui#12601): a search field in the top bar, a greeting with a one-line work summary, two compact builder chips,… (objectui `6c4d00da0`)
23+
- **minor** — `DefaultLoginPage` passes the translated names of `LoginForm`'s new password reveal toggle, `auth.login.showPasswordText` and `auth.login.hidePasswordText` (objectui#12600). It ke… (objectui `8ed7e5a8f`)
24+
- **minor** — `LoginForm` gains an optional `hideHeader` prop and a password reveal toggle, and the shared auth form styles take the console login artboard's sizes (objectui#12600). (objectui `8ed7e5a8f`)
25+
- **minor** — The console's login page follows the approved "refined centred" artboard (objectui#12600). (objectui `8ed7e5a8f`)
26+
- **minor** — New `auth.login` strings for the console login page, in all ten language packs (objectui#12600): `welcomeTitle`, `welcomeDescription`, `showPasswordText`, `hidePasswordText`, `foo… (objectui `8ed7e5a8f`)
27+
- **minor** — Every wide centred dialog keeps a 1rem gutter on each side of the window from `sm` (640px) up, by one shared class (objectui#12603). (objectui `726d8c9fc`)
28+
- **minor** — A grouped grid now draws one table with one sticky column header. Each group is a 36 px band row between the rows: a chevron, the group value and its count, in one cell that spans… (objectui `619e3f7a4`)
29+
- **minor** — The Studio's Publish and Changes buttons no longer open the pending-changes sheet the same way. (objectui `92c2a6976`)
30+
- **minor** — **A record dialog with no authored layout opens bounded, and draws two fields per row wherever no `columns` is declared** (objectui#12521). Three defaults of `ModalForm` change. N… (objectui `7b5e55893`)
31+
- **minor** — **BREAKING** — The console's list toolbar no longer has a write into the shared view, and the data-source method that write used is retired (objectui#12504). (objectui `3eb3ec138`)
32+
- **minor** — Studio's Add field asks for the label and type before it creates the field, and the field type picker also searches descriptions (objectui#12516). (objectui `1b3b5b102`)
33+
- **patch** — Action-param and bulk-action dialogs draw `date` and `datetime` params with the platform date picker, the same face record forms draw since objectui#12622 (objectui#12642). (objectui `c6b7d8203`)
34+
- **patch** — The cloud-connection panel's "Not connected" card no longer repeats the page subtitle (objectui#12655). (objectui `efa9cb4fa`)
35+
- **patch** — Record page sections: a collapsible section's header is now a real button named by the section title (objectui#12625). It carries `aria-expanded`, is reachable by Tab, and Enter o… (objectui `4be76e030`)
36+
- **patch** — Studio's permission-set editor no longer offers the two delegated-admin switches that `@objectstack/spec` 18 retired (objectui#12406, the objectui half of objectstack#23488). (objectui `a98a23c78`)
37+
- **patch** — Inside an app, the product mark stays in the top bar as the way home, and the app menu gains Back to home, All apps and Settings (objectui#12605). (objectui `a5bb7ac2f`)
38+
- **patch** — Setup › Packaged automation keeps a flow row to the table's height when the platform says why the flow is not armed (objectui#12609). (objectui `8a8bcf3dd`)
39+
- **patch** — Record forms draw `date` and `datetime` fields with the platform date picker instead of the browser's native date input (objectui#12622). (objectui `ca7a4c316`)
40+
- **patch** — The record header draws one primary action and the rest as secondary buttons (objectui#12617). (objectui `ef71a09f9`)
41+
- **patch** — **`marketplace.installedAdditiveNote` is plain text in all ten packs** (objectui#12610). Each value dropped its leading `<strong>Note:</strong>` markup (in that pack's language);… (objectui `f74638c1b`)
42+
- **patch** — **Installed Apps' kernel note is the tooltip of the Uninstall it qualifies** (objectui#12610). The "kernel API is additive only" note was a paragraph at the foot of the list, show… (objectui `f74638c1b`)
43+
- **patch** — **A settings action row draws its label once, on the button** (objectui#12610). The settings renderer's `action_button` row printed the manifest's `label` as the row label and aga… (objectui `f74638c1b`)
44+
- **patch** — Marketplace cards show the whole app name, and the id keeps its package name (objectui#12616). (objectui `07b063249`)
45+
- **patch** — Studio at a narrow width keeps the words that matter readable (objectui#12611). (objectui `a8560b228`)
46+
- **patch** — The list toolbar's Display menu now closes once a layout is chosen (segmented or dropdown switcher), returning focus to the Display button, as Escape already does; the panel's tog… (objectui `79c199692`)
47+
- **patch** — The Studio's Automations rail orders its object groups the way the package's app presents its objects (objectui#12612). The groups used to follow the alphabet of the object labels… (objectui `0a41305b0`)
48+
- **patch** — Metric tiles keep their title readable in narrow tiles. The title now owns the first line (up to two lines, with an ellipsis on the second), the value never wraps and steps down o… (objectui `63d00da3d`)
49+
- **patch** — The pending-changes sheet no longer lists the package stamp as one of the author's own changes (objectui#12594). Expanding a new object draft showed `_packageId` under "Also chang… (objectui `33fbc861d`)
50+
- **patch** — An `lg` or `xl` `ModalForm` dialog keeps a 1rem gutter on each side of the window from `sm` (640px) up (objectui#12593). (objectui `85e8961f2`)
51+
- **patch** — A boolean cell's read-only checkbox is named by its field (objectui#12584). (objectui `7d0c174ba`)
52+
- **patch** — Studio's way home leads back to the Studio landing, and the two links that leave for Setup say so (objectui#12513). (objectui `f5eec6383`)
53+
- **patch** — **The console's New and Edit dialog on a field-heavy object opens bounded on desktop** (objectui#12521). For an object with twelve or more authorable fields, `@objectstack/spec`'s… (objectui `7b5e55893`)
54+
- **patch** — Studio: the draft status chip sits in the same place in every pillar's item header (objectui#12524). "Unpublished draft" in Data, Automations and Interfaces, and Access's "saved a… (objectui `ae923ece2`)
55+
56+
⚠️ 3 of these carry a breaking change: 3 by the author's own breaking annotation in the changeset body — objectui declares no `major` inside a launch window (`scripts/check-changeset-no-major.mjs`). Each is marked **BREAKING** in the list above — read them before compiling the release record.
57+
58+
**In this console build, declared nowhere** — objectui merged 3 commits in this range with no `.changeset/*.md`. The code is inside the pin above and ships here, but nothing upstream declared them, so they appear in no objectui CHANGELOG and in no entry above. Listed by subject rather than counted, because a count cannot tell a dependency bump from a form-behaviour change (objectstack#6174); the upstream gate that would prevent this is objectui#3387.
59+
60+
- _(no changeset)_ fix(live-e2e): the backend cold install writes overrides.vitest so npm 10 can install the published graph (objectui#12649) (#12674) (objectui `d66ceea5a`)
61+
- _(no changeset)_ docs(skills): point two published member counts at their declarations instead of numbers (objectui#12588) (#12591) (objectui `8415d9f11`)
62+
- _(no changeset)_ ci: delete the spec-main-shape-gate copy, the spec shape gate runs in objectstack (objectui#12352) (#12596) (objectui `c6a42be39`)
63+
64+
Clause-②: no
65+
66+
<!-- adr-0087: not-required (no-migration-prescription)
67+
This diff moves `.objectui-sha` and the artefacts that travel with it: this console
68+
changeset, the re-recorded `scripts/sdui-manifest.record.json` and the regenerated
69+
`sdui.manifest.json`, and the re-recorded `packages/sdui-parser/objectui-lockstep.json`.
70+
It adds, removes or renames no ObjectStack-authorable key: no Zod shape, no spec
71+
declaration and no stored `sys_metadata` shape moves in it, so `objectstack migrate meta`
72+
has nothing here to rewrite, and this body carries no FROM/TO prescription of its own.
73+
The range carries three objectui changes their authors annotated breaking, and the
74+
digest marked all three. Each is answered against this tree:
75+
(1) objectui#12486 (`f8267df93`), `@object-ui/types`: `PageNodeSchema.kind` drops `jsx`
76+
and the page renderer stops compiling it as `html`. `@objectstack/spec` here already
77+
refuses page `kind: 'jsx'` at parse (`packages/spec/src/ui/page.zod.ts`), so no page
78+
this tree accepts reaches that arm.
79+
(2) objectui#12627 (`608033b82`), `@object-ui/components`: `page:header` stops reading
80+
`showStar`. `@objectstack/spec` here already retires the key; its conversion is the
81+
registered `page.component.page:header.showStar` entry in
82+
`packages/spec/src/conversions/registry.ts`.
83+
(3) objectui#12504 (`3eb3ec138`), `@object-ui/types` and `@object-ui/data-objectstack`:
84+
`DataSource.updateViewConfig` and its capability gate are removed. No package or example
85+
here depends on any `@object-ui/*` package; the name survives in this tree only in two
86+
test comments and one checklist step, none of which calls it.
87+
Scope of the claim, stated rather than implied: it is a claim about THIS diff, not a
88+
per-entry re-measurement of the upstream declared-breaking entries.
89+
-->
90+
91+
objectui range: `daeb77fc84dd...f8267df93aba`

‎.objectui-sha‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
daeb77fc84dd824e74c95b87cdede85afdb248e7
1+
f8267df93aba8fb09cc1cc0692eadc0c8c5a5e56

‎packages/cli/src/commands/compile.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { loadConfig, namedExportRejectionHints } from '../utils/config.js';
1818
import { refuseUnbuiltStack } from '../utils/stack-provenance-refusal.js';
1919
import { isExtractionRefusal, lowerCallables } from '../utils/lower-callables.js';
2020
import { authoringRuleUnionStack } from '../utils/stack-collections.js';
21+
import { makeDirectorySync } from '../utils/make-directory.js';
2122
import { artifactPackages, runPerPackageAuthoringRules } from '../utils/artifact-packages.js';
2223
import { stackFilterJudge } from '../utils/authoring-filter-judge.js';
2324
import { buildAccessMatrix, diffAccessMatrix } from '@objectstack/lint';
@@ -989,7 +990,9 @@ export default class Compile extends Command {
989990
const artifactDir = path.dirname(artifactPath);
990991

991992
if (!fs.existsSync(artifactDir)) {
992-
fs.mkdirSync(artifactDir, { recursive: true });
993+
// Bounded (#23858): an `--output` directory that can never be made is
994+
// refused at once with the OS's code and the path, never a loop.
995+
makeDirectorySync(artifactDir);
993996
}
994997

995998
const finalBundle: Record<string, unknown> = { ...(result.data as Record<string, unknown>) };

‎packages/cli/src/commands/create.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ import { Args, Command, Flags } from '@oclif/core';
129129
import chalk from 'chalk';
130130
import fs from 'fs';
131131
import path from 'path';
132+
import { makeDirectorySync } from '../utils/make-directory.js';
132133
import {
133134
getCliVersion,
134135
NPM_PACKAGE_NAME_MAX_LENGTH,
@@ -726,8 +727,9 @@ export default class Create extends Command {
726727
console.log('');
727728

728729
try {
729-
// Create directory
730-
fs.mkdirSync(targetDir, { recursive: true });
730+
// Create directory. Bounded (#23858): a `--dir` that can never be made
731+
// is refused at once with the OS's code and the path, never a loop.
732+
makeDirectorySync(targetDir);
731733

732734
// Create files from template
733735
for (const [filePath, contentFn] of Object.entries(template.filesFor(placement))) {

‎packages/cli/src/commands/generate.ts‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import chalk from 'chalk';
55
import { createHash } from 'node:crypto';
66
import fs from 'fs';
77
import path from 'path';
8+
import { makeDirectorySync } from '../utils/make-directory.js';
89

910
// [#16319] `FieldType` is now imported as a VALUE below, beside the other
1011
// `@objectstack/spec/data` helpers — the enum object carries the type of the
@@ -1800,9 +1801,11 @@ async function runMetadataGeneration(
18001801
// never prints a `Created` line for a file that is no longer there.
18011802
const written: string[] = [];
18021803
try {
1803-
// `mkdirSync` answers the first directory it created, or `undefined`
1804-
// when the whole path already existed.
1805-
createdDir = fs.mkdirSync(fullDir, { recursive: true }) ?? undefined;
1804+
// `makeDirectorySync` answers the first directory it created, or
1805+
// `undefined` when the whole path already existed — what the recursive
1806+
// `mkdirSync` it replaces answered, bounded (#23858): a `--dir` that can
1807+
// never be made is refused at once instead of looping.
1808+
createdDir = makeDirectorySync(fullDir);
18061809

18071810
// Write file — the same `content` the parse check above accepted, ⛔ not
18081811
// a re-render: a second call to `generator.generate` would make the
@@ -2224,7 +2227,7 @@ async function runTypesGeneration(configPath: string | undefined, flags: { outpu
22242227
const outPath = path.resolve(process.cwd(), flags.output);
22252228
const outDir = path.dirname(outPath);
22262229
if (!fs.existsSync(outDir)) {
2227-
fs.mkdirSync(outDir, { recursive: true });
2230+
makeDirectorySync(outDir);
22282231
}
22292232
fs.writeFileSync(outPath, content);
22302233
printSuccess(`Generated types at ${flags.output}`);
@@ -2371,7 +2374,7 @@ async function runClientGeneration(configPath: string | undefined, flags: { outp
23712374
const outPath = path.resolve(process.cwd(), flags.output);
23722375
const outDir = path.dirname(outPath);
23732376
if (!fs.existsSync(outDir)) {
2374-
fs.mkdirSync(outDir, { recursive: true });
2377+
makeDirectorySync(outDir);
23752378
}
23762379
fs.writeFileSync(outPath, content);
23772380
printSuccess(`Generated client SDK at ${flags.output} (${timer.display()})`);
@@ -4003,7 +4006,7 @@ async function runMigrationGeneration(configPath: string | undefined, flags: { o
40034006
const outPath = path.resolve(process.cwd(), output);
40044007
const outDir = path.dirname(outPath);
40054008
if (!fs.existsSync(outDir)) {
4006-
fs.mkdirSync(outDir, { recursive: true });
4009+
makeDirectorySync(outDir);
40074010
}
40084011
fs.writeFileSync(outPath, content);
40094012
printSuccess(`Generated migration at ${output} (${timer.display()})`);

‎packages/cli/src/commands/i18n/extract.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Args, Command, Flags } from '@oclif/core';
66
import chalk from 'chalk';
77
import { normalizeStackInput } from '@objectstack/spec';
88
import { loadConfig } from '../../utils/config.js';
9+
import { makeDirectorySync } from '../../utils/make-directory.js';
910
import {
1011
printHeader,
1112
printSuccess,
@@ -796,7 +797,9 @@ export default class I18nExtract extends Command {
796797
process.exit(1);
797798
}
798799

799-
fs.mkdirSync(resolvedOutDir, { recursive: true });
800+
// Bounded (#23858): an `--out` that can never be made is refused at
801+
// once with the OS's code and the path, never a loop.
802+
makeDirectorySync(resolvedOutDir);
800803
let written = 0;
801804
for (const { file, content, keys } of emitted) {
802805
fs.writeFileSync(file, content, 'utf8');

‎packages/cli/src/commands/init.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
isExitSignal,
2121
} from '../utils/format.js';
2222
import { validateScaffold } from '../utils/scaffold-validate.js';
23+
import { makeDirectorySync } from '../utils/make-directory.js';
2324
import { summarizeTree, describeEntry } from 'create-objectstack/created-summary';
2425
import { GENERATOR_SCAFFOLD_TARGETS } from './generate.js';
2526

@@ -1262,7 +1263,10 @@ export default class Init extends Command {
12621263
this.exit(2);
12631264
}
12641265
} else {
1265-
fs.mkdirSync(targetDir, { recursive: true });
1266+
// Bounded (#23858): a target that can never be made — this command
1267+
// run inside `/proc` — is refused at once with the OS's code and the
1268+
// path. Node's recursive mkdir looped on it, printing nothing.
1269+
makeDirectorySync(targetDir);
12661270
}
12671271
} else {
12681272
targetDir = startCwd;

0 commit comments

Comments
 (0)