Commit Graph
72 Commits
Author SHA1 Message Date
PiEgg fe491f2b35 ci(v3-studio-image): publish the Studio image for linux/arm64 too (#10771)
* ci(v3-studio-image): publish the Studio image for linux/arm64 too

* ci(v3-studio-image): allow rebuilding a release image by dispatch

* ci(v3-studio-image): name the tags in the job that pushes them
2026-10-12 01:54:26 -04: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
PiEgg f38b341d67 ci(v3-pro-release-beta): point latest at each Pro beta release (#10765) 2026-10-11 21:11:24 -04:00
PiEgg 661b098400 ci(v3-release): split Studio's tests across two release test shards (#10763) 2026-10-11 21:02:33 -04:00
PiEgg f32f2deb82 ci(v3-release-beta): point latest at each beta release (#10761) 2026-10-11 20:34:49 -04:00
PiEgg c41598de14 ci(v3-quality): run Quality in chenos/nocobase-ci through nocobase-bot (#10726) 2026-10-10 22:41:46 -04:00
YANG QIA d3763a2e2a feat(studio): pack the runner and nb-studio into every build and serve them (#10716)
* feat(studio): pack the runner and nb-studio into every build and serve them

Studio's pnpm build now packs the universal nocobase-runner and nb-studio
packages into dist/runners at the exact versions it records in
served-versions.json, and fails when they differ. Studio serves that
directory by default, after agents.dist.dir and NB_STUDIO_RUNNERS_DIST,
so every deployment serves the runner and CLI it was built with; npm
remains the fallback for a Studio without packages.

The release image workflow no longer builds the packages separately or
uploads them as an artifact, and the Dockerfile drops its runners-dist
stage and rejects a prebuilt dist/ without dist/runners. The published
Studio package now includes ai/, which packing nb-studio needs.

* feat(studio-cli): share Studio's version line

nb-studio was served at Studio's version (1.0.0-beta.51) by earlier Studio
builds, so a CLI versioned 0.0.x would never be offered by `nb-studio update`.
@nocobase/studio-cli now starts at Studio's version and joins it in a fixed
changesets group, so both release as 1.0.0-beta.52 and move together.
2026-10-11 00:43:22 +08:00
YANG QIA 6efbe713ca ci(v3-studio-image): build the public Studio release image in this repository (#10715)
A beta release that publishes @nocobase/studio now calls v3-studio-image.yml,
which builds Studio from the release tag without baked runner packages and
pushes nocobase/studio:<version> and :beta to Docker Hub and Aliyun's public
registry, like the v2 release images, instead of dispatching the internal
image workflow in 2013xile/nocobase-ci. It also keeps the release's universal
runner and nb-studio packages as an artifact. Internal images stay in
nocobase-ci.
2026-10-11 00:14:53 +08:00
YANG QIA 61e31dc6b6 ci(v3-release-beta): dispatch the Studio image with the NocoBase App token (#10713)
The dispatch to 2013xile/nocobase-ci now mints a NocoBase App token for that
repository (Actions write), as every other workflow here authenticates,
instead of a separate personal access token. When the App cannot mint it, the
release still warns with the manual command and does not fail.
2026-10-11 00:07:08 +08:00
YANG QIA f34dbe4d11 ci(v3): build the Studio release image after a beta release (#10710)
When a beta release publishes @nocobase/studio, dispatch studio-image.yml in
2013xile/nocobase-ci with the release-beta/<batch> tag and release: true, so
the image is built from the released commit and tagged with the Studio
version and beta. The dispatch needs NOCOBASE_CI_DISPATCH_TOKEN and only
warns when it is missing.
2026-10-10 23:37:17 +08:00
PiEgg bd744ad629 fix(ci): cover stacked v3 PRs in site checks (#10702) 2026-10-10 09:52:30 -04:00
PiEgg a9396a8d29 fix(ci): run v3 checks for named stacked PR targets (#10701) 2026-10-10 09:48:04 -04:00
PiEgg adc559ffc4 ci(v3-pro): scope the Pro registry to @nocobase in release workflows
actions/setup-node was given registry-url: PRO_NPM_REGISTRY without a scope, so it wrote registry=<Pro registry> as the default registry for every package. The new Pro registry (pkg-v3.nocobase.com) serves only @nocobase/* and does not proxy npmjs, so the pnpm install in "Freeze the OSS ... snapshot and install" failed: the minimumReleaseAge check fetched metadata for lodash, @ai-sdk/* and every other third-party package from the Pro registry and got 401 for all of them. The old npm.nocobase.ai proxied the whole of npmjs, which hid the misconfiguration.

Set scope: '@nocobase' on the setup-node step in the beta and stable Pro release workflows so only @nocobase resolves through the Pro registry and everything else uses npmjs, and pass PRO_NPM_TOKEN as NODE_AUTH_TOKEN to the install step so @nocobase metadata requests carry the token setup-node references in .npmrc. Publishing and dist-tag steps already pass --registry and NODE_AUTH_TOKEN and are unaffected. The promote workflow configures no registry and is left unchanged.

Add a policy test that keeps the Pro registry scoped and the install step authenticated.
2026-10-10 07:59:30 -04: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
PiEgg 331d3d6fc4 fix(ci): isolate v3 docs publishing and skip unrelated docs-only checks (#10612) 2026-10-09 07:18:39 -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
PiEgg 563f1d704a chore(ci): prepare v3 branches in the main repository 2026-10-09 02:22:31 -04:00
PiEgg 1181a6acef chore(ci): remove Hub image publishing and automatic Pro follow-ups (#580) 2026-10-08 22:02:36 -04:00
YANG QIA 68d4febbcc feat(plugins): add the agents, projects, knowledge and releases plugins (#554)
* Add the agents, projects, knowledge and releases plugins

Four plugins for agent-run applications, the UI Library's agents and projects items, and the generated UI Library catalog of the application Skill with its check.

* Wait on the sent attachment's link as it renders last in the chat test

* Keep the demo chat's reply timers off window, so they outlive a test's environment quietly
2026-10-08 07:10:52 +08: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
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
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
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 1cdfaf4cd1 ci: run release build, test and pack in parallel (#485)
Split the release prepare job so build-and-pack and a two-shard test job run in parallel after versioning, skip the templates in the release build, run pack:check in parallel without rebuilding, resolve only @nocobase packages from npm.nocobase.ai, and start the Hub image alongside the GitHub Release.
2026-09-29 11:02:34 +08:00
PiEgg 1234564da7 feat(release): include current app template version in commit bodies (#484) 2026-09-29 10:09:33 +08: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 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
chenos 6cba461f43 feat(hub-installer): upgrade and roll back a Hub (#445)
* 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.

* 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.
2026-09-26 07:36:58 -07:00
chenos 7682daf6e5 feat(hub-installer): install a Hub from the published template (#444)
* 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.

* 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.
2026-09-26 07:15:31 -07:00
chenos d5a18ff33c feat(templates): ship a Dockerfile with every application template (#416)
* feat(templates): build every application template into a Docker image from source

Each template now ships a Dockerfile and Dockerfile.dockerignore. The build
stage runs pnpm build on the builder's own architecture and cross-targets
native modules, so multi-platform images compile nothing under emulation. The
runtime stage holds only dist/ and config.example.yml, runs node directly as
the node user without pnpm, and reads /app/config.yml and /app/storage from
mounts. The same file builds a generated application from its root and a
template inside this repository from the repository root via APP_DIR.

The official Hub image is now built from the Hub template's Dockerfile at the
release tag, replacing Dockerfile.hub. This also fixes the published image
failing with EACCES on /app/storage when HUB_STORAGE_DIR is unset, which the
playground Compose file hit and the smoke test, setting it, never did.

app-tools now carries the application's registry lines into dist/.npmrc.
dist/ is its own pnpm root, so the production install ignored the registry
create-app records and failed with a 404 on any machine without it in user
configuration, including every container build.

* feat(templates): package a prebuilt dist into the Docker image with DIST=prebuilt

--build-arg DIST=prebuilt skips the build stage and packages the dist/ in the
build context, selected through FROM dist-${DIST} so BuildKit never runs the
unused stage. It works in a template's own directory, where a source build
cannot resolve workspace dependencies.

The stage refuses a dist/ whose buildTarget is not linux, glibc, the target
architecture and the image's Node major, and one whose client was built for a
different APP_BASE_PATH, since either builds an image that fails only at
runtime. The ignore file re-includes dist/ last so dependency directories
named like excluded paths survive, then excludes dist/.env, which pnpm build
fills from local .env files. A source build without a lockfile in the context
now says how to build instead of failing inside pnpm.

* fix(templates): point a prebuilt dist built for another architecture at --platform

A dist/ built with --target linux-x64 on Apple silicon was rejected with advice
to rebuild for arm64, when the usual intent is an amd64 image for an amd64
server. The check now names the --platform flag that matches the dist/, and the
documented examples pass --platform alongside --target.
2026-09-24 12:47:30 +08:00
Zeke ZhangandClaude Opus 5.5 9e8fc3edd3 fix(ui-library): sync auth-ui, check items as plugins compile them, and document the library (#399)
* fix(ui-library): sync auth-ui, check items as plugins compile them, and document the library

Bring the auth-ui item up to date with the copies the templates ship.
Those gained translations in #264 while the registry source kept
hard-coded English, so `shadcn add @nocobase/auth-ui` installed an older
version than any template carried. The item now declares @nocobase/i18n.

- Relative imports inside items carry the .js extension. A plugin
  compiles installed source with NodeNext, which rejects an extensionless
  import; an application's bundler resolves either form.
- tsconfig.registry.json compiles the registry sources with NodeNext
  against the real workspace packages. tsconfig.json no longer maps the
  authentication plugin's actions to the preview's mock, so both programs
  check the items against the plugin's real exports and the mock is
  typed from them; only the Vite build swaps it in. A plugin change that
  breaks an item now fails typecheck in that plugin's pull request.
- The package is linted with the rules a plugin uses. The preview's
  shadcn primitives get the relaxations the Portal configuration gives an
  application's, and the two problems the rules found in the preview, a
  synchronous setState in an effect and a fetch that was never aborted,
  are fixed. The UI Library workflow lints before it publishes, and the
  pre-commit hook lints the directory with its own configuration.
- The preview mounts an i18n runtime, as an application does, so the
  translated items render without react-i18next warnings.
- MAINTAINING.md and USAGE.md document maintaining the library and
  installing its items into applications and plugins.

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

* feat(ui-library): ship auth-ui's translations with the item

The item's strings were translatable, but the translations lived only in
the templates' own locale files, so any other consumer had to copy the
keys by hand and could not tell when an upgrade added one.

- auth-ui ships locales/en-US.ts and locales/zh-CN.ts, listed in its
  files. en-US exports its shape as a type and zh-CN is annotated with
  it, so typecheck fails on a key either one lacks or adds.
- The item's docs, README and the usage guide describe merging the files
  into the consumer's locale resources, which installing does not do,
  and MAINTAINING.md makes shipping every key a rule for items.
- i18n:check also scans ui-library/registry/.

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

* revert(ui-library): leave the i18n:check scan to #400

The change that adds ui-library/registry/ to i18n:check now lives in
#400, next to the parser fix that makes it effective, so the two pull
requests no longer edit the same lines.

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

* fix(ui-library): address review findings

- The documented merge of an item's locales into a plugin's en-US.ts
  failed the plugin's declaration build with TS9015, because
  isolatedDeclarations cannot infer an object built with a spread. The
  item README now shows en-US annotated as
  `typeof authUi & typeof messages`, and USAGE.md says the merged file
  needs it.
- The preview's theme effect toggled only the `dark` class, leaving the
  inline color-scheme index.html sets on load stale after a toggle; it
  now updates both.
- ESLint relaxed only the first of dev-config's two shadcn registry
  blocks, so adding the chart primitive the documented way failed lint.
  dev-config now exports createShadcnRegistryConfig(root), which the
  Portal factory uses for client/ and ui-library uses for website/, so
  the list is no longer copied.
- tsconfig.registry.json compiled items with the ESNext library and
  without noUnusedLocals, unlike the plugins that install them; it now
  uses the create-plugin template's lib and noUnusedLocals.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:37:41 +08:00
Zeke Zhang 7264cb162a ci(ui-library): inline the OSS target in the workflow (#384) 2026-09-22 22:15:58 +08:00
Zeke Zhang 35ebdb0f2e ci(ui-library): publish the registry to OSS and point templates at it (#347)
* fix(templates): update UI registry URL

* ci(ui-library): add OSS publishing workflow

* ci(ui-library): read OSS bucket and region from secrets

* ci(ui-library): derive registry checks from the index

* ci(ui-library): simplify build output verification

* ci(ui-library): check the built favicon

* ci(ui-library): typecheck and check registry entries

* docs(ui-library): translate the deployment guide

* fix(ui-library): match the auth plugin beta range
2026-09-22 22:05:20 +08:00
PiEgg 944cf3c1e4 ci: release as nocobase bot and skip generated merge checks (#378) 2026-09-22 17:44:00 +08:00
PiEgg 97ad17717d ci: select Pro follow-up per release dispatch (#377) 2026-09-22 17:17:12 +08:00
PiEgg e730e03a46 ci: host commercial release workflows in the OSS repository (#374) 2026-09-22 16:30:39 +08:00
KatherineandClaude Fable 5.1 e5601cb876 docs: expand deployment guides and cover the deployment lifecycle with tests (#336)
* docs: expand standalone and Hub deployment guides

* docs: clarify deployment backup and troubleshooting

* docs: align deployment initialization with develop

* fix(app-host): keep a stopped deployment from serving requests

Stopping a managed deployment only evicted the App runtime and left its
definition enabled, so the next request through the Host cold-started the
App again. Disable the definition when a deployment is stopped, both from
stopDeployment and from a deployment set whose desired state is stopped,
and re-enable it in startDeployment before the definition is read back.

A stopped App now answers 404 through the Host until it is started again.
Covered by a management test with a real in-process runtime and by the Hub
plugin's CLI-to-Host publishing test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test: cover the deployment lifecycle from build to rollback

Deployment was documented far more than it was tested. Add tests for the
parts that only the guide described:

- app-tools: real spawns of pack-dist, build-server-dist-package,
  verify-server-deps, prune-dist-artifacts, the build hook stages and the
  missing-dist hint of the start launcher.
- smoke-create-app.sh: build with --tar, extract the archive into a fresh
  directory and boot it with node dist/server/standalone.js, then retarget
  for another platform and check the recorded build target; the node:test
  suite drives the new steps and their failure paths.
- docker-hub.yml: build the Hub image for the runner and probe its health
  endpoint before the multi-arch push.
- app-host: a cli.test.ts for startAppHostFromEnv.
- app-plugin-hub: publishing-e2e.test.ts runs publishToHub from
  @nocobase/app-cli against a real Hub router and a real App Host child,
  covering upload, deploy, stop, start and rollback.
- templates: assert the three ecosystem.config.js files stay aligned and
  document pm2 in the standalone guide.

Also align the deployment docs with the shipped config.example.yml: the
SQLite path is the `database` field, with `filename` still accepted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore: add changeset for the Hub plugin test dependency

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Revert "fix(app-host): keep a stopped deployment from serving requests"

This reverts commit df748369d2bd2fda250f9a849919bccd516bc3f2.

* test: keep the Host stop gap visible and refine smoke comments

The app-host fix moves to its own pull request, so the publishing test
marks the stopped-App expectation as a known failure again. Also clarify
why the Docker smoke build does not share layers with the multi-arch push
and why the extracted archive gets its own SQLite database.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs: add Hub console screenshots to the deployment guide

Illustrate first login, application creation, release upload, the deploy
dialog's release and configuration steps, deployment records and logs,
and updating with a second release. Images are hosted on the docs CDN.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs: align the deployment guide with the implementation and ship its skill

Fact-checked every page against the source. Corrections: the reverse
proxy section no longer claims forwarded headers drive external-origin
detection (that is APP_PUBLIC_ORIGIN); storage holds database, files and
logs while sessions default to memory; a placeholder secret fails startup
rather than merely needing replacement; the configuration mode is named
"配置文件" as in the Hub UI; stopping an App is described with the current
re-activation caveat until the app-host fix lands.

Additions readers would otherwise discover the hard way: the healthz
endpoint in acceptance, systemd and compose examples; NOCOBASE_STRICT_STARTUP
so restart policies fire; the /main default mount path; Secure session
cookies under NODE_ENV=production; dist/.env carrying whitelisted keys;
APP_CONFIG_FILE resolving relative paths against dist; App ID rules and
fixed /<id> paths; one deployment at a time; a successful deployment
re-enabling a stopped App; --timeout and result.reused for CI; rollback
config-mode matching; what removal deletes; run-<id> image tags and the
SQLite-only official image; Hub request logs and log file naming.

The English index now lists all six chapters with the navigation labels.

Commit the nocobase-deployment skill under .agents/skills, the committed
location for repository skills, with its workflow-artifact error message
quoted verbatim and the health endpoints added to verification.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs: align deployment guide details with the current implementation

- Describe the healthz response as a JSON object whose `ok` is true instead
  of a bare `{"ok":true}`; the route also returns the app name and base path.
- Stop asserting that a `latest` Hub image tag exists. Explain which tags the
  beta release pipeline pushes and ask readers to verify a tag before use.
- Keep the Hub template's relative SQLite path; it resolves under
  HUB_STORAGE_DIR, so rewriting it to an absolute container path is optional.
- Distinguish rotating runtime logs from per-deployment journals, which use a
  single file per deployment and stop at the size limit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 23:27:54 +08:00
PiEgg 9dbe225232 ci: parallelize application smoke checks in quality and releases (#332) 2026-09-20 17:56:40 +08:00
PiEgg e819257048 ci: select database integration jobs by changed paths (#335) 2026-09-20 17:27:33 +08:00
PiEgg e819ad3b31 fix(templates): make scaffolded application tests portable (#328) 2026-09-20 16:10:30 +08:00
chenos 133809c55a ci: publish Hub Docker image after beta releases 2026-09-16 22:26:54 +08:00
chenos 491d14d274 fix: disable Hub image attestations for Alibaba Cloud compatibility 2026-09-16 20:20:56 +08:00
chenos 5c6d2c5c14 fix: always publish Hub images to both registries 2026-09-16 20:04:47 +08:00
chenos bf91c7d664 feat: add Hub playground and Alibaba Cloud image publishing 2026-09-16 20:02:08 +08:00
chenos c606ff3c70 feat: add Hub Docker image and manual multi-platform workflow 2026-09-16 18:57:37 +08:00
chenos d927494c3f fix(templates): align application framework with Default (#250)
* fix(templates): align application framework with Default

* fix(templates): address settings access and review gaps

* test(hub): await the real API keys page loader

* fix(hub): start installed hosts in development

* fix(hub): preserve default application identity in generated config
2026-09-16 17:43:17 +08:00
PiEgg 47dba71f75 ci: smoke test generated apps in dev and production on PRs (#239) 2026-09-15 20:44:13 +08:00
PiEgg 74c98c22c0 ci: skip checks for automated release commits 2026-09-15 18:11:59 +08:00