Commit Graph
48 Commits
Author SHA1 Message Date
chenos 4183756204 docs(skills): use application homepage for next steps (#10772) 2026-10-12 11:31:42 +08:00
chenos 693e43d86b ci(skills): distribute v3 skills on source updates (#10769)
* ci(skills): distribute v3 skills on source updates

* refactor: rename app plugin development skill
2026-10-12 11:17:39 +08:00
chenos fafbebd403 docs(skills): guide offline application deployment over SSH (#10759) 2026-10-12 08:53:55 +08:00
chenos 4dee74270c fix(skills): use beta channel for NocoBase 3 installations (#10756) 2026-10-12 07:54:10 +08:00
PiEgg 71d7607458 docs: require incremental migration changes now that packages are published (#10660) 2026-10-10 05:48:17 -04:00
Junyi 98e79a5a65 feat(lifecycle): durable examples (#10644)
* docs(lifecycle): add examples and docs

* refactor(workflow): prune workflow

* docs(workflow): prune workflow
2026-10-10 14:50:27 +08:00
PiEgg c796cb909b feat(app-client)!: remove settings and dev route surfaces, Hub and AI employee (#10642) 2026-10-10 02:02:13 -04:00
nocobase-studio[bot]andPiEgg a6758ecb55 PM-119: Complete v3 repository reference migration (#10597)
Co-authored-by: PiEgg <marksz@teamsz.xyz>
2026-10-09 04:40:25 -04:00
PiEgg a695d1987f chore(v3): use public npm and unify workflow naming (#10588) 2026-10-09 03:33:32 -04:00
Zeke Zhang bde3b877e0 PM-75: Prepare client plugins for shadcn installation (#582) 2026-10-09 01:11:22 -04:00
dc91aab7fd PM-74: Use package-local subpath imports in registry sources (#576)
* fix(registry): use package-local subpath imports

Co-authored-by: Project lead <agent+studio-project-lead@studio.noreply>

* docs(registry): preserve package imports in plugin guidance

* fix(registry): validate installed components in packed plugins

* test(create-plugin): isolate offline dependency metadata

---------

Co-authored-by: 张中合 <zhonghe.zeke@gmail.com>
Co-authored-by: Project lead <agent+studio-project-lead@studio.noreply>
2026-10-08 23:17:41 -04:00
YANG QIA bc1e83ffac feat(plugins): update the plugins, templates and UI Library for the new runtime (#553)
* Update the plugins, templates and UI Library for the new runtime

Device authorization, scoped API keys, service accounts, user invitations and preferences, secrets-backed credentials, the inbox and the cn package across the existing plugins, examples and the three templates, with the UI Library's auth, inbox, authorization and component items.

* Declare the UI Library site's favicon

* Use the NocoBase favicon on the UI Library site

* Fix generated-app shell tests and the Hub's user deletion test

Inline the authentication client in the templates' Vitest config so its guards share the test's react-router, and update the Hub test to the users plugin's new copy and spinner label.
2026-10-08 01:10:33 +08:00
YANG QIA 37c8d20aa6 feat(app): add the runtime, CLI client and agent runner for agent-run applications (#552)
Encrypted secrets, a command manifest derived from the API document, environment-variable manifests, sample data and on-demand Apps in the runtime, plus the new agent protocol, application CLI client, agent runner and Docker App Host packages.
2026-10-07 23:54:20 +08:00
chenos e23c66d4ad feat(create-plugin): prepare generated client plugins for page tests, and point page tests at app-testing/client (#535)
* docs: point page tests at app-testing/client, and test the Skill's page example with real translations

AGENTS.md and the AGENTS.md create-plugin generates name @nocobase/app-testing/client for page tests. The plugin-development Skill's page test example renders with the strict runtime from @nocobase/i18n/testing and the plugin's locale file instead of a mocked t, which let a missing or misspelt key pass. The example keeps its mocked services until @nocobase/app-testing is published.

* feat(create-plugin): prepare generated client plugins for page tests, and render the Skill's page test with renderWithApp

The Skill's settings page test renders inside a client application with
renderWithApp(), registers the real AuthorizationClient through services and
answers its permissions request with answerApi(), and shows the locale keys
the page needs, so it runs as written. A plugin create-plugin generates with
client code gets app-testing and the jsdom test packages, and a vitest.config.ts
that runs tests/client/ under jsdom; its AGENTS.md describes page tests in a
section of their own, including the plugins option. AGENTS.md no longer calls
renderWithApp()'s toaster the real one.
2026-10-06 19:12:02 +08:00
PiEgg 0b933b39c3 feat: generate the OpenAPI document and serve Swagger UI (#533)
Generates an OpenAPI 3.1 document for every application at GET /api/swagger and serves Swagger UI at GET /api/swagger/docs to signed-in users and API keys. Every hand-written /api route is declared or explicitly hidden, data endpoints are documented per field, Better Auth's endpoints are merged, routes behind runtime dispatchers are registered generically, and pnpm openapi:check enforces the declarations in CI. All changed packages carry changesets.
2026-10-05 09:23:03 +08:00
PiEgg 21d274cc71 refactor: move every existing /api route onto the HTTP API specification (#532)
Moves every /api route in the repository onto the HTTP API specification: camelCase plugin namespaces, standard methods and slash-separated custom methods, { data } / { data, meta } responses, the standard error body with reason and domain, permission before validation, side-effect-free GETs, Repository errors that declare their own status, save-time validation of write grants, duplicate route detection at start, and optional global API limits in config.yml. All changed packages carry changesets.
2026-10-04 16:39:20 +08:00
chenos 463a7a8f38 feat(app-testing): test applications on any dialect, with a portability check and every server dialect in CI (#525)
* chore(db-testing): fail tests that choose a dialect outside the database packages

pnpm db-tests:check reads every test and test helper outside the database
packages and fails on a dialect package import, ':memory:', dialect: 'sqlite',
PRAGMA, sqlite_master or a trigger. A test whose subject is a dialect says so
with a db-test-portability marker and a reason; template tests, which ship to
users, are exempted in the script instead. The app-server, app-cli and
template tests still to be ported with @nocobase/app-testing are listed as
pending, and the check fails once an entry is no longer needed. It runs in
pnpm check and in the Quality workflow next to the dependency checks.

* feat(db-testing): select every package that tests with db-testing through test:db --all

* feat(db-testing): run db-testing tests on every server dialect

* feat(create-plugin): generate database tests on db-testing

* test(create-plugin): keep the generated-test assertion clear of the portability check

* fix(db-testing): clean up a partial create, drop Oracle test users before 23ai, and run one provisioner contract everywhere

`createSqlTestDatabaseProvisioner()` drops what earlier create statements made when a later one fails, since the caller never receives the drop() that would; a failing first statement is left alone, because the name may belong to something that predates the run.

The Oracle provisioner drops a test user through a PL/SQL block that ignores only ORA-01918, instead of `drop user if exists`, which only 23ai accepts, and grants the materialized view and synonym privileges a migration may need.

`describeTestDatabaseProvisioner()` treats `listProvisioned` as optional throughout and drops the stale-run database in a finally, so a failed assertion leaves nothing behind. PostgreSQL and MySQL run it in place of their own provisioner tests.

* chore(db-testing): report every stale exemption and catch the SQLite driver in the portability check

A listed file or a marker that no longer covers a violation is reported, and so is a file both marked and listed, so every kind of exemption only shrinks. New rules catch a `better-sqlite3` or `sqlite3` import, a knex `client` naming either, and a dialect package imported by a name built at run time; the PRAGMA rule matches SQL rather than prose that mentions it. The `config init` test's marker reads as a sentence.

* test: take the pending database tests' databases from @nocobase/db-testing

Fifteen of the eighteen test files the portability check still listed as pending no longer choose a dialect. app-server's collections artifact, doctor, provider and task-plan tests, its repository routes and app-cli's db command tests run on databases provisioned by @nocobase/db-testing, one per connection they name, instead of SQLite files and in-memory databases. The examples template's article, analytics, numeric, permission-seed and workflow tests do the same; its articles migration test is a describeMigration. The cases whose subject is SQLite itself move to files marked sqlite-only or dialect-specific: a dry run not creating a missing SQLite file, and the driver loaded for a configured dialect. The examples template's external CRM test stays on its SQLite stand-in and is exempted in the check, since a template's tests ship to users and carry no marker.

PENDING keeps only the three templates' app-server tests, which move with @nocobase/app-testing.

* fix(db): only read in a dry run of rollback and repair

A migrator's rollback and repair, and a seeder's repair, took the task lock, ensured the history table and, for rollback, upgraded legacy checksums before looking at dryRun. On a database no task had run on, `nocobase db rollback --dry-run` (and redo, which goes through it) and `db repair --dry-run` therefore created the history and lock tables while reporting that nothing changed. SQLite hid it: a dry run against a database file that does not exist yet takes a separate read-only path, and every server database exists.

A dry run now reads history the way history() does, treating a missing table as empty history, and takes no lock. The migrator and seeder integration suites assert that these dry runs create nothing on every dialect, and the app-cli dry-run test no longer tolerates bookkeeping tables.

* feat(app-cli): let a bound test command load a configuration file of its own

bindAppCommand() takes configPath and hands it to the application's runtime the way APP_CONFIG_FILE is, so a command's test can run against a configuration it wrote, such as one naming a test database.

* feat(app-plugin-authentication): sign in from other packages' tests through a ./testing entry

signIn() signs in through the application's own sign-in route and returns a session whose fetch sends requests as that user, so a test can exercise the real authentication and authorization chain instead of an x-test-user header or a hand-written authenticationToken. DEFAULT_ADMIN_CREDENTIALS names the administrator the default seed creates.

* fix(db): keep in-process task lock claims per connection, not per connection name

Two Database Managers in one process each have a connection called main on databases of their own; one migrating while the other did failed with TaskLockBusyError although they never shared a database. The claims are now kept by the Database Connection a migration connection was made from, so the same connection still fails fast while another one under the same name proceeds; managers on the same database still take turns through the lock row. db-testing drops the queue that ran its test databases' migrations and seeds one after another to avoid the error.

* feat(app-testing): start an application on test databases of its own

@nocobase/app-testing/server's createTestApp() starts an application through its own standalone server on databases provisioned by @nocobase/db-testing, through the configuration file a deployment's own config.yml goes through, and installs it on start; createAppTest() is the Vitest fixture around it. ./cli carries @nocobase/app-cli/testing and bindTestAppCommand(), which points the application a command opens at a test configuration. Both re-export the layer below, so an application's or a plugin's tests depend on this package alone.

* test(templates): run the application tests on app-testing and check authorization through a real session

The three templates' application server tests start the template on test databases written by createTestAppConfig() instead of SQLite files, and the examples template's database tests take their API from @nocobase/app-testing/server, which every template now depends on for development. The portability check's pending list is empty and goes away; a template's SQLite-only test stays in EXEMPT.

The examples template signs in through its own sign-in route as the administrator and as a seeded proposal engineer, and checks that the sales confidentiality restriction leaves confidential quotes out of the engineer's list and in the administrator's, and that they appear once the restriction is no longer assigned. The nocobase-app-development Skill and AGENTS.md describe testing through the whole application.

* test: take plugin and example test fixtures from @nocobase/app-testing alone

Plugins, examples and the create-plugin generator import database fixtures
from @nocobase/app-testing/server and the command runner from
@nocobase/app-testing/cli instead of @nocobase/db-testing and
@nocobase/app-cli/testing, and depend on @nocobase/app-testing alone.
The database test portability check now fails when a test in a plugin, an
example or a template imports either lower layer directly; libraries and
tools keep using them. AGENTS.md, the db-testing README and the
app-development commands reference describe the single entry.

* fix: select app-testing users in test:db --all and keep each Skill's test entry its own

`pnpm test:db <dialect> --all` selected packages by a declared
@nocobase/db-testing, so once plugins, examples and templates moved to
@nocobase/app-testing the nightly Database tests workflow would have
stopped running them on PostgreSQL and MySQL. It now selects packages that
are or declare either one.

The command-authoring block shared by the application and plugin Skills
ended with the Test section, which now names a different runner in each:
@nocobase/app-testing/cli for an application, and the released
@nocobase/app-cli/testing in the global plugin Skill. The section moves
after the shared block in both.
2026-10-03 10:38:59 -04:00
chenos 3117923e80 fix(create-app): skip the better-sqlite3 build its prebuilt binaries make unnecessary (#517)
better-sqlite3 13 ships prebuilt binaries for Linux (glibc and musl), macOS
and Windows on x64 and arm64 and loads them first, so its implicit node-gyp
rebuild compiles nothing there. It still needs make, though, and fails the
entire pnpm install on a machine without a C++ toolchain.

Record it as false in the repository's allowBuilds, create-app's
ALLOWED_BUILDS and the deployment dist/pnpm-workspace.yaml. Drop create-app's
automatic pnpm rebuild after a failed load: while allowBuilds skips the
package, rebuild skips it too. Report the platform and how to compile the
driver instead.
2026-09-30 19:23:13 -04:00
Drol e77641ba3c feat(queue): rebuild @nocobase/queue on BullMQ 6.3.6 (#516)
* feat(queue): rebuild @nocobase/queue on BullMQ 6.3.6

Replace the @boringnode/queue wrapper with a per-application QueueService
whose producers, consumers and managers are bound to named queues. A
queueBackend of redis (or a registered BullMQ backend factory) runs on
BullMQ's public Queue and Worker APIs under a hash-tagged prefix; inMemory
runs in the process and persists unfinished jobs to a state file at
shutdown. Both implementations share one consumer layer and one contract
test suite.

- app-server: QueueServiceProvider, queueServiceToken and AppQueueConfig
  replace QueueProvider and its tokens; the queue migration source and
  planAppRuntimeDatabaseTasks are removed; queue.jobs is deprecated,
  ignored and reported once per plugin
- templates: compose QueueServiceProvider, declare memory and redis keys,
  drop the @/jobs alias
- create-plugin: remove the server.jobs capability
- examples: rewrite the queue example on the new API
- docs, Skills and AGENTS.md describe the new contract

* docs(jobs): make JobExecutor the default for background work

The previous commit rewrote every Queue Job reference onto the new queue
API, but most of what those jobs did belongs to @nocobase/jobs now.

- create-plugin: server.jobs generates a JobExecutor job and the provider
  that owns the plugin's executor instead of being removed
- plugin and application development Skills: choose JobExecutor for
  one-off work, ScheduleExecutor for recurring work, Scheduler when
  administrators track it, and a queue only for delays, priorities,
  deduplicating IDs, batches, rate limits and fan-out; jobs receive
  services by closure
- Scheduler Skill, evals and docs: hand a target's lengthy work to a
  JobExecutor with the occurrence's execution record as the reference,
  and let the job decide its terminal outcome; describe Scheduler's own
  jobs backend instead of the removed queue connection
- deployment and Notification Skills: cover the jobs and queue backends
  instead of a queue manager

* refactor(queue): select the implementation with adapter

queueBackend used to pick both the implementation and a BullMQ backend
factory. A configuration now selects the implementation with
`adapter: 'inMemory' | 'redis'`, and the optional `queueBackend` only
names a factory registered with `registerBackend()` for the redis
adapter, defaulting to BullMQ's Redis backend. A registered factory
interprets its own connection, so `connection` is required only on the
Redis backend. `inMemory` is no longer a reserved factory name.
2026-09-30 11:46:47 -04:00
Zeke ZhangandClaude Opus 5.5 7534fb6b99 refactor(templates): add shadcn/ui primitives on demand with an embedded shadcn/ui skill (#464)
* docs(skills): route frontend work through an embedded shadcn/ui skill

The application development Skill now carries the shadcn/ui skill from
shadcn 4.21.0, unchanged, under references/frontend/shadcn/, and
references/frontend/references/shadcn.md says what to read there and
where an application departs from it: `pnpm exec shadcn`, no `apply` or
`--preset`, overlays as child routes, labels above inputs, and the
English built into a few primitives translated.

- Primitives are added on demand with `yes n | pnpm exec shadcn add`,
  which keeps installed primitives from being overwritten and lets a
  non-interactive run finish, then formatted with Prettier.
- styling.md, form.md and the other topic references link to the skill
  instead of restating it; the worked example names the primitives each
  file needs on an **Add first** line.
- The frontend handbook rework: the projects worked example split into
  one document per file, shell.md, the capture tool guide and the
  reviewer prompts.
- Both .prettierignore files skip the vendored skill so a shadcn upgrade
  can replace it wholesale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(app-template-default): ship only the primitives the shell uses

Primitives are added with the shadcn CLI when a page first needs one,
so every application, template and plugin starts from the same CLI
output. The shadcn/ui skill embedded in the development Skill replaces
the reference gallery.

- Remove client/pages/reference/ with its tests, the 49 primitives that
  neither the shell nor the shipped compositions import, and
  use-mobile.ts. The template keeps button, calendar, dialog,
  dropdown-menu, input, label, popover, select, spinner, table, toast
  and tooltip.
- Drop the devDependencies only those primitives used (recharts, cmdk,
  embla-carousel-react, input-otp, react-resizable-panels,
  @shadcn/react) and add cn, which registry primitives now import.
- toast.tsx names its close button through actions.close, as dialog.tsx
  and spinner.tsx translate theirs; primitive-labels.test.tsx fails
  when an update brings the registry's English back.
- DataTable takes showSelectedCount, so a table without row selection
  hides "0 of N row(s) selected".
- Add the page test harness and application locale coverage tests; the
  client routes test pins page grants by their authz resource id.
- AGENTS.md and CLAUDE.md send UI work to ui-workflow.md, list the
  shipped primitives, and describe adding the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(templates): ship only the primitives Hub and Examples use

Hub and Examples follow Default: primitives are added with the shadcn
CLI when a page first needs one, and the shadcn/ui skill embedded in the
development Skill replaces the reference gallery.

- Remove client/pages/reference/ with its tests and use-mobile.ts from
  both templates. Hub keeps the twelve primitives its shell and
  compositions use; Examples also keeps badge, card, field, separator,
  skeleton, textarea, toggle and toggle-group for its example pages.
- Drop recharts, cmdk, embla-carousel-react, input-otp,
  react-resizable-panels and @shadcn/react, which no template imports
  now, and add cn, which registry primitives import.
- Carry over Default's toast close label, DataTable showSelectedCount,
  the page harness, locale coverage and primitive label tests, and the
  AGENTS.md and CLAUDE.md guidance, keeping each template's own route
  grants and wording.
- Add the changeset for all three templates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): name the primitives each template ships

The frontend handbook said the template ships twelve primitives, which
holds for Default and Hub but not for Examples. shadcn.md now lists the
eight Examples adds, and frontend-dev.md and example.md refer to the
primitives the shell uses instead of a count. Add the changeset for the
embedded shadcn/ui skill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): open records in place, add the back button and the dashboard template

A review of a customer-service application built from the handbook found records opening over the wrong page,
a row's Edit opening the drawer as well, a back button among the page actions, an unaligned table in a card and
unsortable date columns. The handbook now says a record opens over the page the user is on (I9), declares the
drawer under every page that opens it, gives a row's Edit its own route, puts BackButton above the title (L6),
makes date and number columns sortable (T1.8), and adds a dashboard template (T5) with a worked example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(templates): add BackButton and keep the page count on one line

BackButton is the way back from a page below another one: a link above the heading that leads to the parent
route with the query string and replaces the history entry, as closing a route overlay does. It stands on its
own, without PageHeader. DataTablePagination gives the page count a minimum width, so the Chinese label no
longer wraps. RouteChildPage and the UI Library describe leaving by the back link instead of breadcrumbs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): show the success path in the unsaved-changes example

A generated application hit this exact bug: the edit dialog's save
cleared neither the dirty flag that beforeClose reads nor the form's
report, so closing after a successful save asked to discard the just
saved changes.

The overlay.md section 2.5 example now carries the whole flow: a stable
onDirtyChange callback, an EditProjectBody whose onSubmitted clears the
flag before onSaved and close, and the imports that go with it. The
frontend handbook's Common mistakes list gains the matching entry.

* fix(templates): keep the Switch and pie chart in proportion in the compact theme

* docs(app-skills): compare the whole design at acceptance and require consistent navigation icons

The acceptance review's D classification was scoped to the design's numbered
criteria, so a design's structural declarations — routes, navigation entries and
their icons, permissions, the component list and states — were only compared
with the code when they happened to be criteria. A trial feature lost two menu
icons this way and the acceptance pass did not catch it.

D now compares the whole design file with the code; the design template requires
every declaration in sections 2–4 to map to a criterion or be marked explicitly
as not a criterion; and the review checklist plus page.md require navigation
icons to be consistent within a group.

* docs(app-skills): open a record's detail under the page that opens it

I9 covered drawers and dialogs only, so a page that referenced a record whose
detail is a page (a payment list's expense number) linked into the other
module's route: the page the user was on disappeared, the menu highlight moved,
and Back landed elsewhere. The page version of "the same drawer over another
page" was undocumented, and page.md told every record page to hard-code `to`,
which blocks reusing one page module under several parents.

I9 now covers a covering child page as well, child-routes.md gains "The same
detail page over another page", page.md limits `to` to a page with one parent,
and the common-mistakes entry names the detail page next to the overlay URL.

* fix(templates): make BackButton a muted text link

* refactor(templates): group the DataTable components in one directory

* docs(app-skills): show a cut-off table cell's full content on hover

Guideline T1.11 requires a table cell that is too narrow for its content to show the full content on hover. table.md gives the pattern: the cut-off content in a Popover whose trigger opens on hover, since a Tooltip does not work well here. A cell does not wrap and the table widens to fit it, so the pattern applies only where a column limits its width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(app-skills): treat the worked example as a reference, not code to copy

Agents copied the projects example verbatim, carrying its names, fields and fixed values into features they did not fit. The frontend handbook now says the examples show the rules rather than the feature to build, and names the projects-domain values to decide again. example.md and styling.md no longer tell an agent to copy the files a task depends on; only session-expired-alert.tsx and use-url-search.ts, which are shared infrastructure, are still copied unchanged.

The acceptance review also checks the other direction of design conformance: names and copy keys from the projects domain, fields, columns, filters or actions the design does not declare, and example values that do not fit the feature's data are reported as D issues.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(app-skills): end a cut-off cell in an ellipsis and reveal it in a tooltip

A generated articles table clipped long titles mid-character and showed "release-notes" as "elease-note": the title used line-clamp in a cell that never wraps, and the tags put truncate on Badge, a flex element that shows no ellipsis and loses both ends of its centered text.

Guideline T1.11 now requires the ellipsis and a reveal on hover only when the content is cut off. table.md shows text and links, the name column included, in a Tooltip whose onOpenChange cancels it when the value fits; the tooltip trigger leaves a Link a link, which a Popover trigger would turn into a button. Styled content such as a Badge stays in a Popover, since a badge is unreadable on the inverted tooltip. It also says where truncate works and that line-clamp does not belong in a cell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(templates): line DataTable up with a card by itself

A generated analytics page put a DataTable in a CardContent with px-0, as the handbook's "Table in a card" said, but not the cell padding that went with it. Its own border then sat on the card's edge, a strip was left below it, and the text started to the left of the card's title. The rule lived in four places and was still only half applied, because it asked the agent to pick a different component and hand-write selector classes.

DataTable now does it: inside a CardContent it drops its frame, reaches the card's edges and pads its first and last cells with --card-spacing, and a CardContent still given px-0 keeps working. The handbook builds a card's short list from DataTable with plain headers and no pagination, the dashboard example uses it, and guideline T1 says it covers list pages while a short list in a card follows T5.3, which is where the design had picked up sorting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(app-skills): record every review checklist item in the review record

Both reviews of a generated content application were non-independent, since the environment could not start subagents, and neither checked T5.3: the design review raised only B1-B4 and S1-S4, and the acceptance review widened a clipped card without asking why the table sorted. The prompts already said to go through the checklist item by item, but the record kept only the items that failed, so a skipped item looked the same as a passed one.

The review record now has a "Review checklist" section with one row per checklist item: pass, fail with the issue it raised, or not applicable with the reason. Both prompts and the checklist's own introduction say to fill it in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(app-skills): validate forms on submit, not as a field loses focus

A field error flashed for a frame whenever a dialog was closed without saving. Guideline T3.3 validated a field when it lost focus, so the handbook and its examples set useForm's mode to 'onTouched'; clicking Cancel, the close button or the backdrop takes the focus off the field first, and the error rendered while the dialog was fading out.

T3.3 now validates on submit, and a form closed without submitting shows no error. The handbook and the four examples leave mode at the default 'onSubmit', and it explains why the blur-based modes do not fit. After a failed submission a field is still revalidated as it changes, so its error clears once it is corrected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(templates): let a child page inside another one cover it whole

A covering page under a tab of a record's page can only render through the tab's outlet, inside the record page's RouteChildPage. That layer both positioned and scrolled, so the nested layer resolved inset-0 against it and scrolled out of sight with its content: in the recruitment application, after the candidate page had been scrolled 292px, the offer page opened at top -240 and showed only its footer over the candidate's details.

RouteChildPage is now two elements, the way the layout's content area is: the outer one positions and never scrolls, the inner one scrolls, with overscroll-contain so scrolling stops at the layer. A layer rendered inside another one anchors to that layer's outer element and covers it whole. Inside another layer it also makes everything around it inert up to that layer's scrolling element — the record page's back link, header and tab bar — not only its own siblings. The layer carries data-slot='route-child-page'; the props are unchanged.

The three templates and the UI Library registry item carry the same file. The templates' AGENTS.md add that an overlay a page's header opens on a page with tabs is declared under every tab and linked through the current tab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(app-skills): open a page header's overlays over the tab being shown

A review of the recruitment application found a record page whose header opened its edit dialog at /candidates/80/edit while the user was on /candidates/80/interviews. The handbook said a record opens over the page the user is on, but not that on a page with tabs that is the tab, and its two examples — the drawer with its stacked edit dialog, and the record page with tabs — never met. A link with a bare `edit` resolves against the route that renders it, not the URL on screen, and the header belongs to the page's route: the dialog replaced the tab in the page's outlet, and closing it went to the page's URL, which redirected to the default tab.

- Guidelines T2.1, I1, I6 and I9 define the view the user is on, the tab included, and I9 gives the test: an overlay's URL minus its own segments is the URL it was opened from, and closing returns there with no redirect.
- child-routes.md declares the header's overlays under every tab through customerHeaderRoutes(tab) inside customerDetailRoutes(owner), links the header through the tab read from the URL, and has each tab pass the page's context on to its outlet. page.md explains what a relative link resolves against, with a table of the cases.
- Going back restores the page underneath exactly (L6): links carry its whole query string down, a view below another one names its own parameters apart (table.md), and the way back removes them with withoutParams (example/url-search.md). child-routes.md no longer advises dropping the current page's filters when linking to another page's record, which lost them on the way back.
- example/detail-page-tabs.md is the complete page, a tab, a test that opens the header's "Edit" from every tab and goes back, and the route test helper; its code was compiled, linted and tested inside the default template.
- The review checklist, overlay.md, testing.md, frontend-dev.md and the run and design templates check overlays opened from a view other than the first one a page shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(templates): add DataTable and DatePicker from the UI Library on demand

The templates no longer ship DataTable or DatePicker, or the calendar,
select and table primitives only they used. Both are UI Library items
now, added with `shadcn add @nocobase/data-table` or
`@nocobase/date-picker` when a page first needs one. Examples keeps
select and table for its own pages, and Hub keeps react-day-picker,
which app-plugin-hub requires as a peer. The dataTable and datePicker
locale keys stay, so an item added later is translated at once.

- ui-library publishes data-table: DataTable and its column header,
  pagination and view menu as one item of four files under
  client/components/data-table/, with a preview demo and the
  dropdown-menu, select and table primitives it renders with.
- date-time-picker declares the calendar, popover and react-day-picker
  that the date-picker.tsx it installs imports; without calendar in the
  templates, installing it alone no longer compiled.
- ui-library gets Vitest. The DataTable and DatePicker tests move there
  from the templates and render the registry sources against the
  preview's primitives; Quality runs them on every pull request and the
  UI Library workflow before it publishes.
- The development Skill adds the items on the worked example's Add
  first lines and says which of the CLI's package.json changes to undo;
  the upgrade Skill covers an application that still has the old copies.
- The pending changesets that described a data-table directory in the
  templates now describe what ships.

* refactor(templates): drop the unused Typography components

client/components/typography.tsx held the class strings from the shadcn
Typography guide for hand-written long-form text. Nothing in the
templates or the plugins imported it, and article content usually comes
from Markdown or rich text, which these components cannot style. The
templates' AGENTS.md and the development Skill no longer list it.

* feat(ui-library): publish BackButton and keep it preinstalled in the templates

Make the UI Library the source of truth for BackButton, as it already is
for PageHeader and the route overlays.

- ui-library: add the back-button component item (back-button.tsx, its
  registry entry, a Navigation preview demo, and a test that renders it in
  the strict runtime built from the components README), document it in the
  components README with its navigation.back key, and use it in the
  RouteChildPage demo instead of a hand-written back link.
- templates: keep client/components/back-button.tsx as an exact copy,
  checked by tests/scripts/template-ui-library.test.mjs, and name
  BackButton among the UI Library components in AGENTS.md. The component
  itself is unchanged, and each template keeps its own back-button test,
  which is the one check that the template's own locales carry the key.
- MAINTAINING.md and USAGE.md list back-button among the preinstalled
  components, and MAINTAINING.md records navigation.back as the one
  exception to item-prefixed keys: it predates the item and pairs with
  the templates' navigation.breadcrumb.
- The development Skill's styling reference now says which composed
  components are preinstalled from the UI Library and which are the
  template's own, instead of saying the CLI cannot add any of them, and
  the plugin Skill lists back-button among the items a plugin copies.

* feat(ui-library): add a ConfirmDialog item built on alert-dialog

ConfirmDialog confirms one action in an alert dialog the page opens from
its own state. It runs the action: while a returned promise is pending
the confirm button shows a spinner and holds the focus, both buttons are
disabled and Escape is ignored; resolving closes the dialog with
onOpenChange(false, 'confirm'), and a rejection or a returned
{ error, retryable } keeps it open with the error inside it. Cancel
holds the initial focus, and focusAfterConfirm names where focus goes
after a success, when the opener has often gone with its record.

A result is reported to the onOpenChange of the latest render, and one
that arrives after the caller closed or unmounted the dialog is dropped,
so a handler whose close meant cancel is not called with stale state.
A ref, not render state, keeps two clicks from running the action twice.

The preview gains the alert-dialog and spinner primitives, a demo with a
slow, a failing and a refused delete, and the item's tests render it in
the strict i18n runtime built from the README's Translations table.

* docs(app-skills): fix the frontend guidance the PR review found wrong

- The toast examples in complete-button.md and public-switch.md, and the
  401 rule in api.md, used actionProps, left over from the Base UI toast
  API; the toaster's option is action: { label, onClick }.
- ui-workflow.md's development step still said to copy the worked
  example, as did frontend-dev.md and i18n.md in passing; they now say to
  write the feature's files from its design and copy only what a
  document calls shared infrastructure. i18n.md no longer lists the extra
  shared keys twice with different wording.
- The customer page test in detail-page-tabs.md assumed a mocked i18n
  client and queried keys; it renders in the strict test runtime with
  the application's locale files and queries the wording. The document
  also says detail/edit.tsx has to be written before the test imports it,
  with the customers.form keys copy.md does not list, and no longer claims
  the test checks what only the route test can.
- project-form.md and list-page.md name the select primitive they
  import, and server-table.md says what it needs and names
  data-table/index.tsx.
- The end-to-end guidance and the screenshot tool say what a Hub
  application sets up first, since the Hub template has no Playwright.
- shadcn.md no longer claims the application installs CLI 4.21.0.
- The docs site's child page example leaves by BackButton, says why a
  deeper outlet sits beside the layer as the component now behaves, and
  its shadcn add example no longer offers to overwrite the translated
  dialog primitive.
- form.md's create page is the frame to follow, not to copy.

* docs(app-skills): require RouteChildPage for a child route that is a page of its own

A generated application declared its article editor as a child route of a
dashboard and of a list and returned a bare PageContainer, so the editor
rendered at the parent's Outlet below the dashboard instead of covering it.
The handbook implied the rule without stating it.

- child-routes.md, page.md, form.md and frontend-dev.md state that only tab
  content renders inline and a page of its own returns RouteChildPage,
  including a form page shared by several parents.
- testing.md adds a covering child page test: the parent's heading sits in
  an inert element, which fails for a page rendered inline.
- The template AGENTS.md files carry the rule, since they are always read.
- The UI Library README and the docs site place the parent's Outlet at the
  end of its PageContainer, as the handbook does.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:10:19 -04:00
PiEgg ec4b76498c feat(i18n): add a real i18n runtime for component tests (#510)
Add @nocobase/i18n/testing with createTestI18nRuntime and TestI18nProvider.
The runtime is built from a package's own locale files and is strict by
default: a key the whole fallback chain lacks throws, even when a
defaultValue would hide it.

Replace every hand-written vi.mock('@nocobase/i18n/client') in plugin,
example and template tests with the real runtime, assert on shipped
wording instead of keys, and document the pattern in the i18n README and
the application and plugin development Skills.
2026-09-30 14:19:29 +08:00
PiEgg 9291dbb3b6 feat(i18n): accept the locales module on both sides and correct the i18n Skills (#504) 2026-09-30 14:06:53 +08:00
Katherine 23f052feab docs: move deployment to its own chapter and make it agent-first (#481)
* docs: move deployment to its own chapter and make it agent-first

Move the deployment section out of the development chapter into a
top-level `deployment/` chapter in both languages, and rewrite it around
two routes: handing the deployment to an AI Agent, with the session
location, Skill and prompt for each scenario, and doing it by hand, with
one page each for Hub and standalone deployment. Runtime configuration
and backup/troubleshooting stay as trimmed reference pages.

Update the home page cards, top navigation, development index and the
tutorial's Hub placeholder to point at the new chapter, and fix the two
links into the old path in the default template README and the
app-installer Skill.

* docs: fix deployment guidance links and rollback

* docs: make agent deployment prompts actionable

* docs: group deployment sidebar entries
2026-09-29 13:15:53 +08:00
chenosandli'mac b845b38f13 docs(skills): point the installer Skill at hub deploy from @nocobase/hub-cli (#473)
The routing table sent Hub publishing to the nocobase-deployment Skill and its release upload command. Since #470 the commands are hub deploy and hub upload, provided by @nocobase/hub-cli, which ships the nocobase-hub-cli Skill into the application that depends on it. This is a global Skill installed with npx skills add, so it reaches users on merge: merge it only once the release carrying #470 has been published.

Co-authored-by: li'mac <limac@Lis-MacBook-Air.local>
2026-09-28 04:33:34 -07:00
chenosandli'mac a857a087bb feat!: print the application CLI's --json envelope from app-installer, create-app and create-plugin (#471)
* fix(app-installer)!: print the application CLI's --json envelope

app-installer's doc comment promised a script that reads `pnpm nocobase ... --json` could read its output the same way, but two things differed: a failure's status was `error` rather than `failure`, and a suggestion's `run` was a shell string rather than `{ command, args }`. Both now match, including the Node.js check in bin/run.js. Suggestions that chained two commands with `&&` are split into two, and text output still prints each command as one quoted line.

`status` no longer prints `undefined` for an advice step that has no command.

tests/envelope.test.ts pins every member of the envelope, and tests/scripts/json-envelope-parity.test.mjs builds the same outcomes through both CLIs and compares the printed documents, so the two cannot drift silently again.

BREAKING CHANGE: scripts parsing app-installer --json must read `status: "failure"` and `run: { command, args }`.

* feat!: print the application CLI's --json envelope from create-app and create-plugin

create-app and create-plugin each had a JSON result of their own. create-app printed a flat object with `status: "error"` and no error codes; create-plugin named itself `operation`, kept its plan at the top level, gave suggestions as strings, and wrote failures to stderr. Both now print `{ schemaVersion, ok, command, status, result | error, warnings }` on stdout, like app-installer and every `pnpm nocobase` command.

create-app reports one code per stage a failure stops at (INVALID_USAGE, TEMPLATE_DOWNLOAD_FAILED, SCAFFOLD_FAILED, INSTALL_FAILED, DRIVER_VERIFICATION_FAILED), with the stage, directory and progress under error.details. create-plugin answers --dry-run with success-noop. Both answer an unsupported Node.js with the envelope under --json. Exit codes are unchanged.

app-installer and scripts/smoke-create-app.sh read create-app's new result and still read the flat one an earlier release prints. The parity test now covers all three standalone tools, and AGENTS.md records the rule.

BREAKING CHANGE: scripts parsing create-app or create-plugin --json read results under `result`, failures under `error` with `status: "failure"`, and create-plugin failures on stdout.

* fix(create-app,create-plugin)!: flush stdout before exiting and name the project in the install retry

The `--json` document was written and `process.exit` called in the same tick; on a platform where a piped stdout is asynchronous the document could be cut short, which the Node.js guard had already worked around on its own path. Both `bin/run.js` now exit through one helper that waits for stdout and stderr to drain, as app-installer's does.

create-app's INSTALL_FAILED suggestion ran `pnpm install` with no directory, so a caller executing `run` as given ran it where create-app was invoked rather than in the project; it now names the project with `--dir`. create-plugin's Node.js guard normalizes the version the way the other two guards do, and create-app pins its one-line output, which app-installer's `parseCreateResult` reads by line.

---------

Co-authored-by: li'mac <limac@Lis-MacBook-Air.local>
2026-09-27 19:21:46 -07:00
chenos 46ce11fabb refactor!: read client runtime values from the server-rendered config and build path-free clients (#455)
* refactor(app-client): read the mount path from the server-rendered config

resolveAppBase and resolveAppUrl now read app.basePath from the client
configuration the server renders into the page, and throw when the page
carries none, instead of reading window.APP_BASE_PATH or falling back to
the bundler's base. The router basename comes from the same value, so
AppRuntimeDefinition.basename is removed and the templates no longer take
it from the deprecated portal SDK.

The AI employee plugin no longer builds an API client when its modules
load. Its service functions take the ApiClient as their first, required
argument, and pages use the new useAIEmployeeClient() hook, which binds
them to the application's client. The registry's NocoBaseAIService now
requires its client, and the nocobaseAIService singleton is gone.

The templates render a config block in their test setup, as the server
would, and drop the unused assetUrl helper.

* refactor(app-server): stop putting runtime values on window

The SPA routes render only the lang attribute and the client
configuration block. window.APP_BASE_PATH, window.NOCOBASE_API_URL and
the window.__nocobase_api_client_* storage settings are no longer
injected: the client reads the mount path and API URL from the
configuration, and nothing read the storage settings except the portal
SDK's unused AuthSession. SpaConfig.runtime, createNocoBaseSpaRuntimeGlobals,
injectSpaRuntimeGlobals and the SpaRuntimeGlobals types are removed, and
runtime-globals.ts becomes runtime-html.ts.

The templates drop the matching spa.runtime defaults and the Vite
defines that baked NOCOBASE_API_URL, NOCOBASE_AUTHENTICATOR and the
NOCOBASE_WS_* values into the client; pnpm dev no longer sets
NOCOBASE_API_URL for its child processes.

* refactor!: remove the portal SDK and rename the portal presets

@nocobase/app-portal-sdk is deleted. Its runtime helpers were already
deprecated, and after the client stopped reading window globals the
templates used nothing from it; its client and routing entry points had
no users in this repository. The peer-dependency list, the registry
dependency check, the root lint configuration and AGENTS.md drop it.

The dev-config presets that still carried the Portal name are renamed:
vite/portal with createPortalViteConfig becomes vite/app with
createAppViteConfig, and the ESLint createPortalConfig becomes
createApplicationConfig. Browser code is now linted against reading
import.meta.env beyond PROD, DEV and MODE.

The templates no longer define __PORTAL_TEMPLATE_NAME__,
__PORTAL_TEMPLATE_VERSION__ or the unread __PORTAL_DEV_SOURCE_ROOT__ at
build time. The server publishes the application's displayName and
version from its package.json as app.displayName and app.version in the
public client configuration, and the sidebar footer reads them there.
The bootstrap loading classes become app-bootstrap-*.

* feat!: build path-free clients and choose the mount path at run time

The shared Vite preset builds with a relative base, so every chunk,
preload dependency and asset resolves against the module that references
it and one build can be mounted at any path. renderBuiltUrl and its
window.APP_BASE_PATH expression are gone. The development server still
needs an absolute base and takes it from APP_BASE_PATH, which pnpm dev
now resolves once and passes to both children; it refuses to start
without one. The templates no longer set a base.

The server points the remaining ./assets/ URLs in index.html, meta
content included, at the path it is mounted at when it renders the page.

pnpm build records nocobase.relocatable instead of nocobase.basePath and
no longer copies APP_BASE_PATH into dist/.env, so the deployment chooses
the mount path. DEFAULT_APP_BASE_PATH in @nocobase/app-server/support is
the only place /main is spelled.

In PROXY_TARGET_URL mode no local server renders the page, so a
development plugin from @nocobase/app-cli/dev/proxy takes the client
configuration from the remote page and substitutes the local mount path
and API URL.

* feat!: choose the mount path when installing or running a build

app-installer takes --base-path and keeps the mount path in app.env, so one
relocatable archive installs anywhere and moves by editing APP_BASE_PATH.
An archive from before relocatable builds records the path its client was
compiled for and is only installed, upgraded to or rolled back to there.

The Hub refuses such an archive unless it was built for /<appId>. The
template Dockerfiles no longer take APP_BASE_PATH as a build argument; the
image defaults to /main (/hub for the Hub) and -e APP_BASE_PATH moves it.
The deployment Skill and documentation drop the build-time mount path.

* chore: add changesets and upgrade steps for the client runtime configuration

The removed @nocobase/app-portal-sdk is dropped from the consumed changesets
in .changeset/pre so the next prerelease run can still read them, and the
one that named only it is deleted. The nocobase-app-upgrade Skill's edge
cases gain the steps an existing application takes to move to the
server-rendered client configuration and relocatable builds.

* ci(smoke): give the deployed archive its mount path at run time

A build no longer copies APP_BASE_PATH into dist/.env, so the extracted
archive started at the server's /main default. The Hub serves at /hub, and
the smoke waited there until it timed out. Pass the path the checkout
served, as a deployment does.

* fix: resolve review findings on client runtime configuration

- app-server rewrites every `./` URL in `index.html` to the mount path,
  not only `./assets/`: a relative base writes every `public/` file the
  page names as `./<file>`, and a deep route resolved those elsewhere.
- app-installer keeps `/hub` for a Hub archive installed without
  `--base-path`; a build records no path and ships no `.env`, so the
  server's `/main` default moved it. Adds a test for `--base-path /`.
- The proxy-mode client configuration fetch reports the status it met
  and a redirect to another origin instead of blaming the URL.
- `createDevProxy` and `createDevClientConfigPlugin` share the local
  prefix computation; the JSON escaping stays local, with the reason.
- The Vite preset README says why serve-mode resolution outside
  `pnpm dev` needs `APP_BASE_PATH` and how a build is previewed.
2026-09-27 06:52:43 -07:00
chenos ba075877f5 fix(skills): make two SKILL.md descriptions valid YAML
An unquoted description containing a colon followed by a space is parsed
as a nested mapping, so skills loaders skip the whole Skill. This stopped
`npx skills add nocobase/nocobase3 --skill nocobase-create-app` from
finding nocobase-create-app, and affects nocobase-app-plugin-ai-employee
the same way.
2026-09-26 23:00:57 -07:00
chenos 09b95894dd docs(skills): split the global Skills into project and installation (#453)
* docs(skills): split the global Skills into project and installation

nocobase-create-app creates a project to develop; nocobase-app-installer
installs built NocoBase where it runs — a Hub from its template, or an
application's archive — and upgrades it. A Hub is installed, not created,
unless the user will develop the Hub's own code. Both Skills carry the same
routing table, and a request to install NocoBase on a server with no project
makes the agent ask what it is for.

nocobase-create-app also says which template to pick and, at the end, how the
application reaches production. nocobase-app-installer separates the build
machine from the one that runs NocoBase, splits installing into what every
installation needs, an archive and a Hub, and covers several installations on
one machine. skills/README.md and AGENTS.md describe the split.

* refactor(app-installer): stop writing HUB_STORAGE_DIR for a Hub

The release carrying the installer also publishes a Hub that reads
APP_STORAGE_DIR, so the old name the installer wrote beside it for earlier Hub
releases is no longer needed. Installing an earlier Hub is refused with
STORAGE_IN_RELEASE.

* ci: smoke the Hub template from this checkout on pull requests

A pull request can change the installer and the Hub template together, as the
move to APP_STORAGE_DIR did, and the Hub smoke job paired this checkout's
installer with the published Hub, so it failed until a release. The Quality
workflow now starts a local registry from Build's tarballs, tags the packages
resolved by tag, and runs the template smoke with the installer and the Hub
both from this checkout. The published Hub is still installed daily and on
demand by app-installer-smoke.yml, which no longer runs that job on pull
requests. The registry steps live in a new local-registry action; the release
workflows' verify-create-app is left as it is.

* test: cover the app-installer smoke job in the Quality gate test

The test runs the gate's shell with every job's result; it now passes the new
job's result and checks that a failure of it fails the gate.
2026-09-26 22:11:45 -07:00
chenos a4ee8aa542 feat(app-installer): generalise hub-installer into app-installer for deployment archives (#451)
* feat(app-server): keep standalone storage under APP_STORAGE_DIR and record build metadata

A standalone application reads APP_STORAGE_DIR for its storage directory, so a
deployment whose code root is replaced per release keeps one storage directory.
The Hub template still honours HUB_STORAGE_DIR when APP_STORAGE_DIR is unset.

pnpm build records nocobase.basePath and nocobase.builtAt in dist/package.json,
and the templates' ecosystem.config.js derives the pm2 process name from the
application's package name, overridable with APP_PM2_NAME.

* refactor(app-installer): rename @nocobase/hub-installer to @nocobase/app-installer

A pure rename ahead of generalising the installer beyond the Hub: the package
directory, name and bin, its unreleased changesets, the smoke script and its
workflow, and pnpm unreleased:hub-smoke, now unreleased:installer-smoke. The
package was never published, so nothing outside this repository refers to the
old name. Behaviour is unchanged.

* refactor(app-installer): generalise the installer's layout, state and backups

Releases are named by version and build time (releases/<version>_<time>/app),
so two builds of one version are two releases; upgrade --rebuild becomes an
ordinary upgrade to a fresh build, which removes the staging and swap logic.
installer.json records the application name, base path and source. The runtime
variables move to app.env with APP_STORAGE_DIR (and HUB_STORAGE_DIR for Hub
releases published before it), health and URLs follow the release's base path,
and backups cover every SQLite database config.yml declares. Messages name the
application rather than the Hub, and the pm2 name defaults to the directory.

* feat(app-installer): install and upgrade from a deployment archive

install takes --archive <dist.tar.gz> or --template hub[@version], one of them
and only one. An archive is unpacked with the tar library pnpm build packs it
with, so a server needs no tar and no pnpm; its manifest names the release, and
an archive without a base path and build time, built for another machine, or
lacking the dialect's driver is refused with the build command that fixes it.
upgrade --archive requires the same application name and base path and refuses
an older version; a release writing its data inside itself is refused as well.
An upgrade takes the kind of source the install did.

* feat(app-installer): source-aware status and rollback advice, free-port suggestion

status and rollback tell an archive installation to build its archive again
for this machine's Node and upgrade to it, where a template installation gets
upgrade --rebuild; status reports the source and skips the registry for an
archive installation. install names a free port when the chosen one is taken.
The README documents both sources, release ids, app.env and every error code.

* test(app-installer): smoke both sources in CI and unreleased:installer-smoke

The smoke script takes --source template or --source archive. The archive run
installs a deployment archive, upgrades to a later build of the same version,
rolls back, restores the database after a migrating upgrade, and rolls back
automatically from a build that fails to start. CI gains a job that builds the
default template's archive from the checkout and runs it; unreleased:installer-
smoke creates and builds an application from the snapshot and runs both.

install reports the release's build time. The template alignment test no
longer expects each template to name its own pm2 process.

* docs: document app-installer and rename its global Skill

The global nocobase-hub-installer Skill becomes nocobase-app-installer and
covers both sources: building and installing a deployment archive, and the Hub
template. The nocobase-deployment Skill offers app-installer for a standalone
server, and the documentation site gains a page for it, with the Hub page,
the deployment overview, operations and standalone pages updated. Examples that
run published Hub images keep HUB_STORAGE_DIR, which those images still read.

The unreleased installer changesets, written for the Hub-only package, are
replaced by one describing the package as it now ships.

* docs: name only APP_STORAGE_DIR in the deployment pages

The Hub pages, operations page and the app-development Skill no longer
document HUB_STORAGE_DIR: nothing has been released under the old name that
the documentation needs to keep serving.

* refactor: drop HUB_STORAGE_DIR in favour of APP_STORAGE_DIR

The Hub template no longer reads HUB_STORAGE_DIR, which removes its own path
policy: the runtime applies APP_STORAGE_DIR for every standalone application.
app-installer stops writing the old name for a Hub installation. Nothing has
been released under the old name that has to keep working; a deployment that
sets only HUB_STORAGE_DIR renames it before upgrading.

* fix(app-installer): write HUB_STORAGE_DIR for a Hub until one reads APP_STORAGE_DIR

Every Hub release published so far reads only HUB_STORAGE_DIR, so a template
install of one kept its data inside the release and was refused with
STORAGE_IN_RELEASE. The installer writes the old name beside APP_STORAGE_DIR
for a Hub installation again; a Hub that reads APP_STORAGE_DIR ignores it.
Remove it once such a release is published.

* fix(app-installer): address review findings on PR 451

Recognise a Hub by its manifest's nocobase.templateKind rather than by the
install source, so a Hub project deployed from an archive keeps its App Host
child (treekill off) and its confirmations name the hosted applications;
installer.json records the kind. Suggestions no longer carry a placeholder in
a command meant to run as printed, and every rebuild suggestion made during an
upgrade builds for the installed base path. An unreadable config.yml stops an
upgrade with CONFIG_UNREADABLE instead of passing as "no databases".
rollback --to <version> resolves to the newest build of that version other
than the running one. The upgrade prechecks are hoisted out of both source
branches, and tests cover the storage check rolling an upgrade back and an
unrecorded release directory being adopted.

* docs(skills): route Hub installs and app-installer deployments to nocobase-app-installer

nocobase-create-app is for installing, creating or trying NocoBase, and hands a
request to install a Hub, or to deploy an application with app-installer, to
nocobase-app-installer. Only a Hub whose own code will change is created as a
project. Both Skills now carry the same routing table, and their descriptions
name what each is for and what it hands over.
2026-09-26 20:42:41 -07:00
chenos 19a8431c10 docs(skills): add nocobase-hub-installer and document the Hub installer (#447)
* feat(hub-installer): install a Hub from the published template

Add @nocobase/hub-installer for servers that run an unmodified Hub.

`install <dir>` resolves the template version, generates and builds the
Hub in `.build/<version>/` with create-app, unpacks only the deployment
archive into `releases/<version>/hub`, writes `hub.env` and `config.yml`
outside the release, applies the migrations with the release's own CLI,
switches `current`, and starts the Hub with pm2 until `/hub/api/healthz`
answers. A failure before the switch removes everything it wrote.

`status` reports the current version, health, pm2 process, releases,
Node compatibility and whether the registry has a newer version.

The pm2 configuration runs `node` itself with the entry file as its
argument, because pm2's fork mode loads a script through a wrapper where
`import.meta.main` is false and the Hub never starts.

A smoke test installs the published template on Debian bookworm with
Node 24 on pull requests that touch the package, and daily, since the
template ships no lockfile and a new dependency release can break it.

* feat(hub-installer): upgrade and roll back a Hub

`upgrade` builds the new release beside the running one, or reuses a
release already on disk, and checks it with its own `config check` and
`db apply --dry-run` before any downtime. It then stops the Hub, copies
the SQLite database, config.yml and hub.env to backups/, switches
`current`, migrates and starts the new release behind a health check.
A failure after the switch rolls back automatically, restoring the
database when the new release may have migrated it: exit 3 when the
previous release is healthy again, 4 when it is not. Releases beyond
`--keep` are pruned.

`rollback` returns to the release the last upgrade came from and
restores the pre-upgrade database only when that upgrade migrated.

An operation interrupted during the downtime is recorded in
installer.json: status reports it, upgrade refuses to stack on it, and
rollback recovers by undoing an interrupted upgrade or finishing an
interrupted rollback. Both commands confirm first or take `--yes`, and
a non-SQLite database requires `--backup-done`.

The smoke test now upgrades from a copy of the installed release and
rolls back, exercising the downtime path without a second build.

* feat(scripts): add unreleased:hub-smoke for the Hub installer

`pnpm unreleased:hub-smoke` installs a Hub with the snapshot's
hub-installer from the local registry, checks it, upgrades from a copy
registered as an older release, rolls back and checks again. It runs
pm2 under a short temporary PM2_HOME, stops it afterwards, and keeps
the Hub and its logs outside the repository.

The CI smoke test can only see changes to the installer itself: it
installs the published template. This covers changes to the Hub
template, app-cli, app-host and create-app before they are released.

Both run scripts/smoke-hub-installer.mjs, which replaces the inline
checks the workflow carried, so the local and CI checks cannot drift.

* fix(hub-installer): address the install review

- Refuse a pm2 name that is already registered: `pm2 start` on a taken
  name restarts that process with this install's environment instead of
  starting a new one, and START_FAILED then deleted it.
- Check before the first write that the port binds, that every
  `--set-from-env` variable is set, and bound `--port` and
  `--health-timeout`; a regular file at the target is a precheck error.
- SIGINT/SIGTERM stop the running step and let the failure handling
  clean up; a second signal exits. Children run in their own process
  group, so a timeout or interrupt stops what pnpm started, and the
  promise settles only once the group has exited.
- Write the pm2 config and installer.json before switching `current`,
  so the switch is the last write before starting; remove parents the
  install created; keep `.build/` on failure under `--keep-source`.
- Fail fast when pm2 reports the new process as crashed, instead of
  waiting out the health timeout.
- `status` no longer starts the pm2 daemon; the lock is linked into
  place with its pid already written; dist-tags use `hasOwn`;
  `install --help` prints help; `config set` values are masked in
  output; an unsupported Node exits 2.
- The smoke test builds the package and runs dist/, and also runs on
  lockfile and workspace changes.
- Tests drive `install()` through a fake of every child process it
  starts, and runCommand through real process groups.

* docs(skills): add nocobase-hub-installer and document the Hub installer

Add the global `nocobase-hub-installer` Skill for installing, upgrading,
rolling back and checking an unmodified Hub with @nocobase/hub-installer.
It routes a Hub whose source changes to nocobase-create-app with
`--template=hub` and Docker to the documentation, runs every command
through `npx --registry=${NOCOBASE_REGISTRY:-https://npm.nocobase.ai}`
with `--json`, and makes the agent relay the confirmation notes before
passing `--yes`.

NocoBase 3 publishes to https://npm.nocobase.ai, not to the public npm,
where @nocobase/create-app answers 404. nocobase-create-app and the
agent guide on the documentation site claimed the opposite, so on a
machine without a scoped registry `pnpm create @nocobase/app` failed.
They now name the registry before `create`, in the same
NOCOBASE_REGISTRY-aware form, which unreleased:env still redirects.

skills/README.md covers both global Skills; the Hub deployment pages
gain an installer route, and nocobase-deployment tells a Hub project
apart from an unmodified Hub.

* fix(hub-installer): address the upgrade and rollback review

rollback:
- Restore only from a backup that holds a database. An external
  database is never in one, so a Hub on PostgreSQL or MySQL used to
  stop, fail to restore, and stay down.
- Keep installer.json in step with the `current` link: `current` is
  written only after the link moves. A failed stop changes nothing and
  exits 1; a target that does not start stays pending, and running
  rollback again retries it, restore included.
- Recover an interrupted upgrade by what it actually did: before the
  switch nothing was migrated, so the old release is just started; an
  interrupted rollback keeps its backup and restores it again.

upgrade:
- Record the backup in `pending` only once it is complete, and mark the
  switch before the link moves; remove a half-written backup on abort.
- Move the switch inside the rollback's error handling.
- Write the successful result before pruning, then prune.
- Never prune the release upgraded from; `--keep` is at least 2.
- Keep the new release, and the pending state, when the automatic
  rollback fails too, so `rollback` can finish or return to it.
- Refuse a downgrade; reuse a recorded release only when it was built
  for this machine, otherwise rebuild it and replace its record.
- Check that the pm2 process belongs to this root and that the port is
  free once the Hub is stopped; wait for health failing fast on a
  crashed process; read the state under the lock; UTC backup names.

Flow tests drive both commands through the fake world: exit 3 and 4,
recovery after each, external databases, stop failures, interrupted
upgrades, rebuilds, ownership and pruning. The smoke test pins the
upgrade target to the installed version.

* docs(skills): sharpen nocobase-hub-installer from agent trials

Two trial runs of an agent following the Skill against the unreleased
snapshot surfaced gaps: reading config.yml to report the first sign-in
contradicted keeping it out of the conversation; nothing covered
reaching the Hub without a proxy (--host 0.0.0.0), changing the origin
after installing, relaying result.nextCommands, result.notes and
result.pruned, pruning under --keep, or npx prompting in a terminal.
Each is now covered, and the pm2 check no longer starts its daemon.

* test(scripts): cover the restore and automatic rollback paths in the Hub smoke test

The smoke test upgraded to a copy of the same release, which applies no
migrations, so the rollback never restored the database and no upgrade
ever failed. It now also rolls back an upgrade recorded as migrating and
checks that a table written after it is gone, and upgrades to a release
whose server entry throws, expecting exit 3 with UPGRADE_ROLLED_BACK and
the previous release healthy again.

Both scripts refuse port 13010, where the Hub's App Host listens; a Hub
on it failed its health check with no clear reason.

* feat(hub-installer): start through a launcher, report endpoints, and make suggestions runnable

pm2 now runs launcher.mjs in the Hub root. On every start it reads
hub.env and resolves `current`, then becomes the Hub through
process.execve, keeping the pid pm2 watches and passing node flags on.
A plain `pm2 restart` therefore applies an edited hub.env or a switched
release; ecosystem.config.cjs no longer captures either.

- status reports `endpoints`: the public URL and origin, and the host
  and port the Hub listens on.
- install reports `initialAdmin` (username, email, and whether the
  password is still the template default, never the password), and
  takes --dir like the other commands.
- Every suggested command and --help example runs as printed: npx with
  the registry named, pinned to this installer's version, paths quoted.
  Suggestions that were not commands, and the release CLI's own
  commands, become messages.
- pm2 4.3 or later is required, the first release that reads
  ecosystem.config.cjs as a configuration.
- Under --json, an unsupported Node.js prints the error envelope.
- The README documents every error code, changing the origin or port,
  and the launcher.

The smoke test switches releases with `pm2 restart` and checks that the
pm2 process runs the expected release's server entry.

* docs(hub-installer): restart with pm2 after editing hub.env, and report the sign-in account

The launcher reads hub.env on every start, so the Skill and the Hub
deployment docs change the origin or port with `pm2 restart` rather than
delete and start. The Skill reports the first administrator from
`result.initialAdmin`, the Hub URL from `endpoints` in status, asks for
a long timeout on install and upgrade, and names pm2 4.3 as the floor.

* docs(hub-installer): address the Skill and deployment docs review

The Skill said an upgrade rebuilds a release after a Node major change,
but an upgrade to the installed version is a no-op; it now says only a
newer version rebuilds and that the installer cannot rebuild otherwise.
Its Docker route links the Compose walkthrough that exists, on the
Chinese page. It names every key of error.details, scopes the loopback
health check to 0.0.0.0, says a SIGKILL leaves the target for the user
to empty, notes that an external database still gets its configuration
backed up, adds that a new port must be free, and gives the --keep
sentence its own step instead of a manual line break.

The Skills README names the pm2 4.3 floor. The English page qualifies
the upgrade backup as SQLite; the Chinese page lists installer.json,
ecosystem.config.cjs and launcher.mjs in the layout and offers the
installer alongside the template when no image exists yet.

* docs(hub-installer): point the Skill and the deployment pages at upgrade --rebuild

The installer can now rebuild the installed version for a machine whose
Node major changed. The Skill tells the agent to run the command the
status warning names, confirmed and read like an upgrade, and that an
interrupted rebuild is finished by running it again rather than by
rollback. Both deployment pages mention the flag next to upgrade.
2026-09-26 17:29:27 -07:00
chenos 3c56455152 feat(scripts): add unreleased:hub-smoke for the Hub installer (#446)
* feat(hub-installer): install a Hub from the published template

Add @nocobase/hub-installer for servers that run an unmodified Hub.

`install <dir>` resolves the template version, generates and builds the
Hub in `.build/<version>/` with create-app, unpacks only the deployment
archive into `releases/<version>/hub`, writes `hub.env` and `config.yml`
outside the release, applies the migrations with the release's own CLI,
switches `current`, and starts the Hub with pm2 until `/hub/api/healthz`
answers. A failure before the switch removes everything it wrote.

`status` reports the current version, health, pm2 process, releases,
Node compatibility and whether the registry has a newer version.

The pm2 configuration runs `node` itself with the entry file as its
argument, because pm2's fork mode loads a script through a wrapper where
`import.meta.main` is false and the Hub never starts.

A smoke test installs the published template on Debian bookworm with
Node 24 on pull requests that touch the package, and daily, since the
template ships no lockfile and a new dependency release can break it.

* feat(hub-installer): upgrade and roll back a Hub

`upgrade` builds the new release beside the running one, or reuses a
release already on disk, and checks it with its own `config check` and
`db apply --dry-run` before any downtime. It then stops the Hub, copies
the SQLite database, config.yml and hub.env to backups/, switches
`current`, migrates and starts the new release behind a health check.
A failure after the switch rolls back automatically, restoring the
database when the new release may have migrated it: exit 3 when the
previous release is healthy again, 4 when it is not. Releases beyond
`--keep` are pruned.

`rollback` returns to the release the last upgrade came from and
restores the pre-upgrade database only when that upgrade migrated.

An operation interrupted during the downtime is recorded in
installer.json: status reports it, upgrade refuses to stack on it, and
rollback recovers by undoing an interrupted upgrade or finishing an
interrupted rollback. Both commands confirm first or take `--yes`, and
a non-SQLite database requires `--backup-done`.

The smoke test now upgrades from a copy of the installed release and
rolls back, exercising the downtime path without a second build.

* feat(scripts): add unreleased:hub-smoke for the Hub installer

`pnpm unreleased:hub-smoke` installs a Hub with the snapshot's
hub-installer from the local registry, checks it, upgrades from a copy
registered as an older release, rolls back and checks again. It runs
pm2 under a short temporary PM2_HOME, stops it afterwards, and keeps
the Hub and its logs outside the repository.

The CI smoke test can only see changes to the installer itself: it
installs the published template. This covers changes to the Hub
template, app-cli, app-host and create-app before they are released.

Both run scripts/smoke-hub-installer.mjs, which replaces the inline
checks the workflow carried, so the local and CI checks cannot drift.

* fix(hub-installer): address the install review

- Refuse a pm2 name that is already registered: `pm2 start` on a taken
  name restarts that process with this install's environment instead of
  starting a new one, and START_FAILED then deleted it.
- Check before the first write that the port binds, that every
  `--set-from-env` variable is set, and bound `--port` and
  `--health-timeout`; a regular file at the target is a precheck error.
- SIGINT/SIGTERM stop the running step and let the failure handling
  clean up; a second signal exits. Children run in their own process
  group, so a timeout or interrupt stops what pnpm started, and the
  promise settles only once the group has exited.
- Write the pm2 config and installer.json before switching `current`,
  so the switch is the last write before starting; remove parents the
  install created; keep `.build/` on failure under `--keep-source`.
- Fail fast when pm2 reports the new process as crashed, instead of
  waiting out the health timeout.
- `status` no longer starts the pm2 daemon; the lock is linked into
  place with its pid already written; dist-tags use `hasOwn`;
  `install --help` prints help; `config set` values are masked in
  output; an unsupported Node exits 2.
- The smoke test builds the package and runs dist/, and also runs on
  lockfile and workspace changes.
- Tests drive `install()` through a fake of every child process it
  starts, and runCommand through real process groups.

* fix(hub-installer): address the upgrade and rollback review

rollback:
- Restore only from a backup that holds a database. An external
  database is never in one, so a Hub on PostgreSQL or MySQL used to
  stop, fail to restore, and stay down.
- Keep installer.json in step with the `current` link: `current` is
  written only after the link moves. A failed stop changes nothing and
  exits 1; a target that does not start stays pending, and running
  rollback again retries it, restore included.
- Recover an interrupted upgrade by what it actually did: before the
  switch nothing was migrated, so the old release is just started; an
  interrupted rollback keeps its backup and restores it again.

upgrade:
- Record the backup in `pending` only once it is complete, and mark the
  switch before the link moves; remove a half-written backup on abort.
- Move the switch inside the rollback's error handling.
- Write the successful result before pruning, then prune.
- Never prune the release upgraded from; `--keep` is at least 2.
- Keep the new release, and the pending state, when the automatic
  rollback fails too, so `rollback` can finish or return to it.
- Refuse a downgrade; reuse a recorded release only when it was built
  for this machine, otherwise rebuild it and replace its record.
- Check that the pm2 process belongs to this root and that the port is
  free once the Hub is stopped; wait for health failing fast on a
  crashed process; read the state under the lock; UTC backup names.

Flow tests drive both commands through the fake world: exit 3 and 4,
recovery after each, external databases, stop failures, interrupted
upgrades, rebuilds, ownership and pruning. The smoke test pins the
upgrade target to the installed version.

* test(scripts): cover the restore and automatic rollback paths in the Hub smoke test

The smoke test upgraded to a copy of the same release, which applies no
migrations, so the rollback never restored the database and no upgrade
ever failed. It now also rolls back an upgrade recorded as migrating and
checks that a table written after it is gone, and upgrades to a release
whose server entry throws, expecting exit 3 with UPGRADE_ROLLED_BACK and
the previous release healthy again.

Both scripts refuse port 13010, where the Hub's App Host listens; a Hub
on it failed its health check with no clear reason.
2026-09-26 07:37:46 -07:00
YANG QIA 2f97f0016c feat(authorization): department subjects — inspector inheritance, localized subject titles, an organisation Skill, and a departments example (#435)
* feat(app-plugin-authorization): list a subject's members and link to its management page

Subject types may implement members and manage; the assignments tab shows a
members drawer and a manage link, the inspector explains inherited sets, and
assignment changes on non-user subjects refresh every client.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* docs(app-skills): build an organisation dimension and wire it into authorization

Add organization.md to the application development Skill, covering the
department model, service, routes, settings pages, subject type, refresh,
seeds and tests, and link it from the reference table and authorization.md.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* feat(examples): add a departments example wired into authorization

A department tree registered as the org.department subject type, with
settings pages, a directory page, seeds and tests; registered in the
examples template.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* docs(app-skills): fill the gaps the departments example exposed

Cover the plugin layout, the full service, 403 mapping, id-list record
access, empty scopes, demo accounts and the test harness.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* refactor(authorization-example): replace team subjects with the departments example

Drop the example's team subject, membership and team assignments; order
relations now target carriers, and inherited jobs point to the departments example.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* fix(authorization): answer 403 for a denied require and an empty result for an empty scope

AuthorizationDeniedError now answers 403 through Hono's default error handler, and a granted record access that selects nothing yields a scope matching no rows instead of a denial.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* feat(departments-example): seed demo accounts and show inherited, multi-department and direct access

Demo accounts are written by the seed with hashPassword, and the workarounds for denied require and empty scopes are gone.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* docs(app-skills): seed demo accounts directly and drop fixed workarounds

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* refactor(app-plugin-authorization): drop subject members and manage links, translate subject titles

Removes members/manage for now and lets subject options carry { key, ns } titles rendered in the viewer's language. Inspector inheritance, the global refresh and the 403/empty-scope fixes stay.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* feat(departments-example): build departments on the authorization example's sales domain

Seeds a trading company that reuses the sales permission sets and rules, syncs department regions into the sales data scope, and rebuilds Settings → Departments in the workspace style, localized in English and Chinese.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* docs(app-skills): update organization.md for localized names and attribute sync

Drops members/manage and describes one localized name, descriptor titles, attribute sync into business scopes, and department baselines with direct job roles.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* docs(authorization): split the organisation Skill and make authorization Skills self-contained

The organisation reference becomes an entry page with four sub-references, and no authorization Skill depends on the example plugins.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* feat(authorization-example): make the rule plugins optional

The seed skips default-access, sharing and restriction rows when their Collection is absent.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* feat(departments-example): add department heads, department data scopes and cross-department sharing

Heads hold a fixed subject; three owner-based scopes cover projects, quotes and orders; the seed works without the rule plugins.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* docs(app-skills): add an organisation permission design guide and department scopes reference

Covers job-role sets, department heads, scope choice and optional rule plugins with permission-set alternatives.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* docs(authorization): link the authorization Skills to the organisation permission design guide

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* fix(departments-example): notify heads above a moved department

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* feat(departments-example): share specific sales projects with Delivery and seed the example's own sales data

Drop the selected-department scope, give Delivery an own-projects viewer set, stop placing the authorization example's accounts in departments, and seed a few projects, quotes and an order owned by this example's staff.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* docs(app-skills): drop the selected-department scope and share cross-department records by id

The department scopes are viewer-relative, so the guide now shares specific records with a department through a sharing rule, and says what remains with permission sets alone.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* test(ai-employee): expect an empty result for an empty record scope

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE

* chore: consolidate the changesets for department subjects

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE
2026-09-26 21:39:07 +08:00
chenos ec92b20eda feat(app-cli)!: AppCommand authoring API and one --json envelope for every command (#441)
* feat(app-cli)!: add the AppCommand authoring API on the root entry

AppCommand exposes rootDir, withApp(({ app, env }) => ...), setStatus and
appPath() flags; --json prints one envelope for success and failure, and
CommandError carries a code, suggestions and details. The authoring API moves
from ./plugins to the package root, a ./testing entry provides
bindAppCommand, the runner closes runtimes a command left open, and console
logs of an application a command creates go to stderr.

* feat(app-cli)!: move every command to the AppCommand output contract

Built-in, plugin and generated commands extend AppCommand, return their
result, and throw CommandError on failure, so --json prints one envelope for
every command. Plugin commands reach the application through withApp() and
take appPath() flags; the CLI example's artifact build is a devCommand.

The testing entry exports runAppCommand next to bindAppCommand, dev-config's
ESLint presets refuse the patterns the contract forbids in cli/, app-cli is
registered as identity-sensitive, and the application and plugin-development
Skills each carry the command-authoring guide, kept identical by a test.

* feat(app-cli): keep command diagnostics on stderr and behind NOCOBASE_CLI_DEBUG

An AppCommand's logToStderr keeps writing under --json: stderr never carries
the document, so progress and confirmations no longer disappear for the agent
that asked for JSON. A CommandError keeps its cause off the chain oclif prints,
and NOCOBASE_CLI_DEBUG prints the redacted error chain behind any command's
failure, from source or dist, replacing release's own handling. The Skills
describe using the application's logger inside withApp() for records that
belong in its log files.

* feat(app-cli): make commands discoverable, previewable and recoverable for agents

- `nocobase commands --json` lists every registered command with its source,
  arguments, flags and whether it takes --json, --dry-run and --force; a
  deployment lists no development command.
- Invalid usage suggests the flag or command that was probably meant and the
  command's --help, drops oclif's trailing hint from the message, and always
  exits 2.
- db apply, rollback, redo and reset take --dry-run and return the plan;
  FORCE_REQUIRED carries that plan and the --force/--dry-run forms; a busy task
  lock fails with DATABASE_LOCKED, backed by TaskLockBusyError in
  @nocobase/db. Help examples lead with --dry-run.
- release reports upload and deployment progress on stderr while it waits.
- The runner no longer exits 13 with an unsettled top-level await when a
  reader such as `| head` closes stdout early.

* fix(app-cli): address review findings on the command contract

- withApp() and the db commands report a failure to put the application away as a warning instead of failing work that already succeeded or replacing the failure it ended in; a rerun of `db rollback` no longer rolls back a second batch
- suggestions and config init's next steps name `node <dist>/cli/index.js` in a built dist/, which has no pnpm; AppCommand gains `this.cliCommand(args)` for the same in a command's own suggestions
- usage errors never repeat a typed value, list allowed values and the flags a conflict involves, and release upload/deploy report them as INVALID_USAGE like every other command; `this.error()` reports COMMAND_FAILED rather than invalid usage
- NOCOBASE_CLI_DEBUG prints a failure's diagnostics once, and oclif's raw stack no longer replaces the message and its suggestions
- appPath() defaults declared in baseFlags resolve against the application root
- every --dry-run answers success-noop, and so does a db command that found nothing to do
- a dry run creates no SQLite file that does not exist yet: drivers may report hasStorage(), and a dry run then answers for an empty database without connecting
- the runner's safety net destroys a leaked runtime's scope even when shutdown fails
- a command bound without an id is named after its class, and the Skills' examples name app commands as the runner does

* fix(app-cli): address the remaining review threads on the command contract

- a single misspelled topic, such as `plugn`, now suggests that topic's help instead of only the full command list
- `commands --json` marks an appPath() default with `defaultRelativeTo: "application-root"`, matching what --help says
- the reason oclif gives for a `combinable` conflict is repeated instead of collapsing to a generic message: the pattern expected "Only of the following" where oclif writes "Only the following"
- the runner's own EPIPE handler is removed: it never installed, because @oclif/core swallows EPIPE on stdout at import, and exiting there would have skipped withApp() and the runner's cleanup; a test now covers a command that keeps printing after its reader leaves

* test(ai-employee): give the Skills tests' first render room to load the page

The first render in the file waits for the employee heading with findBy's default 1 s, which the page's import graph can outlast when CI runs every package's tests at once. The Tools tests hit the same wait and got a 10 s timeout in #413; the Skills tests now match.

* test(app-cli): inspect a workspace of the test's own rather than the repository

The workspace-root inspection test from #439 scanned the real repository's packages/, where other app-cli tests create and delete temporary applications under tests/.tmp while they run. Run together, the scan read one mid-write and the inspection failed. It now builds the one-application workspace it needs.
2026-09-25 19:57:00 -07:00
chenos dbf5631e1f fix: clear the leftovers of the CLI consolidation (#439)
* chore: replace the command ids left over from the CLI consolidation

The scheduler and template-print documentation still named `schedule sync`
and `skills:sync`, the template reference pages showed `pnpm migrate`, and the
global create-app Skill taught `pnpm config:*` scripts that applications no
longer carry. The create-app smoke test now records `<topic> <command>` ids
directly instead of mapping them back to the old script names.

* chore: drop references to the removed client:inspect and server:inspect

Application inspection was removed in #329, but the plugin-development
Skill, skills/README.md and a few comments still sent readers to
`client:inspect` and `server:inspect`. `plugin inspect` covers registration
state only, so the guidance now points there for registration and drops the
composition diagnostics. The authentication test that asserts no command is
prescribed now checks for `config init` rather than the old script name.

* fix: clear the remaining leftovers of the CLI consolidation

A second sweep after #431, #434, #437 and #438 found guidance and code that
still described the old command line.

- `plugin inspect` suggested the removed `pnpm plugin:register` and
  `pnpm skills:sync` scripts; it now suggests `pnpm nocobase plugin register`
  and `skills sync` with the flags that located the App, and the remaining
  colon-form hints name the real commands.
- The Default template never registered the scheduler's CLI entry, so the
  documented `pnpm nocobase scheduler sync` did not exist there.
- `@refinedev/cli` and `tsc-alias` are spawned by `nocobase build` and are now
  declared as optional peers of `@nocobase/app-cli`.
- `peers:check` covers `@nocobase/app-cli` again after its move to
  `packages/app`, and `deps:check` scans plugin `cli/` sources and source
  directories named `dist`.
- Skills, templates, plugin AGENTS.md copies, READMEs and the docs site now
  describe current commands, the `metadata/` directory from #437, the Hub
  archive path, `release` availability and the two `dist/package.json`
  scripts. Examples and Hub ignore `/.env`.

* fix: address review threads on the CLI leftovers

- the templates' Docker build contexts exclude build output where builds write it (each workspace package's dist/, the documentation sites') instead of every directory named dist/, so a source directory with that name, such as app-cli's dist commands, needs no exception
- `plugin inspect` suggests `skills sync --package <full name>` rather than the compatibility `--plugin`, and a test covers suggestions for a `--workspace-root`/`--app` inspection
- the CLI example Skill states when two plugin topics collide: once the scope and an `app-plugin-` prefix are removed
- `peers:check` fails on a manifest that does not parse instead of leaving the package out
- the smoke test's fake pnpm records a top-level command such as `nocobase build` by its own id, reading the top-level commands from app-cli
- the plugins changeset no longer lists create-app, whose change another changeset already covers
2026-09-25 19:34:23 -07:00
Zeke ZhangandClaude Opus 5.5 2217eb28e7 refactor(notification)!: remove the notification provider plugin in favor of the Base UI toast (#436)
* refactor(notification)!: remove the notification provider plugin in favor of the Base UI toast

@nocobase/app-plugin-notification-provider wrapped Sonner, which is not a
Base UI component, and bridged it into Refine's notification provider.
Refine is being phased out and the templates already ship a Base UI toast,
so the plugin is removed rather than rewritten on Base UI.

It was also the only toast host and the only Refine notification provider,
so without a replacement every toast would have disappeared silently:

- The templates mount their own Base UI Toaster in client/react-providers.ts
  and call toast.add from @/components/ui/toast; the plugin and sonner leave
  client/plugins.ts and package.json.
- Hub, Users, Workflow and AI employee report through
  Toast.useToastManager(); Workflow and AI employee no longer use Refine's
  useNotification() or declare @refinedev/core.
- The app-skills frontend references and upgrade guide, and the plugin
  development Skill, describe the new toast usage.
- The removed package is dropped from the consumed changesets in
  .changeset/pre so the next prerelease run can still read them.

BREAKING CHANGE: the new Hub, Users, Workflow and AI employee versions
require the application to mount a Base UI Toast.Provider; the upgrade steps
are in the nocobase-app-upgrade Skill's edge cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(notification): ship the toast migration as a major change and harden the upgrade path

- Hub, Users, Workflow and AI employee take a major bump, so their next
  versions are 1.0.0-beta: an existing application's ^0.1.0 ranges, and
  therefore `pnpm nocobase plugin update`, no longer install them before
  the application mounts the Base UI toaster.
- The nocobase-app-upgrade edge cases gain a "Notifications and the Base UI
  toast" section with ordered steps and a verification pass, and each
  affected plugin's Skill names the toaster requirement and the error that
  reveals it.
- Hub's ErrorNotification no longer reports its own cleanup close as a
  dismissal. Base UI calls onClose synchronously, so replacing one error
  with another made the parent clear the new one. Tests cover replacement,
  unmount and timeout.
- The hub and examples templates assert the toaster provider, as the
  default template already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(templates): keep toasts above dialogs and sheets

Every shadcn overlay in the templates, the toast viewport included, uses
z-50. The application's toaster portals into the body when the app starts,
so a dialog, alert dialog or sheet opened later comes after it in the DOM
and paints over its toasts: an error raised from inside a dialog showed up
dimmed and blurred under the overlay. Sonner, which the removed plugin
mounted, sat at z-index 999999999 and never hit this.

A [data-slot='toast-viewport'] rule at the end of client/styles.css lifts the
viewport to z-index 100, outside every CSS layer so it outranks the
generated component's z-50 utility. The component itself stays as shadcn
generates it. The styling reference and the upgrade steps say to keep the
rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(examples): raise a toast from inside the route overlay examples

The route dialog and drawer examples get a "Show a notification" button,
so the toaster can be checked where it used to fail: raised from inside an
open overlay, a toast must appear above the dialog or drawer rather than
under its backdrop. The button lives in the overlay component the dialog,
the drawer and their nested layers share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 23:26:04 +08:00
chenos ae43f41a94 refactor(app-cli): tidy the release commands and print --json failures on stdout (#434)
* refactor(app-cli): tidy the release commands and print --json failures on stdout

- Name the JSON operation release:upload and release:deploy, after the
  command ids, instead of app.upload and app.deploy.
- Register the release commands only in a source checkout: a built dist/
  has no archive for release upload to send.
- Resolve --file and --config from the current directory; the default
  archive stays under the App root.
- Name the missing, invalid or unknown flag in an argument error without
  repeating any value, and print an unexpected failure's cause to stderr
  under NOCOBASE_CLI_DEBUG.
- Give both commands a description and examples, and share their run
  logic in one ReleaseCommand base.
- Print --json failures of the plugin, package and skills commands on
  stdout, as every other command already did.
- Move the Hub publishing guidance into the nocobase-deployment Skill and
  describe the stdout-only --json contract in the CLI reference.

* test(app-plugin-hub): pass the publishing config as an absolute path

publishToHub now resolves --config from the current directory rather
than the App root, so the fixture names the file it wrote.

* docs(app-plugin-hub): resolve release --config and --file from the current directory
2026-09-25 02:13:32 -07:00
chenos 02d54029a8 refactor(app-cli)!: merge nb3-cli and app-tools into one application command line (#431)
* refactor(templates): drop the forwarding files in cli/

The templates re-exported @nocobase/app-cli through cli/database-command.ts,
cli/hub-publishing.ts and cli/commands/i18n-check.ts, and passed
createAppCommands through cli/standard-commands.ts. cli/commands/index.ts now
calls createAppCommands itself, the shared i18n check test lives in app-cli,
and app-plugin-hub imports publishToHub from the package rather than from a
template. app-cli drops the ./database-command and ./commands/i18n-check
subpaths that only those files used.

* refactor(app-cli)!: absorb nb3-cli and app-tools into one command line

@nocobase/app-cli now provides the nocobase bin and carries the command
assembly, the plugin contract and plugin/Skill management from nb3-cli, and
the dev, build, start and server-deps tooling from app-tools. Both packages
are removed.

Commands are found by path under src/commands and an application's
cli/commands, and the runner locates the application from the nearest
package.json (nocobase.templateKind or nocobase.buildTarget) or
NOCOBASE_APP_ROOT. A built-in command is dispatched without importing the
application's plugins. Development commands are not registered in a built
dist/, and the tooling they need is declared as optional peers. build and
dev hand the plugins' hooks to the scripts in NOCOBASE_CLI_HOOKS instead of
asking a second process.

Consumed changesets in .changeset/pre name the removed packages and are
renamed to @nocobase/app-cli, as the app-client consolidation did, so the
next prerelease run can still read them.

* refactor(plugins)!: take the CLI topic from the package name

A plugin's CLI topic is now its package name without the scope and
app-plugin- prefix, so the scheduler's commands move from schedule to
scheduler and the CLI example's from demo to cli-example; the built-in
topics avoid every plugin name. The workflow plugin's check and build are
devCommands, which a built dist/ leaves out.

Plugins and the create-plugin template peer on @nocobase/app-cli instead of
@nocobase/nb3-cli, and the workflow tests and skill-eval harness run the
Default template's CLI through the bin with NOCOBASE_APP_ROOT.

* refactor(templates)!: run every command through the nocobase bin

The templates drop scripts/*.mjs, cli/index.ts and cli/commands/index.ts.
Their scripts are postinstall, dev, build, start and the quality checks;
every other command is pnpm nocobase <topic> <command>, which pnpm resolves
to the bin without a script. The Default template enables the Hub
commands with nocobase.cli.publishing, and all three declare tar for the
build's --tar.

create-app runs pnpm nocobase skills sync and prints the new next steps.
The smoke script and the repository's template tests follow the new layout.

* docs: describe the merged CLI and use the new command ids

AGENTS.md, the application and plugin-development Skills, the docs site,
package READMEs and runtime messages now name nocobase db repair,
config init, locales check, release upload and the other new ids instead
of the removed app-topic ids and script aliases. The upgrade Skill gains the
step-by-step move to the new layout, which the changeset points to.

The global nocobase-create-app Skill and skills/README.md are left for a
follow-up after the release, because they reach users on merge.

* chore: cover the packages whose Skills name the new command ids

* refactor(app-cli)!: name the Collection and build commands by what they act on

db doctor compares Collection metadata with the schema, not migrations, so
it becomes collections doctor beside collections generate, and db holds only
migrations, seeds and locks. server-deps retarget and verify are two steps of
the build, run again on an existing dist/, so they become build retarget and
build verify. A dist topic was considered and rejected: a commands/dist
directory is ignored by the repository's .gitignore, Prettier and ESLint.

Topic and command descriptions now say application rather than app and Skill
rather than skill, package describes the one thing it does, and skills sync
mentions the .claude/skills links it writes.

* refactor(app-cli)!: move retarget and verify to a dist topic as dist retarget and dist check

Both act on an existing dist/ and never build, so they belong to a dist
topic rather than under build, which returns to being a plain command.
verify becomes check, the verb every other checking command uses
(config check, locales check, workflow check).

A dist directory is ignored by the repository's .gitignore, both Prettier
ignore files and the shared ESLint preset, and commands are found by their
directory, so src/commands/dist/ gets an exception in each of the four with a
comment saying why. tests/command-files.test.ts fails if any command file is
ignored by git, Prettier or ESLint, so losing an exception cannot go
unnoticed. The templates' AGENTS.md warns applications away from those
directory names in cli/commands/.

* docs(app-skills): add a command-line reference for the application CLI

nocobase-app-development gains references/cli.md: a task-to-command map, the
agent conventions --help does not state (--json, exit codes, --dry-run, and
that db reset, rollback, redo and repair refuse without --force outside a
terminal, which is not to be worked around), how to read the data-model
snapshot and keep it fresh, how to write an application command, and what a
built dist/ registers. The Skill links it before any pnpm nocobase command;
the deployment Skill notes dist retarget, dist check and running commands in
dist/; the three templates' AGENTS.md keep only the essentials and point to
the reference instead of repeating it.
2026-09-25 01:28:45 -07:00
YANG QIA 757eedfbfe fix(app-client): never fail startup on a page without authz (#433)
A page without authz inherits its nearest ancestor page; a first page defaults to unrestricted-only (protected app and settings pages) or skip (guest, optional and dev pages), with a development warning.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE
2026-09-25 13:38:45 +08:00
Zeke ZhangandClaude Opus 5.5 91cc403007 feat(ui-library): publish the page and route overlay components as registry components (#426)
* feat(ui-library): publish the page and route overlay components as registry items

Move PageContainer, PageHeader, RouteDialog, RouteDrawer, RouteChildPage and
useRouteOverlay out of the templates' client/components/ and into the UI
Library as two items, page-ui and route-overlay-ui, so applications and
plugins install one maintained source instead of copying template files.

- ui-library: add the items under registry/page/, conforming to the library's
  authoring rules (.js relative imports, explicit export types, tailwind-merge
  instead of @/lib/utils, prefixed translation key routeOverlay.close with
  shipped locales), preview demos, the dialog primitive, and generic preview
  wiring in website/app.tsx.
- templates: ship byte-identical installed copies under
  client/extensions/nocobase-page-ui/ and nocobase-route-overlay-ui/, import
  from there, and add routeOverlay.close to client/locales/.
- tests/scripts/template-ui-library.test.mjs fails when a template copy drifts
  from the library source.
- Point the app development Skill, the plugin development Skill, the template
  AGENTS.md files, the docs site and the library's own guides at the new
  locations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(ui-library): publish one component per item, installed into client/components

The page and route overlay components are single business components, not a
complete feature, so they follow shadcn's convention for components rather
than the block layout auth-ui uses.

- Split page-ui and route-overlay-ui into page-container, page-header,
  route-dialog, route-drawer and route-child-page. route-dialog and
  route-drawer both list the route-overlay.tsx and use-route-overlay.ts they
  share, so installing either brings its implementation and the two keep one
  Context.
- Target client/components/<file>, so templates keep their original paths and
  imports; revert the import rewrites across pages, tests, Skills and docs.
- Document the two kinds of item in MAINTAINING.md and USAGE.md: a component
  installs into client/components/, a block such as auth-ui into
  client/extensions/nocobase-<item>/, with the rules on relative imports,
  translations, targets, READMEs, upgrading and removal that follow.
- Components ship no locale files; routeOverlay.close is listed in the group
  README and each overlay item's docs.
- Rework the preview demos per item and the sync test for component targets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(ui-library): merge class names with cn from @/lib/utils

The components now live in client/components beside the templates' other
components, which all merge class names with cn from @/lib/utils. Importing
tailwind-merge directly made them the odd ones out.

- Import cn from @/lib/utils in page-container, route-child-page and
  route-overlay, and list utils in their registryDependencies instead of
  depending on tailwind-merge.
- Allow @/lib/utils as the second @/ import an item may use, map it in
  tsconfig.registry.json, and give the preview a real cn (the one shadcn's
  utils item generates) so the drawer's overriding classes merge there too.
- Document the rule, the plugin rewrite, and keeping the templates'
  lib/utils.ts (it also exports assetUrl) when an item requests utils.
- Teach the sync test that utils resolves to client/lib/utils.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:43:05 +08:00
YANG QIA c8ddd7d7f2 refactor(authorization): consolidate the authorization API (#417)
Consolidates the authorization library and plugins around one model: judgement by resource types and built-in composite resources, display through authz.ui, unified rule shapes, and a snapshot that agrees with can(). Existing databases must be reset.

Claude-Session: https://claude.ai/code/session_01KLK7YsekQSSZa8iRjRBmuE
2026-09-24 23:03:12 +08:00
chenos 0231d462f0 feat(app-client): type-check paths read through config.public (#423) 2026-09-24 01:55:02 -07:00
chenos f6c3cd87d2 feat(config): validate configuration sections and publish chosen fields to the browser (#420)
* fix(hub): drop the unused AUTH_DISABLE_SIGN_UP env variable

* feat(config): validate configuration sections and publish chosen fields to the browser

defineAppConfig now takes { defaults, validate, public }. Validators run at
startup, on reload and in config:check; public fields travel in a separate
block of the runtime payload and are read with config.public.get. The
authentication plugin uses this through defineAuthConfig so the templates
hide sign-up while the server has disabled it.
2026-09-24 01:17:42 -07:00
chenosandClaude Opus 5.5 1edc9b21b9 docs(skills): only ask for a new session when app Skills are not loaded
Some agents pick up the application's AGENTS.md and synchronized Skills
without a restart. Check whether they are available in the current
session before telling the user to start a new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 08:34:46 +08:00
chenos 57c0f6cf95 chore(scripts): rename local-registry commands to unreleased (#406)
The local-registry:* commands sat next to pnpm registry, which builds shadcn
Registry items, and read as a local variant of it. What they do is test the
unreleased checkout as published packages, so they are now unreleased:*,
with verify renamed to smoke and stop to clean. Clean also removes a session
prepared under the old names, which would otherwise keep holding the port.
2026-09-23 23:50:14 +08:00
chenos cde9a8ea11 chore(skills): commit repository Skills under skills/ and link them on install (#404)
* chore(skills): stop linking the deployment Skill into the repository

nocobase-deployment is written for generated applications and reaches
them through @nocobase/app-skills. Linking it into .agents/skills offered
it to agents working on the framework itself, where no application is
deployed.

* docs(db-testkit): move integration test guidance out of a repository Skill

The nocobase-db-integration-testing Skill was reference material for
this repository only, and AGENTS.md already pointed at it from a section
every agent loads. Fold it into db-testkit's TESTING.md next to the code
it covers, drop the parts AGENTS.md already states, and point AGENTS.md
and the plugin development reference there instead.

* docs(db-testkit): keep the integration testing guide under docs/

A guide at the package root counts as published output to
require-changesets, although the package ships only dist and README.
Under docs/ it is documentation, which is what it is.

* chore(skills): commit repository Skills under skills/ and link them on install

skills/ now holds every Skill this repository commits, including
nocobase-plugin-development. scripts/sync-skills.mjs replaces
link-claude-skills.mjs and, on postinstall, links each of them into both
.agents/skills/ and .claude/skills/, which become generated and ignored.

The links sit one directory deeper than skills/, so relative Markdown
links from the Skill into the repository resolved differently depending
on the path an agent opened. They are now repository-root paths.
2026-09-23 22:20:37 +08:00
chenos 979c8390c5 docs(skills): tighten the create-app Skill and its agent guide (#403)
* docs(skills): cover Hub creation and soften the new-session handover

The create-app Skill now passes --template=hub for a Hub and starts it with
the nextCommands create-app returns, clarifies that nextCommands default to
SQLite, and recommends rather than requires a new session, with a fallback
for continuing in the current one.

The agent guide the Skill links to no longer tells agents to run
pnpm config set @nocobase:registry, which the Skill forbids.

* docs(skills): stop and explain clearly when Node.js or pnpm is missing

The previous change dropped "before continuing", so an agent could report a
missing toolchain and carry on. It now stops before creating anything and
tells the user the found and required versions, the install command for
their system, and to open a new shell, then checks again.

* docs(skills): say exactly how to start the new session in the application

Distinguish an application created in the session's own directory, where
restarting there is enough, from one elsewhere, which needs its path and
the agent's start command, and cover desktop clients in the Skill.

* docs(skills): suggest a first request and keep the handover short

Finish now tells the user what to ask for in the new session, with a
small example and the first-feature guide, and asks for a concise
handover that names only the session case that applies.

* docs(skills): keep the service and database choice from surprising users

- Say the agent-started service stops with the session, and how to start it
  again.
- Use a database the user already named instead of asking again.
- After retrying a failed install, continue with Configure using the known
  commands, since the retry result no longer carries them.
- Require WSL on Windows, where the POSIX command form does not work.
- Allow a long timeout for a Hub build, wait for the Local: line of pnpm dev,
  and describe AGENTS.md as possibly unloaded rather than never loaded.

* docs(skills): leave the Hub out of the create-app Skill for now

The Skill and its guides describe only the default template until the Hub
flow is ready to document.
2026-09-23 21:31:28 +08:00
chenos 1fb8d55497 docs(skills): describe installing NocoBase through the global Skill (#402)
skills/README.md covers the two ways to use nocobase-create-app: against
the published packages, which is what users do, and against a local
registry holding the checkout, which is how a change is tested before it
is released. The second part records why `local-registry:env` is needed
rather than a few hand-set variables, and how to tell a snapshot install
from a published one.

AGENTS.md now points there instead of suggesting a plain copy install,
which installs the checkout's Skill but still resolves every package
from the published registry.
2026-09-23 20:38:49 +08:00
chenos 5db40ed1db feat(skills): add a globally installable nocobase-create-app Skill (#397)
An agent working outside any NocoBase application has nothing to tell it
how to reach NocoBase 3, and the NocoBase 2 Skills a user may already have
installed point it at the `nb` CLI. This Skill covers the stretch from an
empty directory to a running application: create it with
`pnpm create @nocobase/app --json`, configure it with `config:init`,
`config:set` and `config:check`, start it, and hand over to the
application's own AGENTS.md and Skills.

It lives in the root `skills/`, is installed with
`npx skills add nocobase/nocobase3 --skill nocobase-create-app -g`, and is
released by merging rather than by publishing. AGENTS.md records that, and
that such a Skill may describe only released behavior. `.prettierignore`
leaves it alone like the other Skill directories.
2026-09-23 20:14:22 +08:00