Commit Graph
88 Commits
Author SHA1 Message Date
PiEgg e6dd56e8fa docs: keep AGENTS.md small and move detail into .agents/rules (#10791) 2026-10-12 19:47:48 +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
PiEgg c41598de14 ci(v3-quality): run Quality in chenos/nocobase-ci through nocobase-bot (#10726) 2026-10-10 22:41:46 -04:00
chenos a827a6363c docs: clarify application access URLs for agents (#10728) 2026-10-11 09:45:36 +08:00
PiEgg a9396a8d29 fix(ci): run v3 checks for named stacked PR targets (#10701) 2026-10-10 09:48:04 -04:00
YANG QIA 0cc0327806 feat(studio): import NocoBase Studio into packages/apps (#10666)
NocoBase Studio moves from the private nocobase/studio repository
(archived at e4a18fd) into packages/apps/studio and is published as
@nocobase/studio. The framework submodule, the mirrored workspace file,
the lockfile and the submodule check go away; Studio now resolves the
framework packages and plugins from this workspace.

- packages/apps is a new package category for product applications.
  check-openapi checks Studio, and the database test portability check
  covers it; Studio's tests now take their databases from
  @nocobase/app-testing.
- A new migration, 202610220010_studio_rename_package, moves the
  migration and seed history recorded under the old package name
  `studio` to `@nocobase/studio` and rewrites the i18n namespace of
  workflow definitions copied from the software template. Released
  migrations are unchanged.
- Studio follows the framework changes since its last submodule
  pointer: the workflow plugin and the client's dev and settings route
  surfaces are gone.
- Prepared for publication: internal infrastructure, ticket keys and
  references to the predecessor product are removed, the demo data is
  in English, and tests are no longer published.
2026-10-10 19:53:49 +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
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
chenos 71dbc34542 feat(lifecycle): add record lifecycles with ticket, expense and office flow examples (#519)
Add record lifecycle definitions, transactional transitions, durable effects, recovery, jobs integration, and React bindings. Include ticket, expense, and office flow examples in the Examples application, with concurrency and database compatibility fixes.
2026-10-08 20:55:03 -04:00
chenos 8a05e6658a feat(create-plugin): group generated tests by source directory and record the test layout (#551)
Group generated plugin tests by source directory, provide shared Node test configuration, and document the layout. Include TSX tests in Node projects with a regression test for test discovery.
2026-10-08 18:14:44 -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 e123790fef fix: move packages with breaking changes to the 2.0.0 prerelease line and reject swallowed majors (#538)
Moves app-server, app-client, authentication, users, hub, workflow and ai-employee to 2.0.0-beta so their next release is 2.0.0-beta.0, republishes their 32 internal dependents, and makes validate-changesets reject a major changeset on an X.0.0-beta.N package while in prerelease mode.
2026-10-05 11:09:21 +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
PiEgg 3f01f61679 refactor: standard /api error body, request ids and the HTTP API specification (#530) 2026-10-04 10:29:22 +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 be0fbbd9ca feat(db-testing): portable database tests for packages outside db, with pnpm test:db (#523)
* feat(db-testing): portable database fixtures for tests outside the db packages

Add @nocobase/db-testing: tests run on in-memory SQLite by default and on
the dialect NOCOBASE_TEST_DB_DIALECT names otherwise, each file isolated in
databases of its own. createDatabaseTest() gives a Vitest test with migrated
databases, describeMigration() applies, rolls back and reapplies a migration
with metadata and table checks, and expectCollection() asserts on the schema
by Field and Collection name.

Dialect packages own provisioning: @nocobase/db/testing defines
TestDatabaseProvisioner, and db-sqlite, db-postgres and db-mysql export one
from a ./testing entry, with integration tests for the server dialects.

The scheduler plugin's migration tests move to describeMigration and no
longer use PRAGMA.

* chore(changeset): cover the scheduler's new development dependency

* chore(db-testing): keep the build output out of the format check

* fix(db-testing): leave the primary key's own index out of a Collection snapshot

PostgreSQL reports the index behind a primary key as a unique index of its
own, SQLite does not, so the same migration produced different snapshots.
The package's own tests also asserted the SQLite dialect by name; they now
compare against the selected one and pass on PostgreSQL and MySQL.

* fix(db-mysql): default test connections to the examples template's MySQL service

The examples application's docker-compose.yml publishes MySQL on 3306 and
reads MYSQL_PORT, as the provisioner does; defaulting to 13306, which only
the integration adapter used, meant starting that service was not enough to
test on MySQL. The db-testing README points at the compose file.

* feat(db-testing): remove the isolated databases an interrupted run left behind

Isolated databases are now named nbt_<host>_<pid>_..., and the first
provisioning in a process drops every nbt_ database carrying this machine's
host tag whose process no longer exists. Other machines' databases on a
shared server and those of runs still in progress are left alone, and a
failure to clean up only warns.

TestDatabaseProvisioner gains optional listProvisioned and dropProvisioned,
implemented by PostgreSQL and MySQL and covered by their integration tests.
provisionTestDatabases() accepts a provisioner directly.

* test(notification, workflow): run the existing dialect tests through db-testing

The notification migration-runner test read NOTIFICATION_MIGRATION_DIALECT,
which no script set, so it never ran; the workflow integration tests read
INTEGRATION_DB_CONNECTIONS and built their own connections. Both now take
their database from @nocobase/db-testing and run on SQLite by default and on
whatever NOCOBASE_TEST_DB_DIALECT selects.

The workflow tests pass on PostgreSQL and MySQL, including the MySQL-only
connection time zone test. The notification test passes on SQLite and MySQL;
on PostgreSQL its rollback fails because the idempotency migration's down
drops a partial unique index as a constraint, a released defect left for a
follow-up in @nocobase/db.

expectCollection().toHaveField() also compares a string Field's length.

* feat(db-testing): read the selected dialect's capabilities before any database exists

testDatabaseCapabilities() resolves them from the capabilities the dialect's
driver declares, which TestDatabaseProvisioner now carries, merged with the
defaults @nocobase/db applies. A test file awaits it at the top level and
passes it to test.skipIf(), so skipping is decided while tests are collected;
a test checks the result against what a live connection reports.

This replaces the planned Vitest plugin, whose only remaining purpose was to
hand capabilities to workers through provide()/inject().

The workflow plugin's Vitest alias for @nocobase/db matches the bare
specifier only, so it no longer rewrites @nocobase/db/testing.

* feat(db-testing): run packages' tests on a dialect with pnpm test:db

pnpm test:db <dialect> --filter <package> [-- <test arguments>] starts the
dialect from its package's Compose file in a disposable project with a random
port, runs the filtered packages' test scripts one after another with
NOCOBASE_TEST_DB_DIALECT and the service address set, and removes the project.
SQLite starts nothing.

@nocobase/db-testkit's runner splits the service lifecycle from the command
into runWithDatabaseService(), which runDatabaseIntegration() now uses. Each
server dialect declares its Compose service once, in
scripts/integration-service.ts, read by both its integration suite and
pnpm test:db.

* chore(changeset): cover the dialect packages' new integration service scripts

* feat(db-sqlite): provision SQLite test databases as files

An in-memory database is private to one Database Manager, so a test that
restarts an application on the database the first start seeded, or opens a
second connection to it, found it empty on SQLite while it worked on every
server. Each isolated database is now a file under
NOCOBASE_TEST_DB_SQLITE_DIRECTORY or the system's temporary directory, and
SQLite implements listProvisioned/dropProvisioned so an interrupted run's
files are removed too. The workflow and ai-employee suites run as fast as
on :memory:.

* fix(test-db): run every filtered package even after one fails

pnpm stops a recursive run at the first failing package, which hid the
results of every package after it on the dialect under test.

* test(plugins, examples): take test databases from db-testing

The database tests of 21 plugin and example packages no longer configure an
in-memory SQLite database. Helpers and test files get their databases from
createTestDatabase(), provisionTestDatabases() or createDatabaseTest() and
destroy them afterwards; PRAGMA and sqlite_master assertions are written
with expectCollection() against Field and Collection names; migration up and
down tests use describeMigration(); the SQLite triggers that made a write
fail in hub and authentication are replaced by spies on the write. Each
package drops its @nocobase/db-sqlite development dependency.

The departments example's seed replay test reopens the database the first
start seeded, which the file-backed SQLite provisioner now allows. All 21
packages pass on SQLite; running them on PostgreSQL and MySQL exposes
failures that are investigated separately.

* fix(departments-example): fit restriction rule assignment ids in their column

The organization seed built assignment ids up to 69 characters long for a
64-character column, so seeding failed on PostgreSQL and MySQL and the
application did not start. The shared example-public- prefix of the rule key
is left out of the id; the seed matches existing assignments by rule and
subject, so an installation seeded on SQLite is unaffected.

* fix(authentication, workflow): search regardless of case on every database

User administration's search and the workflow list query used the default
string mode, which follows the database's comparison: case-insensitive on
SQLite and MySQL, case-sensitive on PostgreSQL. Both pass
{ mode: 'insensitive' } now, as their in-memory matching already did.

* test(repository-example, notification): state the portable contract on every dialect

AVG results keep each database's decimal places, as @nocobase/db documents,
so the repository example compares them as exact decimals rather than as
SQLite's strings. A unique constraint that SQLite builds as an index is
reported under constraints only on PostgreSQL and MySQL, so the notification
and in-app notification migration tests look for it there.

* test(hub, ai-employee): give tests the schema an application has on every dialect

PostgreSQL and MySQL check what SQLite never did, and the tests relied on it:

- hub's authorization and role-permission tests now apply the api-keys
  migrations an application runs before hub's, whose foreign key needs
  the apikey table; ai-employee's tests apply authentication's, whose
  user table its conversations and usage events reference.
- hub's service tests destroyed their database only when every teardown
  step succeeded, leaking one per test until MySQL refused connections.
- the concurrent administrator deletion test boots UsersProvider, whose
  user subject is what makes a deleted user stop counting as active.
- ai-employee fixtures use a UUID where the column is a uuid, a numeric
  id generator for bigint keys, and compare decimals by their digits
  rather than SQLite's representation.

* fix(db): bind compared temporal and boolean values in update and delete predicates

A select's where clause encoded a value compared with a boolean Field by the
Field's type, but temporal Fields were not encoded anywhere, and an update's
or delete's where clause had no Collection in scope, so neither was encoded
there. MySQL rejected an ISO instant compared with a DATETIME column, which
broke notification delivery leasing, although the same string was accepted
as a written value.

Comparisons and between bounds on temporal and boolean Fields are now bound
like writes, and update and delete queries resolve their Collection to do it.
A shared contract test covers it on every dialect.

* fix(ai-employee): let the repository generate tool message ids

The message store gave tool messages and interrupted tool replies random
UUIDs for their bigint id columns. SQLite stored them; PostgreSQL and MySQL
rejected the insert, so every agent tool call failed to save there. The
repository already generates these ids from the application's id generator
when none is given.

* fix(hub): fit the release artifact key's unique index within MySQL's limit

artifactKey was varchar(1024) with a unique index, beyond MySQL's 3072-byte
key limit under utf8mb4, so the plugin's tables could not be created on
MySQL. It is now varchar(512); keys are at most 172 characters.

The released migration is edited deliberately: the failing statement is in
it, so no later migration could get a MySQL installation past it, and it
never succeeded there. Installations that already ran it keep the wider
column and see a checksum warning that db repair clears.

* fix(db): drop a partial unique constraint as the index it is

A unique constraint with a predicate exists only as a partial unique index,
but dropConstraint compiled it to ALTER TABLE ... DROP CONSTRAINT, which
PostgreSQL refuses, so notification's idempotency migration could not be
rolled back there. The compiler now drops it as an index, recognising it by
its predicate or, in a definition resolved from the database, by an index of
the same name. Covered by a unit test and by the shared partial-unique
contract on every dialect that supports partial indexes.

* fix(db-mysql): run transactions at READ COMMITTED

Under MySQL's REPEATABLE READ default, a transaction's snapshot is taken at
its first read, and every transaction starts by reading Collection metadata,
so a check made after taking a lock still counted rows a concurrent
transaction had removed: two Hub administrators could delete each other.
Each pooled connection now sets its session to READ COMMITTED, the level
PostgreSQL, SQL Server, Oracle and OceanBase default to.

* test(hub): register the user subject in the role scope tests

Without UsersProvider's user subject every assignment counts as active, so
whether a disabled administrator still protected the last one depended on
which of the two concurrent transactions took the lock first; MySQL ordered
them the other way and both succeeded.

* fix(db): encode compared temporal values only where a stored value is meant, and share the SQL test provisioner

Comparisons with a temporal Field ran the write-side validator on every operator, so a `like` pattern, a date without a time or an ISO string without seconds threw a RepositoryError instead of reaching the database as before. Only `= != <> < <= > >= in not in` encode the value now, and a string that is not a complete V1 literal is bound as given. The shared mutation contract test covers a date-only comparison and a `like` pattern.

Update, delete and insert resolve the Collection once instead of looking it up for the write fields and again for the comparison scope.

`@nocobase/db/testing` exports `createSqlTestDatabaseProvisioner()`; the PostgreSQL and MySQL provisioners declare only their connection options and three statements, and every administrative statement runs on a connection opened for it and closed afterwards.

* fix(db-testing): isolate concurrent tests, empty supplied metadata stores, and check rolled-back schemas field by field

A test running concurrently with its siblings gets databases of its own instead of resetting the file's shared database under them; migrations and seeds of every test database in one process run one after another, because the runners' in-process lock is keyed by connection name rather than by database. `reset()` empties a metadata store the caller supplied, whose Collection documents otherwise outlived their tables. `verifyMigration` compares every table's Fields, indexes and foreign keys after rollback, not only the list of tables.

Vitest becomes an optional peer that only the `./vitest` entry needs, exported constants carry explicit types, and a dialect package that is installed but predates its `./testing` entry is told to upgrade rather than to be added. The scheduler's foreign key test asserts `onDelete: 'restrict'` again, and AGENTS.md records the one exception to never editing a released migration.

* test(db-testkit): assert pass-through comparisons only where the database understands them

The date-only comparison and the `like` pattern on a temporal Field describe what a database does with a value the Field could not store, which is the database's own business: Oracle's session timestamp format refuses a date without a time, and the PostgreSQL family has no `like` operator for a timestamp. The contract is that the value reaches the database as given, so each assertion runs on the dialects whose behaviour it describes.

* test(workflow): give the tests that compile workflows room on a loaded runner
2026-10-03 10:17:12 -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
DrolandClaude Opus 5.5 aeff80a1d2 feat(jobs)!: add @nocobase/jobs and move Scheduler onto it (#463)
* feat(schedule): scaffold the @nocobase/schedule package (T001)

Adds packages/libs/schedule on the shared dev-config presets with BullMQ
6.3.6, cron and cron-parser as dependencies, lists it among the
isolatedDeclarations libraries, and records msgpackr-extract, which
arrives through BullMQ, as a deliberate build skip both here and in the
allowBuilds that create-app writes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(schedule): add the service contract, configuration fallback and validation (T002)

Exports the ScheduleExecuteService, ScheduleExecutor and job types from
the plan and createScheduleExecuteService. The service resolves a named
configuration, then schedule.default, then a built-in memory
configuration it reports through onFallback; returns one executor per
(scope, configuration); and rejects a repeated call whose overrides
change the effective concurrency or attempts. Scopes, job names and job
options are validated before anything reaches an adapter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(schedule): add the in-memory scheduler (T003)

Adds the executor shared by both adapters: it registers handlers, holds
jobs added before setup() and writes them there, skips a write whose
rule, payload and execution settings are unchanged, passes immediately
only for a new rule, keeps handlers after removeJob, fails firings
without a handler as handler-not-registered, and delivers subscriber
events in order.

The memory adapter plans firings with cron-parser and fires them through
one-shot cron jobs created from a Date. Every rule, its next firing and
its firing count persist to a versioned state file per namespace and
scope, written atomically; a missed firing runs once after a restart. An
exclusive lock file refuses a live holder or another host and takes over
a lock left by a crashed process. A cron rule without tz runs in UTC.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(schedule): add the redis adapter on BullMQ job schedulers (T004)

The namespace is the queue prefix and the scope the queue name. Rules
map to upsertJobScheduler with a template carrying the payload, attempts
and retention, compared through getJobScheduler before any write. A
worker runs firings with the configured concurrency and reports its
local active, completed and failed events; a firing without a handler
fails as an UnrecoverableError and is reported as handler-not-registered.
Shutdown aborts running handlers and waits for them before closing.

Only public Queue, Worker and Job API is used. The planned time is
job.timestamp plus the delay in job.opts, because BullMQ zeroes the
job's own delay once the job leaves the delayed set.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(schedule): run the redis adapter against a real Redis (T005)

Adds test:integration, which starts Redis in a disposable Compose
project, runs tests/integration against it and removes the project. The
suite covers one execution per firing across instances, continuing on
another instance after a shutdown, planned and next firing times,
skipping an unchanged write so a due firing survives, a changed rule
replacing it, immediately firing once, re-adding a removed job, a
missing handler failing without retry, retries, shutdown waiting for a
running handler, limit, and listing.

Measuring against BullMQ 6.3.6 changed two things in the adapters. An
end date already in the past is rejected by upsertJobScheduler, so both
adapters now remove such a rule instead of writing it. A scheduler whose
limit is spent keeps reporting its last planned time as next, so a next
firing that is not after the current one is reported as none.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(app-server): compose the schedule service under @nocobase/app-server/schedule (T006)

Adds scheduleExecuteServiceToken, AppScheduleConfig and
ScheduleExecuteServiceProvider. The provider builds the service from the
schedule section with the application name as the default namespace and
storage/schedule as the built-in memory location, reports a fallback to
that configuration outside develop and development, starts nothing, and
shuts down whatever executors their owners left running.
@nocobase/schedule is a peer, provided by the application.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(app-plugin-scheduler)!: schedule through @nocobase/schedule (T007)

Scheduler now hands each definition's rule to its own executor from
@nocobase/app-server/schedule, on the @nocobase/app-plugin-scheduler
scope with concurrency 1 and a single attempt, instead of the queue's
schedule projection and ScheduleDispatchJob.

- A new migration adds nextRunAt, lastRunAt, runCount, appliedLimit and
  lastOccurrenceId to scheduleDefinitions and scheduledAt to
  scheduleOccurrences; the published migration is unchanged.
- Startup registers every definition's handler, adding rules for the
  enabled ones, before setup(); sync-only mode sets the executor up
  without consuming and starts no reconciliation.
- A rule is rewritten only when its definition changed, carrying the
  limit left after the firings already run; disabling removes the rule
  and keeps the handler.
- The occurrence id is the firing's job id; starts count once per
  occurrence; a target that is not ready now records a skipped
  occurrence instead of failing with OCCURRENCE_NOT_FOUND.
- Shutdown clears the reconciliation timer, unsubscribes, and waits for
  the executor.

BREAKING CHANGE: ScheduleDispatchJob is no longer exported, the plugin
contributes no queue jobs, and nothing is migrated from queue_schedules
or queue_jobs. Applications must compose ScheduleExecuteServiceProvider
and provide @nocobase/schedule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(templates): compose the schedule service in every template (T008)

Each template adds ScheduleExecuteServiceProvider with the application's
NODE_ENV, a server/config/schedule.ts offering memory and redis
configurations without choosing a default, and @nocobase/schedule as a
production dependency, and drops the queue that existed only for the
Scheduler. config.example.yml documents the section, and the deployment
Skill covers choosing the backend for one or several instances.

Tests that start applications now keep schedule state in their own
temporary directory: two suites starting apps on the template's
storage/ at the same time are two processes on one memory state file,
which the adapter's lock refuses. The deployable dist records
msgpackr-extract as a deliberate build skip, as the repository and
create-app already do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor!: replace @nocobase/cron with @nocobase/schedule (T009)

Removes packages/libs/cron and the dependency the default and examples
templates declared on it; nothing imported it. The application
development Skill's section on work that runs on a schedule now uses an
executor from scheduleExecuteServiceToken under the application's own
scope, set up in start() and shut down in shutdown(), and explains that
the redis adapter, not a hand-written lock, is what keeps several
instances from firing the same job. The cron catalog entry stays for
@nocobase/schedule.

BREAKING CHANGE: @nocobase/cron is no longer developed here; its
published 0.1.0 remains installable.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(scheduler): describe scheduling on the schedule service (T007)

The Scheduler capability page still described the queue's schedule
projection: a schedule queue worker, queue_schedules, a paused queue
schedule, and limit as queue claims. It now says where schedules run —
the schedule configuration, memory on one host unless schedule.default
names redis — in both languages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(changeset): describe the schedule service and the Scheduler move to it (T010)

Adds changesets for @nocobase/schedule and @nocobase/app-server, the
breaking Scheduler change with no migration from the queue tables, the
templates and app-skills replacing @nocobase/cron with
@nocobase/schedule, and the msgpackr-extract build skip in create-app
and app-tools.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(schedule): share the memory adapter's state across the processes of one host (T011)

The memory adapter held an exclusive lock for its whole lifetime, so a
second process — nb3 schedule:sync while the application ran, or a PM2
cluster — was refused. It now reads its state file without a lock, since
the file is replaced by rename and always whole, and takes a short
exclusive lock only for each read-modify-write, reading the file again
inside it. Each consuming process arms every planned firing and claims
it under the lock by advancing the rule past it, so a firing runs once
among them, and polls the file's signature to pick up rules the others
changed. A lock left by a process that no longer runs, or held far
longer than a write takes, is taken over.

Every write records a revision and its writer; a file another host
writes after this process started is refused, since a shared file system
gives neither atomic rename nor exclusive create reliably. The executor
passes its unchanged-rule check to the backend so memory decides it
again under the lock.

Documentation, Skills, the deployment guidance, the templates' comments
and the changesets now describe memory as one host rather than one
process.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor(schedule): keep the memory adapter's state in memory and write it on shutdown (T012)

The memory adapter now reads its state file once, in setup(), changes
only its memory while it runs, and overwrites the whole file in
shutdown(), after it stops firing and running handlers finish. This
replaces the shared, lock-guarded file of T011: there is no lock, each
process holds its own copy, and the one that shuts down last overwrites
the file. A process that ends without shutting down loses what changed
since it started, and a setup() that could not read the file never
overwrites it.

The lock module, the file's revision and writer, the signature poll,
the executor's unchanged-rule argument, the multi-process fixture and
its tsx dependency are removed. Documentation, Skills, deployment
guidance, template comments and changesets describe memory as one
process whose state reaches storage when the application stops.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(app-plugin-scheduler): remove stale rules again on every synchronization (T013)

Synchronization only ever added rules; it removed one only when it
deactivated a definition itself or an administrator disabled it. A
removal that was lost stayed lost: on the memory adapter a running
application overwrites the state file nb3 schedule:sync --finalize wrote,
so the rule of a definition removed from code came back and failed as
handler-not-registered at every firing.

Every synchronization now removes again the rule of each disabled
definition in the manifest and of each definition already inactive,
once the executor is set up. It reads the definition again first, so a
schedule another instance enabled in the meantime keeps its rule. An
active definition a partial manifest leaves out is still left alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(schedule): explain how applications and plugins use the schedule service

The README now covers when to use the package rather than the Scheduler
plugin or a queue job, how an application composes it, the schedule
configuration and how a consumer's configuration is chosen, the provider
pattern for plugins and application code with its scope, ordering and
shutdown rules, dependency declarations, job options and their edge
cases, receipts, handlers and orphan rules, events, both adapters'
guarantees and limits, and testing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(examples): add a plugin running a recurring job on the schedule service

@nocobase/app-plugin-schedule-example shows the pattern the schedule
README describes. Its provider takes an executor of its own from
scheduleExecuteServiceToken under its package name, subscribes to its
events, registers a heartbeat that runs every minute, sets the executor
up, removes the rules of jobs it no longer defines, and shuts the
executor down with the application. The handler records each run from
its own context, since events arrive after the fact. An authenticated
GET /api/schedule-example returns the next firing and the recent runs.

The examples template registers the plugin and covers the route end to
end on the application's schedule service.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: follow the consolidated app-cli after rebasing onto develop

@nocobase/app-tools is now part of @nocobase/app-cli, so the
msgpackr-extract changeset covers app-cli, whose dist workspace carries
the entry. The Scheduler's command is nocobase scheduler sync in the
changeset and the capability page.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor!: rename @nocobase/schedule to @nocobase/jobs and let Scheduler choose its configuration

The package moves to packages/libs/jobs as @nocobase/jobs, its
configuration section from schedule to jobs, and the built-in memory
configuration's state from storage/schedule to storage/jobs, so the
section no longer reads like the Scheduler plugin's. Nothing was
published under the old name.

Scheduler gains a scheduler section: scheduler.jobs, or SCHEDULER_JOBS,
names the jobs configuration its schedules run on, and without it they
follow jobs.default. The jobs service would quietly fall back to the
default for a name it does not know, so Scheduler refuses to start
instead. The default and examples templates declare the section.

@nocobase/app-server/schedule and the ScheduleExecute* identifiers keep
their names for now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(jobs): report the planned time BullMQ stored in addJob receipts

upsertJobScheduler returns a job whose timestamp and delay come from two
clock readings, one in JavaScript and one in its script, so their sum
could miss the planned time by a millisecond even for a cron rule. The
receipt now reads the scheduler's own next, which is the planned time
itself, and falls back to the job only when there is none. The Redis
suite's check that a replaced firing runs at the receipt's time failed
on that millisecond.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor(jobs): rename JobExecutorService and use configured executor settings

* fix(app-cli): resolve native platform packages from declared dependencies

* fix(jobs): exclude optional native accelerator and revert retarget changes

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 12:09:24 -04:00
Zeke ZhangandClaude Opus 5.5 db169452d1 refactor(toast)!: report toasts through a toaster the application implements (#462)
* refactor(toast)!: report toasts through a toaster the application implements

After #436, plugins called Base UI's Toast.useToastManager() directly,
which tied every plugin to one UI library: changing the toast UI meant a
major release of each plugin, a page rendered without a Toast.Provider
failed, and plugins carried Base UI details such as priority. The toast
contract now lives in @nocobase/app-client and the application supplies
the implementation, as Refine's notification provider did.

- app-client exports toasterToken, useToaster() and resolveToaster().
  Without a registered toaster a toast shows nothing and the first one
  warns in the console; nothing throws.
- The templates add client/lib/toaster.ts, which forwards to the Base UI
  toast manager and decides priority for the whole application, and
  register it in client/service-provider.ts. The account menu, the
  language switcher and the Examples overlay demo use useToaster().
- Hub, Users, Workflow and AI employee report through useToaster() and
  no longer choose a priority; their tests mock useToaster rather than
  Base UI.
- Template tests that stub the application's services gain has(), which
  useToaster() calls.
- The frontend references, the plugin Skills, the plugin development
  Skill and the nocobase-app-upgrade edge cases describe the toaster.

BREAKING CHANGE: the new Hub, Users, Workflow and AI employee versions
need the matching @nocobase/app-client, and the application must
register a toaster under toasterToken; without one their toasts show
nothing. The steps are in the nocobase-app-upgrade Skill's
"Notifications and the application toaster" edge case.

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

* fix(toast): address review of the toaster contract

- The fallback for an application that registers no toaster logs every
  toast it drops, error toasts with console.error, and says how to
  register one the first time, instead of warning once without saying
  what was lost. The docs no longer claim pages keep working without a
  toaster: a Hub page that reports an error only through a toast shows
  nothing.
- The contract states that clicking a toast's action leaves it open, as
  Base UI does, and that replacing a toast by id does not run the
  replaced content's onClose.
- The template adapters ignore close() without an id, which Base UI
  takes as closing every toast, and describe the priority rule as it is
  implemented: any error that is not plain text keeps the default.
- The adapter tests cover the default wiring to the mounted Toaster and
  render and click an action; the template alignment test compares
  client/lib/toaster.ts.
- The upgrade guide, the changeset and the plugin Skills say that
  `pnpm nocobase plugin update` installs the new plugins without
  updating @nocobase/app-client, and the plugin Skills spell out the
  registration instead of only naming a section an older app-skills
  lacks.
- The plugin development Skill keeps describing the released API and
  changes after the release that ships useToaster(), as AGENTS.md
  requires of a global Skill.
- AGENTS.md lists toasterToken among app-client's identity-keyed exports.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:39:31 +08:00
chenosandli'mac 9f75a27b15 refactor(cli): build the --json envelope and the Node.js guard from one @nocobase/cli-envelope package (#474)
* refactor(cli): build the --json envelope and the Node.js guard from one @nocobase/cli-envelope package

`@nocobase/app-cli`, `@nocobase/app-installer`, `@nocobase/create-app` and `@nocobase/create-plugin` each kept a copy of the `--json` envelope and of the Node.js version guard their `bin/run.js` runs before loading anything else. The copies had drifted once already, and a cross-package parity test was the only thing holding them together.

The new `packages/libs/cli-envelope` holds both: the envelope's types and builders in `src/index.ts`, and the guard in `node-guard.js`, plain JavaScript with no imports so that it runs on the Node.js it is there to refuse and on the TypeScript sources a development checkout runs from. `formatCommandLine` and `renderSuggestion`, which app-cli and app-installer also each had, move with them. The four packages declare it as an ordinary dependency: its envelope mark is a registered symbol, so two copies agree, and nothing else in it holds state.

The parity test stays, now comparing each tool's thin wrapper with the application CLI's document. `AGENTS.md` names the package as where the envelope is defined. app-cli's guard now answers `--json` with the `NODE_UNSUPPORTED` document like the other tools, and app-installer's guard takes the first non-flag argument as the command.

* refactor(cli-envelope): derive the guard's command, check node-guard.js against its declarations, vendor published files

Review follow-ups for the shared envelope package.

- `unsupportedNodeVersionOutput` names the command from the arguments before the first flag, through the new `commandFromArgv`, unless the tool names one. app-cli and app-installer drop their own derivations; the installer's took a leading flag's value for the command.
- `tsconfig.node-guard.json` checks `node-guard.js` under `checkJs` and the tests with it, and `node-guard.js` assigns its exports to `typeof import('./node-guard')`, so the hand-written declarations cannot drift from the JavaScript.
- `build-server-dist-package.mjs` vendors every path a workspace package's `files` names rather than `dist` alone, so the `./node-guard` export of the vendored `@nocobase/cli-envelope` no longer points at a missing file.
- The parity test keeps one comparison of the shared guard against the application CLI's failure document instead of three identical ones; `formatError` reads an InstallerError's details directly; AGENTS.md and the AppCommand header no longer describe the per-tool copies this branch removed.

---------

Co-authored-by: li'mac <limac@Lis-MacBook-Air.local>
2026-09-27 20:38:50 -07:00
chenosandli'mac 29ba7e5a79 docs(agents): say that renaming a branch under an open pull request closes it (#472)
The branch rule said that renaming a branch once a pull request is open means renaming it on GitHub too, which reads as if the pull request follows. It does not: GitHub's branch rename closes the pull request as a head-branch deletion, and a pull request's head branch cannot be changed afterwards, so the change has to be reopened from the renamed branch. This is what happened to #466, reopened as #470.

Co-authored-by: li'mac <limac@Lis-MacBook-Air.local>
2026-09-27 19:25:23 -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
chenosandli'mac e1e88069f3 docs(agents): name branches by change type and drop AI attribution (#467)
Branches are named feat/<name> or fix/<name>, or by another
Conventional Commits type for a change that is neither, and never
pushed under a tool's own prefix such as claude/ or codex/.

Commit messages and pull request descriptions carry no attribution to
an AI tool. A committed .claude/settings.json turns Claude Code's own
attribution off for everyone working in the repository.

Co-authored-by: li'mac <limac@Lis-MacBook-Air.local>
2026-09-27 17:27:41 -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 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 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
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
Zeke ZhangandClaude Opus 5.5 f38ae767e1 docs(app-skills): move the frontend topic references into references/frontend/references (#415)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:17:33 -07:00
Zeke ZhangandClaude Opus 5.5 8240685485 docs(app-skills): replace the frontend references with a workflow, UI guidelines and handbook (#409)
* docs(app-skills): replace the frontend references with a workflow, UI guidelines and handbook

Frontend guidance in the application development Skill now lives in
references/frontend/. ui-workflow.md is where every change under client/
starts: it chooses between a full workflow (a design file reviewed once,
then an independent acceptance review) and a quick change (edit directly,
static checks only). ui-guidelines.md holds numbered Must/Should rules for
designing and reviewing pages, and frontend-dev.md routes each task to a
topic document with complete examples, written against the current
default template. The workflow ships fill-in templates and a Playwright
screenshot script.

The eight references these documents replace are removed. i18n.md keeps
server-side text, the offered languages, the default language and
fallback; testing.md, server-routes.md, SKILL.md and each template's
AGENTS.md and CLAUDE.md point at the new documents.

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

* fix(app-skills): declare the screenshot script's imports as optional peers

`references/frontend/scripts/capture.mjs` ships in the package and imports
`@playwright/test` and `prettier`, which `pnpm deps:check` requires to be
declared. The script runs inside an application, which owns the copy it
resolves (both are template devDependencies), so they are optional peers,
with matching devDependencies here.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 10:26:55 +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 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
chenos b00290dcfa fix(app-tools): declare the tools it spawns and gate the TypeScript load (#382)
`dev` spawns `vite`, `tsx` and `nocobase`, and only two of them were
declared. Neither `deps:check` nor `peers:check` could catch it, because
both read import specifiers and a spawned binary has none, so both now
cover `packages/tools`, the README carries a table of what these scripts
spawn, and AGENTS.md records the rule.

`typescript` becomes a peer so the parser reading `server/plugins.ts` and
the `tsc` the build invokes stay on one version, and the parse is gated on
a workspace package being named in the file, which keeps 24 MB of compiler
out of every `pnpm dev` answered with nothing to watch.

`cross-spawn` and `tar` move to the catalog, settling `tar` on one range,
and the templates drop the two declarations nothing in them imports.
2026-09-22 20:48:44 +08:00
KatherineandClaude Fable 5.1 2ae6b2b722 docs: ship the deployment Skill with applications and finish the deployment guide (#376)
* docs: ship the deployment Skill with app-skills and close the deployment guide gaps

Move `nocobase-deployment` from the repository-only `.agents/skills/` into
`@nocobase/app-skills`, so `nocobase skills sync` delivers it to every
generated application; the repository keeps a relative symbolic link there
and the Claude mirror follows it. Point each template's AGENTS.md at the
Skill, explain in the READMEs and the Skill that `pnpm build` already runs
`pnpm install --prod` inside `dist/`, tell the workflow runtime's missing
Artifact error how to enable the deployed hash, and fix the tutorial's
archive path and start command.

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

* docs: write the English deployment overview, configuration, standalone and Docker pages

The four pages were "Being written" placeholders while their Chinese
counterparts were complete. Each now carries the same content as the
Chinese page: the deployment modes and how Hub relates to applications,
the shared, standalone and Hub-hosted configuration with the database,
secret, address and reverse proxy details, the build-to-systemd flow with
its acceptance checks, and the Docker image, Compose and update steps.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 18:19:16 +08:00
chenos 9f0edf85ae docs(create-plugin): say how a plugin's migrations differ from an application's (#369)
The template explained `baseDir` and the compiled manifests but never the
layout that declaration points at, so the application shape — one directory
per connection under `database/<connection>/` — was the only one an agent
had seen. A plugin declares a single `database/migrations` and
`database/seeds` with no connection segment, because it contributes to the
installing application's default connection alone and cannot know or target
another.

Two consequences follow, and both surface in someone else's application
rather than in the plugin. The loader flattens every source into one list
per connection and rejects a duplicate migration name across all of them,
so a bare timestamp that collides fails the run for the whole application.
Ordering is then by name alone, so a plugin's migrations interleave with
the application's instead of applying as a block.

The root AGENTS.md carries the same section for plugins that already exist
and will not be regenerated.
2026-09-22 12:33:14 +08:00
chenos 53806426a6 feat(db): give migrations and seeds a repository on their context (#367)
* feat(db): give migrations and seeds a repository on their context

`query` reaches rows through the Connection naming strategy and expresses
exactly what it is given, which leaves three things for each task to
assemble by hand: cross-dialect field encoding, Collection-level naming
overrides, and relation writes including the junction rows behind a
`belongsToMany`. Existing migrations and seeds show all three, from
repeated `JSON.stringify` on write to the `typeof row.x === 'string' ?
JSON.parse(row.x) : row.x` guard on read.

`context.repository(name)` covers them, resolving the Collection as the
database itself records it — the metadata a previous `builder` operation
wrote — rather than importing any application definition, so it does not
loosen the self-containment rule migrations are held to.

The two contexts default differently, and the field order says so without
a word of prose. A migration changes structure, so `builder` and `query`
stay its tools and `repository` is for the writes `query` would get
wrong. A seed changes no structure and writes installation data in
Collection terms, so `repository` is its normal tool.

Inside a transaction the Repository comes from the transaction's own
connection, so a failed task discards its writes. This is what the task
service container has been protecting: resolving the application's
DatabaseManager there would have produced a Repository writing outside
the task's transaction, and its refusal now names the supported path
instead of only refusing.

* feat(db): publish a nocobase-db Skill for applications

The package's documentation was never published: `files` carried `dist`
alone, so an application that installed `@nocobase/db` got no guidance
from it. What guidance existed lived in the application template as a
second-hand copy covering QueryAdapter and not Repository.

Ship the Skill under `skills/`, where `nocobase skills sync` copies it
into the `.agents/skills/` of every application depending on the package.

It is organized as the six areas an application meets, and records what
the type declarations cannot: which layer a task belongs to, the reverse
criterion for reaching past `query` on a task context, the asymmetry of
`database/<connection>/collections/` between managed and external
connections, and the API shapes that are otherwise guessed.

* docs(db): record what a task context's types cannot say

Three things about a migration or seed context are decisions rather than
shapes, so the type declarations leave them out.

`context.container` is the one that misleads: it is typed
`ServiceResolver` and reads as the application container, but at runtime
it is a frozen allow list holding `idGeneratorToken` alone. Code written
against the type compiles and then throws. Naming the allowed token also
prevents the opposite mistake of hand-rolling an identifier.

The transaction default and the cost of `transaction: false`, and what
the task `connection` is for, are the other two.

The context shape itself stays out. It is visible in the signature being
written, in the declarations, and now in the JSDoc on each field; a copy
here would be a second source of truth that drifts the next time a field
is added, as `repository` just was.

* docs(app-skills): hand the database API back to the package that owns it

The application development Skill carried its own account of the database
API because `@nocobase/db` published none. It now ships a Skill, so two
copies would drift — and this copy already had: it documented QueryAdapter
and not Repository, and its seed example wrote through `query` when a seed
context defaults to `repository`.

The three references keep what the application owns: where the directories
sit, what `db:apply`, `db:reset` and `db:repair` do, how another connection
configures its tasks, legacy migration, checksum drift, compiled manifests,
the task config reader, dialect packages and their fields, driver
installation and native binaries. They point to the package's Skill for the
API the files are written against.

`migrations.md` loses the examples, the field builders, the alter
operations and the naming rules, and keeps every command and configuration
section. `database-and-data.md` keeps resolving the manager, where data
access belongs, and generated IDs.

* docs(db): stop listing a migration context as a source of collections

`connection.collections` has never been reachable from a migration or a
seed. `createMigrationConnection()` builds a fresh object carrying `name`,
`driver`, `dialect`, `capabilities` and `client()`; it does not pass the
DatabaseConnection through, so the property is undefined at runtime rather
than merely absent from the type. `MigrationConnection` has never declared
it either.

The claim arrived with `db.collections(name?)` in #218, in a sentence
listing the places that already hold a connection. Two of the three are
right: a `transaction()` callback receives a full DatabaseConnection, and
so does test setup.

Say what the task contexts do carry instead, and why the omission is
consistent with the self-contained rule — a resolved Collection is a shape
that keeps changing, and a record already executed elsewhere cannot depend
on it.
2026-09-22 12:15:01 +08:00
PiEgg fc34a66814 chore: link .agents/skills into .claude/skills on install (#349) 2026-09-21 16:19:25 +08:00
PiEgg e819257048 ci: select database integration jobs by changed paths (#335) 2026-09-20 17:27:33 +08:00
chenos a793d08ddb fix(tooling): align template driver checks and support local registry reset (#311)
* fix(tests): align template driver assertions with create-app

* feat(tooling): support resetting local registry preparation

* docs: keep the root README empty and record documentation policy

* test: reject SQLite in template devDependencies
2026-09-19 21:04:06 +08:00
PiEgg 6e15911083 fix(cli): register plugins as production dependencies (#271) 2026-09-17 15:00:35 +08:00