From d013c327d33b5c61e26394d5ac9905b3b721a8e4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 10:33:18 +0000 Subject: [PATCH 1/5] docs: CONTRIBUTING.md describes this repository and points to AGENTS.md; one pnpm floor; the examples page counts five CONTRIBUTING.md still described the retired protocol-only repository: clone and upstream URLs, issue and discussion links, an internal/planning directory that does not exist, docs trees that do not exist, a bilingual .cn.mdx convention with zero files, and a pnpm >= 8 floor. It now names this repository, sends contributors to AGENTS.md for the rules instead of restating them, and carries the changeset step of the flow that gates a PR. The getting-started prerequisites and the examples Quick Run now say pnpm 10, the floor the workspace enforces, matching README.md and CONTRIBUTING.md. The examples page opens with the five examples examples/ holds. Claude-Session: https://claude.ai/code/session_0115N1oNnQS5WqofZ2DzaT3q Co-authored-by: Claude --- CONTRIBUTING.md | 505 +++++----------------- content/docs/getting-started/examples.mdx | 4 +- content/docs/getting-started/index.mdx | 3 +- 3 files changed, 112 insertions(+), 400 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d533d0f2e7..f4dbca1fca5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,447 +1,158 @@ -# Contributing to ObjectStack Protocol +# Contributing to ObjectStack -Thank you for your interest in contributing to ObjectStack! This guide will help you get started with contributing to the protocol specifications. +Thank you for your interest in contributing to ObjectStack! This repository is the +whole open stack, not only the protocol: the Zod protocol (`packages/spec`), the +microkernel, the runtime, the drivers, plugins and services, the CLI, the client SDK, +the example apps and the documentation site. This guide takes you from a fork to a +pull request. + +**[AGENTS.md](./AGENTS.md) is the rulebook, for human contributors and coding agents +alike.** It holds naming, the Zod-first and contract-first rules, the documentation +guardrails and the changeset rules. This file does not restate them. Where a rule +matters, it points you to AGENTS.md, and if the two ever disagree, AGENTS.md wins. ## 📋 Table of Contents - [Code of Conduct](#code-of-conduct) - [Getting Started](#getting-started) -- [Development Workflow](#development-workflow) -- [Contribution Types](#contribution-types) -- [Coding Standards](#coding-standards) -- [Testing Guidelines](#testing-guidelines) -- [Documentation Guidelines](#documentation-guidelines) +- [Making a Change](#making-a-change) +- [Where Things Live](#where-things-live) - [Pull Request Process](#pull-request-process) - [Community](#community) ## Code of Conduct -We are committed to providing a welcoming and inclusive environment. Please be respectful and professional in all interactions. +We are committed to providing a welcoming and inclusive environment. Please read the +[Code of Conduct](./CODE_OF_CONDUCT.md) and be respectful and professional in all +interactions. ## Getting Started ### Prerequisites -- **Node.js** >= 22.0.0 -- **PNPM** >= 8.0.0 -- **Git** >= 2.0.0 +- **Node.js** 22 or later (`engines.node` in the root `package.json`) +- **pnpm 10**. `corepack enable` installs the exact version the root `package.json` + pins in `packageManager`, and a pnpm 10 you installed yourself switches to that + pinned version inside this repository. pnpm 8 and 9 cannot install this workspace: + pnpm 8 refuses the lockfile, and pnpm 9 stops a frozen install on the `overrides` + that live in `pnpm-workspace.yaml`. +- **Git** ### Initial Setup ```bash -# 1. Fork the repository on GitHub +# 1. Fork objectstack-ai/objectstack on GitHub # 2. Clone your fork -git clone https://github.com/YOUR_USERNAME/spec.git -cd spec +git clone https://github.com/YOUR_USERNAME/objectstack.git +cd objectstack -# 3. Add upstream remote -git remote add upstream https://github.com/objectstack-ai/spec.git +# 3. Add the upstream remote +git remote add upstream https://github.com/objectstack-ai/objectstack.git # 4. Install dependencies +corepack enable pnpm install -# 5. Build the project -pnpm build - -# 6. Run tests -pnpm test -``` - -## Development Workflow - -### 1. Choose What to Work On - -Before starting, review: -- **[PRIORITIES.md](./internal/planning/PRIORITIES.md)** - Current sprint priorities -- **[DEVELOPMENT_ROADMAP.md](./internal/planning/DEVELOPMENT_ROADMAP.md)** - Long-term roadmap -- **[GitHub Issues](https://github.com/objectstack-ai/spec/issues)** - Open issues - -### 2. Create a Branch - -```bash -# Update your main branch -git checkout main -git pull upstream main - -# Create a feature branch -git checkout -b feature/your-feature-name -# or -git checkout -b fix/your-bug-fix -``` - -### 3. Make Your Changes - -Follow the [Coding Standards](#coding-standards) and ensure: -- Changes are minimal and focused -- Code follows existing patterns -- Tests are added/updated -- Documentation is updated - -### 4. Test Your Changes - -```bash -# Run all tests -pnpm test - -# Run tests for specific package -pnpm --filter @objectstack/spec test - -# Build to verify schemas +# 5. Build the workspace (nothing builds it implicitly) pnpm build ``` -### 5. Submit Your Changes - -```bash -# Stage your changes -git add . - -# Commit with descriptive message -git commit -m "feat: add new field type for encrypted data" - -# Push to your fork -git push origin feature/your-feature-name - -# Create Pull Request on GitHub -``` - -## Contribution Types - -### 🔧 Protocol Definitions - -Adding or modifying protocol definitions in `packages/spec/src/`: - -1. **Create Zod Schema** - Always start here -2. **Add JSDoc Comments** - Document with `@description` -3. **Write Tests** - Target 80%+ coverage -4. **Generate Schemas** - Run `pnpm build` -5. **Create Documentation** - Add MDX in `content/docs/references/` - -> **Single Source of Truth — One Zod schema per metadata type.** -> Each metadata type (`view`, `dashboard`, `flow`, `agent`, `tool`, `object`, …) -> has exactly **one** Zod schema under `packages/spec/src/{domain}/`. Do -> **not** re-declare the same shape as a `*.object.ts` projection table — -> that pattern was removed in ADR-0005 (see `docs/adr/0005-…`). Studio -> editing forms and the overlay validator (`resolveOverlaySchema()` in -> `packages/objectql/src/protocol.ts`) both bind to that one schema. -> -> **Runtime opt-in for org overlays** lives in exactly one place: the -> `allowOrgOverride` boolean on the type's entry in -> `DEFAULT_METADATA_TYPE_REGISTRY` -> (`packages/spec/src/kernel/metadata-plugin.zod.ts`). Do not maintain a -> parallel whitelist in runtime code. - -Example: -```typescript -/** - * Represents an encrypted field for storing sensitive data - * @description Provides end-to-end encryption for sensitive information - */ -export const EncryptedFieldSchema = z.object({ - /** Field type identifier */ - type: z.literal('encrypted'), - - /** Encryption algorithm (default: AES-256-GCM) */ - algorithm: z.enum(['aes-256-gcm', 'rsa-4096']).default('aes-256-gcm'), - - /** Key management strategy */ - keyManagement: z.enum(['user', 'organization', 'system']).default('organization'), -}); -``` - -### 📚 Documentation - -- **Concepts** - High-level explanations in `content/docs/concepts/` -- **Guides** - How-to tutorials in `content/docs/guides/` -- **References** - API documentation in `content/docs/references/` -- **Specifications** - Protocol specs in `content/docs/specifications/` - -### 🐛 Bug Fixes - -1. Create an issue describing the bug (if not exists) -2. Reference the issue in your PR -3. Add regression tests -4. Update documentation if behavior changes - -### ✨ Examples - -Add working examples in `examples/`: -- Include `README.md` with setup instructions -- Provide `objectstack.config.ts` configuration -- Add `CHANGELOG.md` for version history - -## Coding Standards - -### Naming Conventions - -**CRITICAL**: Follow these naming conventions strictly: - -#### Configuration Keys (TypeScript Properties) -Use `camelCase`: -```typescript -{ - maxLength: 100, - defaultValue: 'none', -} -``` - -#### Machine Names (Data Values) -Use `snake_case`: -```typescript -{ - name: 'project_task', - object: 'account', - field: 'first_name', -} -``` - -### Schema Definition Pattern - -```typescript -import { z } from 'zod'; - -/** - * Schema description - * @description Detailed explanation - */ -export const MySchema = z.object({ - /** Property description */ - propertyName: z.string().describe('Property description'), - - /** Another property */ - anotherProperty: z.number().optional().describe('Optional property'), -}); - -export type MyType = z.infer; -``` - -### File Organization - -``` -packages/spec/src/ -├── data/ # ObjectQL - Data Protocol -│ ├── field.zod.ts -│ ├── object.zod.ts -│ └── validation.zod.ts -├── ui/ # ObjectUI - UI Protocol -│ ├── app.zod.ts -│ ├── view.zod.ts -│ └── theme.zod.ts -├── system/ # ObjectOS - System Protocol -│ ├── manifest.zod.ts -│ ├── plugin.zod.ts -│ └── driver.zod.ts -├── ai/ # AI Protocol -│ ├── agent.zod.ts -│ └── model.zod.ts -└── api/ # API Protocol - ├── envelopes.zod.ts - └── requests.zod.ts -``` - -## Testing Guidelines - -### Test Coverage - -- **Target**: 80%+ code coverage -- **Location**: Co-located `*.test.ts` files -- **Framework**: Vitest - -### Test Structure - -```typescript -import { describe, it, expect } from 'vitest'; -import { MySchema } from './my-schema.zod'; - -describe('MySchema', () => { - describe('validation', () => { - it('should accept valid data', () => { - const result = MySchema.safeParse({ - propertyName: 'valid value', - }); - expect(result.success).toBe(true); - }); - - it('should reject invalid data', () => { - const result = MySchema.safeParse({ - propertyName: 123, // wrong type - }); - expect(result.success).toBe(false); - }); - }); - - describe('type inference', () => { - it('should infer correct TypeScript types', () => { - type MyType = z.infer; - const data: MyType = { - propertyName: 'value', - }; - expect(data.propertyName).toBe('value'); - }); - }); -}); -``` - -## Documentation Guidelines - -### MDX Documentation - -Create documentation in `content/docs/references/` matching the source structure: - -```markdown ---- -title: MySchema -description: Schema description for SEO and navigation ---- - -# MySchema - -Brief description of what this schema represents. - -## Overview - -Detailed explanation of the schema's purpose and use cases. - -## Schema Definition - -\`\`\`typescript -import { MySchema } from '@objectstack/spec'; - -const config = { - propertyName: 'value', -}; - -const validated = MySchema.parse(config); -\`\`\` - -## Properties - -### propertyName - -- **Type**: `string` -- **Required**: Yes -- **Description**: Description of this property - -## Examples - -### Basic Usage - -\`\`\`typescript -const basic = { - propertyName: 'simple value', -}; -\`\`\` - -### Advanced Usage - -\`\`\`typescript -const advanced = { - propertyName: 'complex value', - anotherProperty: 42, -}; -\`\`\` - -## Related - -- [RelatedSchema](./related-schema) -- [AnotherSchema](./another-schema) -``` - -### Bilingual Support - -Provide both English and Chinese versions: -- English: `my-schema.mdx` -- Chinese: `my-schema.cn.mdx` +The README's [Hack on the framework](./README.md#hack-on-the-framework) section +covers the rest: building the Console, running an example app with `pnpm dev`, and +how long a first build and a full test run take. + +## Making a Change + +1. **Start from an issue.** Pick one from the + [issue tracker](https://github.com/objectstack-ai/objectstack/issues), or open one + that describes the bug or the proposal before you write code. + [ROADMAP.md](./ROADMAP.md) and [docs/NORTH-STAR.md](./docs/NORTH-STAR.md) show + where the project is going. +2. **Branch off an up-to-date `main`, one branch and one pull request per issue**, + with the issue number in the branch name: + + ```bash + git fetch upstream + git checkout -b fix/issue-1234-short-slug upstream/main + ``` + + Working in a checkout that coding agents also use? Give every task its own + `git worktree` instead of switching branches in a shared tree. AGENTS.md + (Prime Directive #11 and *Multi-agent working discipline*) explains why, and what + a worktree does not isolate. +3. **Make the change, following AGENTS.md.** Read its *Prime Directives* before a + structural change, and its *Context Routing* table for the rules of the path you + are editing. +4. **Test what your change reaches**, not only the file you edited: + + ```bash + pnpm --filter @objectstack/ test # one package + pnpm --filter @objectstack/ typecheck # a type-check-covered package + pnpm turbo run test --affected # every package your branch reaches + pnpm lint # the only style authority (no formatter) + ``` + + Did you touch `packages/spec`? Then regenerate its checked-in artifacts before you + push. The steps are in AGENTS.md under *Touched `packages/spec`?*. +5. **Add a changeset when the change publishes.** Run `pnpm changeset` and commit the + `.changeset/*.md` file it writes. A bug fix in a released package takes a `patch` + changeset. AGENTS.md's *Post-Task Checklist* (step 3) states the whole rule: which + bump a change takes, the `Clause-②` declaration, and the migration a breaking + change must carry. + +## Where Things Live + +| Path | What it holds | +|:---|:---| +| `packages/spec/src/` | The protocol: Zod schemas, types and constants. AGENTS.md lists the domains. | +| `packages/`, `packages/plugins/`, `packages/services/`, … | The kernel, runtime, drivers, plugins, services, CLI and SDK. The README's package directory has them all. | +| `content/docs/` | The documentation site's pages, in trees such as `getting-started/`, `concepts/`, `data-modeling/`, `ui/`, `automation/`, `api/` and `deployment/`. Preview with `pnpm docs:dev`. | +| `content/docs/references/` | Generated from the Zod schemas by `packages/spec/scripts/build-docs.ts`. Never edit it by hand. | +| `content/docs/releases/` | Written at release time from changesets. Never edit it in a code pull request. | +| `examples/` | The example apps. The README's *Examples* table describes each one. | +| [ARCHITECTURE.md](./ARCHITECTURE.md) | Design details, the plugin lifecycle and the dependency graph. | + +AGENTS.md (*Documentation Guardrails*) has the rules for every docs path, including what a +new page needs before it shows up in the navigation. ## Pull Request Process ### Before Submitting -- [ ] All tests pass (`pnpm test`) -- [ ] Code builds successfully (`pnpm build`) -- [ ] Documentation is updated -- [ ] Naming conventions are followed -- [ ] JSDoc comments are complete -- [ ] No unrelated changes included +- [ ] The tests and type-check for what your change reaches pass +- [ ] `pnpm lint` passes +- [ ] Documentation is updated where behaviour changed +- [ ] A changeset is included if the change publishes +- [ ] No unrelated changes are included - [ ] If the PR changes the auth/audience **defaults** or the **accept/reject behaviour** of the unauthenticated surface, label it `needs:pack-smoke` — that runs the packed-install smoke on the merge preview before you merge, instead of finding out at release time. -### PR Checklist - -Use this template for your PR description: - -```markdown -## Description -Brief description of changes - -## Type of Change -- [ ] Bug fix -- [ ] New feature -- [ ] Breaking change -- [ ] Documentation update - -## Changes Made -- Item 1 -- Item 2 - -## Testing -- [ ] Unit tests added/updated -- [ ] All tests passing -- [ ] Manual testing completed - -## Documentation -- [ ] JSDoc comments added -- [ ] MDX documentation created/updated -- [ ] Examples provided - -## Checklist -- [ ] Zod schema follows naming conventions -- [ ] Comprehensive JSDoc comments with @description -- [ ] Unit tests with 80%+ coverage -- [ ] Documentation with examples -- [ ] JSON schema generated successfully -- [ ] All existing tests pass -``` +### Opening the Pull Request + +Push your branch to your fork and open a pull request against `main`. Its first line +names the issue it fixes (`Fixes #1234`). The body says what changed, why, and how you +verified it. ### Review Process -1. **Automated Checks** - CI/CD runs tests and builds -2. **Code Review** - Maintainers review your code -3. **Feedback** - Address review comments -4. **Approval** - At least one maintainer approval required -5. **Merge** - Maintainers will merge when ready +1. **Automated checks:** CI runs the repository gates, the type-check, the tests and the + builds. +2. **Code review:** maintainers review your change. +3. **Feedback:** address the review comments. +4. **Merge:** maintainers land the pull request through the merge queue. ## Community -### Communication Channels - -- **GitHub Discussions** - General questions and discussions -- **GitHub Issues** - Bug reports and feature requests -- **Pull Requests** - Code contributions - -### Getting Help - -- Review [PLANNING_INDEX.md](./internal/planning/PLANNING_INDEX.md) for documentation navigation -- Check [ARCHITECTURE.md](./ARCHITECTURE.md) for system design -- Read [QUICK_START_IMPLEMENTATION.md](./QUICK_START_IMPLEMENTATION.md) for implementation examples - -### Recognition - -Contributors will be: -- Listed in release notes -- Mentioned in the CHANGELOG -- Credited in documentation (where applicable) +- **[GitHub Discussions](https://github.com/objectstack-ai/objectstack/discussions)** — + questions and ideas +- **[GitHub Issues](https://github.com/objectstack-ai/objectstack/issues)** — bug reports + and feature requests +- **[Documentation](https://objectstack.ai/docs)** — the published docs site ## License By contributing, you agree that your contributions will be licensed under the -Apache License, Version 2.0. - ---- - -**Questions?** Open a [GitHub Discussion](https://github.com/objectstack-ai/spec/discussions) - -**Need Help?** Check the [documentation](./content/docs/) or ask in discussions +Apache License, Version 2.0 (see [LICENSE](./LICENSE) and [LICENSING.md](./LICENSING.md)). **Thank you for contributing to ObjectStack! 🚀** diff --git a/content/docs/getting-started/examples.mdx b/content/docs/getting-started/examples.mdx index f3bd457e6e5..be43a3526cc 100644 --- a/content/docs/getting-started/examples.mdx +++ b/content/docs/getting-started/examples.mdx @@ -6,7 +6,7 @@ description: Run and explore the built-in example applications to learn ObjectSt import { CheckSquare, Building2, BarChart3, Server } from 'lucide-react'; -The monorepo ships three ready-to-run examples in `examples/` that progressively demonstrate ObjectStack features — from a simple Todo app to a full CRM and a kitchen-sink reference. For a larger external enterprise reference, see the [HotCRM repository](https://github.com/objectstack-ai/hotcrm). +The monorepo ships five examples in `examples/`. Three are ready-to-run apps that progressively demonstrate ObjectStack features — from a simple Todo app to a full CRM and a kitchen-sink reference — and each has its own `pnpm dev:*` script. The other two demonstrate a shape rather than a feature set: `app-multi-package` is one release artifact carrying two packages that share a namespace ([below](#a-project-is-a-multi-package-artifact)), and `embed-objectql` uses ObjectQL as a plain library, with no kernel and no plugins. For a larger external enterprise reference, see the [HotCRM repository](https://github.com/objectstack-ai/hotcrm). **Read these as reference, then build your own with an agent.** Each example is the same @@ -50,7 +50,7 @@ The fastest way to explore all examples at once: ```bash git clone https://github.com/objectstack-ai/objectstack.git cd objectstack -pnpm install # Node 22+, pnpm 8+ (corepack enable) +pnpm install # Node 22+, pnpm 10 (corepack enable) pnpm build # REQUIRED — nothing builds the workspace implicitly pnpm dev:showcase # or: pnpm dev:todo / pnpm dev:crm ``` diff --git a/content/docs/getting-started/index.mdx b/content/docs/getting-started/index.mdx index 4cc8b019709..a48e7949cf3 100644 --- a/content/docs/getting-started/index.mdx +++ b/content/docs/getting-started/index.mdx @@ -173,7 +173,8 @@ scaffolds a project with its own TypeScript toolchain and npm scripts. **pnpm and TypeScript versions only matter for the framework monorepo.** If you clone [`objectstack-ai/objectstack`](https://github.com/objectstack-ai/objectstack) itself (to contribute, or to run the bundled examples), you'll additionally need -pnpm 8+ (`corepack enable` pulls the pinned 10.x) — and the repo builds against +pnpm 10 (`corepack enable` installs the version the repo pins in `packageManager`; +pnpm 8 and 9 cannot install the workspace) — and the repo builds against TypeScript 6.x. From 500e82db9b3095df3a1d7eec0660052c47d1b416 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 10:37:29 +0000 Subject: [PATCH 2/5] fix(cli): os dev --help names OS_PORT; os init --no-install names the package manager it resolved `os dev --port`'s help read "overrides $PORT" while the command reads OS_PORT first and PORT only as the legacy alias. It now names both, in that order. `os init` resolved its package manager only inside the install branch, so a --no-install run kept the literal 'npm' initialiser and printed `npm install` / `npx objectstack` even under --package-manager pnpm or a pnpm invocation. The package manager is now resolved before that branch, by the same rule (flag, then the invoking package manager, then npm). Claude-Session: https://claude.ai/code/session_0115N1oNnQS5WqofZ2DzaT3q Co-authored-by: Claude --- ...22156-cli-port-help-and-init-next-steps.md | 10 ++ packages/cli/src/commands/dev.ts | 2 +- .../init-next-steps-package-manager.test.ts | 95 +++++++++++++++++++ packages/cli/src/commands/init.ts | 10 +- 4 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 .changeset/22156-cli-port-help-and-init-next-steps.md create mode 100644 packages/cli/src/commands/init-next-steps-package-manager.test.ts diff --git a/.changeset/22156-cli-port-help-and-init-next-steps.md b/.changeset/22156-cli-port-help-and-init-next-steps.md new file mode 100644 index 00000000000..b0bb195cd3b --- /dev/null +++ b/.changeset/22156-cli-port-help-and-init-next-steps.md @@ -0,0 +1,10 @@ +--- +"@objectstack/cli": patch +--- + +`os dev --help` names `OS_PORT` for `--port`, and `os init --no-install` names the package manager it resolved in its Next steps. + +Clause-②: no + +- **`os dev --port`.** The help text read `Server port (overrides $PORT)`, though `os dev` reads `OS_PORT` first and `PORT` only as its legacy alias. It now reads `Server port (overrides $OS_PORT; $PORT is the legacy alias)`. Which variables are read, and in what order, is unchanged. +- **`os init --no-install`.** The package manager was resolved only when an install ran, so a `--no-install` run always printed `npm install` and `npx objectstack …`, even with `--package-manager pnpm` or when invoked through pnpm. It is now resolved before the install step, the same way as before (the `--package-manager` flag, then the invoking package manager, then npm), so `os init my-app --no-install --package-manager pnpm` prints `pnpm install` and `pnpm exec objectstack …`. A run with no flag, invoked through npm or `npx`, still prints `npm install`: the scaffold supports npm, yarn and bun as well as pnpm. diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index 1c2f5e6156d..5ffd2e6d463 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -228,7 +228,7 @@ export default class Dev extends Command { description: 'Kernel logger level forwarded to `serve` (overrides $OS_LOG_LEVEL / $LOG_LEVEL; default `warn`). One of: debug | info | warn | error | fatal | silent.', options: ['debug', 'info', 'warn', 'error', 'fatal', 'silent'], }), - port: Flags.string({ char: 'p', description: 'Server port (overrides $PORT)' }), + port: Flags.string({ char: 'p', description: 'Server port (overrides $OS_PORT; $PORT is the legacy alias)' }), // #16804 — developer-supplied TLS, forwarded to the `serve` child. Declared // through the shared contract so the two commands cannot drift on the flag // names, the prose, or what half a pair means. diff --git a/packages/cli/src/commands/init-next-steps-package-manager.test.ts b/packages/cli/src/commands/init-next-steps-package-manager.test.ts new file mode 100644 index 00000000000..4558ea327aa --- /dev/null +++ b/packages/cli/src/commands/init-next-steps-package-manager.test.ts @@ -0,0 +1,95 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * `os init --no-install` names, in its printed Next steps, the package manager + * the run resolved: the `--package-manager` flag first, then the invoking + * package manager (`npm_config_user_agent`), then `npm`. + * + * The run used to resolve that package manager only inside its install branch. + * Under `--no-install` the branch never ran, the variable kept its literal + * `'npm'` initialiser, and the Next steps told a user who had passed + * `--package-manager pnpm` (or invoked the CLI through pnpm) to run + * `npm install` and `npx objectstack`. + * + * The third case is the control: a run invoked through npm, with no flag, still + * names npm. The scaffold deliberately supports npm, yarn and bun as well as pnpm + * (`engines.pnpm`, never a `packageManager` stamp; `init.test.ts` pins that), so + * `npm install` is the right instruction for an npm run, and this file must not + * read as "never npm". + * + * In-process (`Init.run` against the package root, the `doctor-*.test.ts` + * pattern) and `--no-install`, so nothing is spawned and nothing is installed. + */ + +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import Init from './init.js'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +/** `packages/cli` — the oclif root the real command is loaded against below. */ +const CLI_ROOT = path.resolve(HERE, '..', '..'); + +/** Written as `\x1b`, never as the byte itself, so `grep` keeps reading this file as text. */ +const SGR = /\x1b\[[0-9;]*m/g; + +const NPM_UA = 'npm/10.9.4 node/v22.22.0 linux x64 workspaces/false'; +const PNPM_UA = 'pnpm/10.31.0 npm/? node/v22.22.0 linux x64'; + +describe('os init --no-install: the Next steps name the resolved package manager', () => { + let tmp: string; + let cwdSpy: ReturnType; + + beforeEach(() => { + tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'os-init-next-steps-pm-')); + cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(tmp); + }); + + afterEach(() => { + cwdSpy.mockRestore(); + vi.unstubAllEnvs(); + fs.rmSync(tmp, { recursive: true, force: true }); + }); + + /** Run `os init` in `tmp` and return the printed lines from `Next steps:` on, colour stripped. */ + async function nextSteps(argv: string[], userAgent: string): Promise { + vi.stubEnv('npm_config_user_agent', userAgent); + const logs: string[] = []; + const logSpy = vi.spyOn(console, 'log').mockImplementation((...a: unknown[]) => { + logs.push(a.join(' ')); + }); + try { + await Init.run(argv, { root: CLI_ROOT }); + } finally { + logSpy.mockRestore(); + } + const out = logs.join('\n').replace(SGR, ''); + const at = out.indexOf('Next steps:'); + expect(at, `no "Next steps:" block was printed:\n${out}`).toBeGreaterThan(-1); + return out.slice(at); + } + + it('--package-manager pnpm, invoked through npm: the flag wins', async () => { + const steps = await nextSteps(['probe-app', '--no-install', '--package-manager', 'pnpm'], NPM_UA); + expect(steps).toMatch(/^\s*pnpm install\s+# Install dependencies$/m); + expect(steps).toMatch(/^\s*pnpm exec objectstack validate\b/m); + // Word boundary: the text `pnpm install` itself contains the substring `npm install`. + expect(steps).not.toMatch(/\bnpm install\b/); + expect(steps).not.toMatch(/\bnpx objectstack\b/); + }); + + it('no flag, invoked through pnpm: the invoking package manager', async () => { + const steps = await nextSteps(['probe-app', '--no-install'], PNPM_UA); + expect(steps).toMatch(/^\s*pnpm install\s+# Install dependencies$/m); + expect(steps).not.toMatch(/\bnpm install\b/); + }); + + it('no flag, invoked through npm: still npm (control)', async () => { + const steps = await nextSteps(['probe-app', '--no-install'], NPM_UA); + expect(steps).toMatch(/^\s*npm install\s+# Install dependencies$/m); + expect(steps).toMatch(/^\s*npx objectstack validate\b/m); + }); +}); diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index ccd65107b09..c4c2caca967 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -1219,7 +1219,14 @@ export default class Init extends Command { let installSucceeded = false; let installAttempted = false; - let chosenPm: 'npm' | 'pnpm' | 'yarn' | 'bun' = 'npm'; + // Resolved before the install branch, not inside it: the Next-steps block + // below names this package manager whether or not an install ran. Resolved + // only under `--install`, a `--no-install` run kept a literal `'npm'` and + // printed `npm install` even for `--package-manager pnpm` or a pnpm-invoked + // run — the hardcoded answer `create-objectstack`'s Next steps are pinned + // against (`scaffold-next-steps-pm.test.ts`). + const chosenPm: 'npm' | 'pnpm' | 'yarn' | 'bun' = + (flags['package-manager'] as 'npm' | 'pnpm' | 'yarn' | 'bun' | undefined) ?? detectPackageManager(); try { // 1. Create package.json if missing @@ -1271,7 +1278,6 @@ export default class Init extends Command { // Install dependencies if (flags.install) { - chosenPm = (flags['package-manager'] as typeof chosenPm | undefined) ?? detectPackageManager(); printStep(`Installing dependencies with ${chosenPm}...`); installAttempted = true; const { execSync } = await import('child_process'); From 6be33e23cc1699e1919dfa501d02595e253748e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 10:46:53 +0000 Subject: [PATCH 3/5] docs: the README's data curl signs in first; the tutorial's clean validate is clean README.md's first data call answered 401 UNAUTHENTICATED against a scaffolded project: data endpoints run under the same permissions as the UI. It now signs in as the dev admin `os dev` seeds and sends the session cookie, the same two calls the scaffolded README and Your First Project show. build-with-claude-code.mdx promises every example passes `os validate` verbatim and prints a clean transcript, but its `description` field sat on no view, so every run printed a field-no-consumers warning. The view file now declares the create and edit form that places it, the prompt asks for that form, one paragraph says why, and the transcript carries the two summary lines the command prints today. Claude-Session: https://claude.ai/code/session_0115N1oNnQS5WqofZ2DzaT3q Co-authored-by: Claude --- README.md | 10 +++++++-- .../build-with-claude-code.mdx | 21 +++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7d9816bd07d..a87fc210cef 100644 --- a/README.md +++ b/README.md @@ -136,10 +136,16 @@ export const Ticket = ObjectSchema.create({ }); ``` -The REST API exists the moment the object does — no controllers to write: +The REST API exists the moment the object does — no controllers to write. It +runs under the same permissions as the UI, so a data call needs a session: sign +in once as the dev admin `os dev` seeds on an empty database, then call it. ```bash -curl http://localhost:3000/api/v1/data/support_desk_ticket +curl -c cookies.txt -X POST http://localhost:3000/api/v1/auth/sign-in/email \ + -H "Content-Type: application/json" \ + -d '{"email":"admin@objectos.ai","password":"admin123"}' + +curl -b cookies.txt http://localhost:3000/api/v1/data/support_desk_ticket ``` In the browser, the typed client SDK and React hooks (`useQuery`, `useMutation`, diff --git a/content/docs/getting-started/build-with-claude-code.mdx b/content/docs/getting-started/build-with-claude-code.mdx index f10dbace8d0..86b7de0e5f4 100644 --- a/content/docs/getting-started/build-with-claude-code.mdx +++ b/content/docs/getting-started/build-with-claude-code.mdx @@ -96,8 +96,8 @@ language: > description, a `priority` select (low/normal/high/urgent) and a `status` select > (open/pending/resolved/closed). Add a **Resolve** action that only shows on > tickets that aren't already resolved or closed. Add a list view (subject, -> status, priority) with an "Open tickets" filter, and an app with a Support nav -> group. Run `npm run validate` when you're done. +> status, priority) with an "Open tickets" filter, a form for entering tickets, +> and an app with a Support nav group. Run `npm run validate` when you're done. You don't specify field types in TypeScript, wire barrel exports, or remember the CEL scoping rules — the agent does, because the skills told it how. @@ -198,6 +198,16 @@ export const TicketViews = defineView({ sort: [{ field: 'priority', order: 'desc' }], }, }, + form: { + type: 'simple', + data, + sections: [ + { + label: 'Ticket', + fields: [{ field: 'subject' }, { field: 'description' }, { field: 'priority' }, { field: 'status' }], + }, + ], + }, }); ``` @@ -238,6 +248,11 @@ passes an unregistered one, the `os dev` boot log warns fails when clicked (the [two paths](/docs/ui/actions#give-it-server-behavior--two-paths) a handler takes). +The view file also declares the create and edit **form**, and the form is where the +long-text `description` is entered and shown. The list columns leave it out, so the +form is the only metadata that names it. A field that no view, form, flow, formula or +action names gets an advisory `field-no-consumers` warning from `os validate`. + The agent also **exports each new file from its directory's barrel** — the object from `src/objects/index.ts`, and the action, view and app from `src/actions/index.ts`, `src/views/index.ts` and `src/apps/index.ts`. The @@ -313,6 +328,8 @@ Once it's clean: Data: 2 Objects 6 Fields UI: 1 Apps 1 Views 1 Actions + Logic: 0 Flows + Security: 0 Positions 0 Permissions Runtime: 3 plugins ``` From 45eef017857e214fb8c8d926ca7130340aee673a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 10:59:08 +0000 Subject: [PATCH 4/5] docs, test: name the tutorial form's section; give the in-process init test a 60s budget check-docs-section-name requires a teaching example's form section to carry a `name` (its i18n anchor). The init Next-steps test now takes the 60s budget the in-process doctor tests use: the first oclif load in a busy worker ran past vitest's 5s default. Claude-Session: https://claude.ai/code/session_0115N1oNnQS5WqofZ2DzaT3q Co-authored-by: Claude --- .../docs/getting-started/build-with-claude-code.mdx | 1 + .../commands/init-next-steps-package-manager.test.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/content/docs/getting-started/build-with-claude-code.mdx b/content/docs/getting-started/build-with-claude-code.mdx index 86b7de0e5f4..e5b53a739c8 100644 --- a/content/docs/getting-started/build-with-claude-code.mdx +++ b/content/docs/getting-started/build-with-claude-code.mdx @@ -203,6 +203,7 @@ export const TicketViews = defineView({ data, sections: [ { + name: 'ticket', label: 'Ticket', fields: [{ field: 'subject' }, { field: 'description' }, { field: 'priority' }, { field: 'status' }], }, diff --git a/packages/cli/src/commands/init-next-steps-package-manager.test.ts b/packages/cli/src/commands/init-next-steps-package-manager.test.ts index 4558ea327aa..7fcd9d274a1 100644 --- a/packages/cli/src/commands/init-next-steps-package-manager.test.ts +++ b/packages/cli/src/commands/init-next-steps-package-manager.test.ts @@ -18,7 +18,9 @@ * read as "never npm". * * In-process (`Init.run` against the package root, the `doctor-*.test.ts` - * pattern) and `--no-install`, so nothing is spawned and nothing is installed. + * pattern, with its 60 s budget: the first oclif load in a busy worker took + * longer than vitest's 5 s default) and `--no-install`, so nothing is spawned + * and nothing is installed. */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; @@ -79,17 +81,17 @@ describe('os init --no-install: the Next steps name the resolved package manager // Word boundary: the text `pnpm install` itself contains the substring `npm install`. expect(steps).not.toMatch(/\bnpm install\b/); expect(steps).not.toMatch(/\bnpx objectstack\b/); - }); + }, 60_000); it('no flag, invoked through pnpm: the invoking package manager', async () => { const steps = await nextSteps(['probe-app', '--no-install'], PNPM_UA); expect(steps).toMatch(/^\s*pnpm install\s+# Install dependencies$/m); expect(steps).not.toMatch(/\bnpm install\b/); - }); + }, 60_000); it('no flag, invoked through npm: still npm (control)', async () => { const steps = await nextSteps(['probe-app', '--no-install'], NPM_UA); expect(steps).toMatch(/^\s*npm install\s+# Install dependencies$/m); expect(steps).toMatch(/^\s*npx objectstack validate\b/m); - }); + }, 60_000); }); From daed4bb9a1d897a0e86df0061f6064d0d8752fa2 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 11:47:52 +0000 Subject: [PATCH 5/5] fix(cli), docs: os start --help names OS_PORT; the scaffold and app-todo pnpm floors; a non-exhaustive consumer list `os start --port`'s help read "overrides $PORT" while the command reads OS_PORT first, the same drift as `os dev`. It now names OS_PORT with PORT as the legacy alias, and keeps "default 3000". The changeset names it. your-first-project.mdx said pnpm 8+ for a scaffolded project whose package.json declares the pnpm floor 10.15; it now says 10.15+. examples/app-todo/README.md said pnpm 8+ for the monorepo; it now says pnpm 10, as the other on-ramp pages do. The tutorial's field-no-consumers sentence named five consumers as if the list were complete; it now says they are among the ones the rule counts. Claude-Session: https://claude.ai/code/session_0115N1oNnQS5WqofZ2DzaT3q Co-authored-by: Claude --- .changeset/22156-cli-port-help-and-init-next-steps.md | 3 ++- content/docs/getting-started/build-with-claude-code.mdx | 5 +++-- content/docs/getting-started/your-first-project.mdx | 2 +- examples/app-todo/README.md | 2 +- packages/cli/src/commands/start.ts | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.changeset/22156-cli-port-help-and-init-next-steps.md b/.changeset/22156-cli-port-help-and-init-next-steps.md index b0bb195cd3b..096eb4aecae 100644 --- a/.changeset/22156-cli-port-help-and-init-next-steps.md +++ b/.changeset/22156-cli-port-help-and-init-next-steps.md @@ -2,9 +2,10 @@ "@objectstack/cli": patch --- -`os dev --help` names `OS_PORT` for `--port`, and `os init --no-install` names the package manager it resolved in its Next steps. +`os dev --help` and `os start --help` name `OS_PORT` for `--port`, and `os init --no-install` names the package manager it resolved in its Next steps. Clause-②: no - **`os dev --port`.** The help text read `Server port (overrides $PORT)`, though `os dev` reads `OS_PORT` first and `PORT` only as its legacy alias. It now reads `Server port (overrides $OS_PORT; $PORT is the legacy alias)`. Which variables are read, and in what order, is unchanged. +- **`os start --port`.** The same drift: the help text read `Port to listen on (overrides $PORT, default 3000)`, though `os start` reads `OS_PORT` first as well. It now reads `Port to listen on, default 3000 (overrides $OS_PORT; $PORT is the legacy alias)`. The port behaviour is unchanged. - **`os init --no-install`.** The package manager was resolved only when an install ran, so a `--no-install` run always printed `npm install` and `npx objectstack …`, even with `--package-manager pnpm` or when invoked through pnpm. It is now resolved before the install step, the same way as before (the `--package-manager` flag, then the invoking package manager, then npm), so `os init my-app --no-install --package-manager pnpm` prints `pnpm install` and `pnpm exec objectstack …`. A run with no flag, invoked through npm or `npx`, still prints `npm install`: the scaffold supports npm, yarn and bun as well as pnpm. diff --git a/content/docs/getting-started/build-with-claude-code.mdx b/content/docs/getting-started/build-with-claude-code.mdx index 07e3642d1f1..9da7c9016a1 100644 --- a/content/docs/getting-started/build-with-claude-code.mdx +++ b/content/docs/getting-started/build-with-claude-code.mdx @@ -251,8 +251,9 @@ fails when clicked (the The view file also declares the create and edit **form**, and the form is where the long-text `description` is entered and shown. The list columns leave it out, so the -form is the only metadata that names it. A field that no view, form, flow, formula or -action names gets an advisory `field-no-consumers` warning from `os validate`. +form is the only metadata that names it. A field that no consumer names (a view, a form, a +flow, a formula or an action, among the others the rule counts) gets an advisory +`field-no-consumers` warning from `os validate`. The agent also **exports each new file from its directory's barrel** — the object from `src/objects/index.ts`, and the action, view and app from diff --git a/content/docs/getting-started/your-first-project.mdx b/content/docs/getting-started/your-first-project.mdx index fe35e76927c..8dc1367c172 100644 --- a/content/docs/getting-started/your-first-project.mdx +++ b/content/docs/getting-started/your-first-project.mdx @@ -22,7 +22,7 @@ The two paths produce identical projects. | Tool | Minimum | Check | |:---|:---|:---| | **Node.js** | 22+ | `node --version` | -| A package manager | npm 9+ / pnpm 8+ / yarn / bun | `npm --version` | +| A package manager | npm 9+ / pnpm 10.15+ / yarn / bun | `npm --version` | That's all. A standalone project does **not** need pnpm, TypeScript pre-installed, or a database server — the default template runs on an in-memory driver, and diff --git a/examples/app-todo/README.md b/examples/app-todo/README.md index cc78fb40a22..b911ed97a59 100644 --- a/examples/app-todo/README.md +++ b/examples/app-todo/README.md @@ -98,7 +98,7 @@ examples/app-todo/ ## 💡 How to Run ### Prerequisites -- Node.js 22+ and pnpm 8+ +- Node.js 22+ and pnpm 10 (`corepack enable`) - Install from monorepo root: `corepack enable && pnpm install` ### Type Check diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 19777ea426c..e371596371c 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -91,7 +91,7 @@ export default class Start extends Command { static override flags = { // Server - port: Flags.integer({ char: 'p', description: 'Port to listen on (overrides $PORT, default 3000)' }), + port: Flags.integer({ char: 'p', description: 'Port to listen on, default 3000 (overrides $OS_PORT; $PORT is the legacy alias)' }), ui: Flags.boolean({ description: 'Mount the Console portal at /_console/ (default: true so you can install marketplace apps)', default: true,