-
Notifications
You must be signed in to change notification settings - Fork 6
feat: add the builder package #646
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
d25a08d
feat(builder): create the package
mobeenabdullah b18c51d
fix(release): version the builder package in lockstep with the others
mobeenabdullah a505d0a
fix(builder): catch dynamic imports in the layering guard
mobeenabdullah 21e23b2
fix(builder): require react 19, as the renderer it depends on does
mobeenabdullah 0a3d5e8
refactor(builder): export the package name under its own name
mobeenabdullah 14f8cb8
ci: accept the builder scope in pr titles
mobeenabdullah cc38c59
docs: map the builder package and its commit scope
mobeenabdullah 7d9e07d
docs(release): say what the builder package is for
mobeenabdullah 85b5741
Merge remote-tracking branch 'origin/main' into feat/builder-package
mobeenabdullah 74983d0
ci: run the builder package tests
mobeenabdullah bf1e562
fix(builder): scan require calls in the layering guard
mobeenabdullah b684cbe
fix(builder): allow exact specifiers, not every subpath of a package
mobeenabdullah 71a2278
fix(builder): catch typeof-import type queries in the layering guard
mobeenabdullah 0b29229
ci: lint builder with the react rule set
mobeenabdullah 50c2242
docs: claim only the layering rules the guard can check
mobeenabdullah 2edb1ac
fix(builder): read every module shape the bundler follows
mobeenabdullah a6b40cc
docs(builder): add the package readme
mobeenabdullah 9af8d23
docs(builder): the builder is not the renderer
mobeenabdullah e4ea618
fix(builder): read import types inside jsdoc comments
mobeenabdullah c7dc2e4
ci: treat javascript sources as test inputs
mobeenabdullah f740369
test(builder): enforce the admin boundary at the dependency graph
mobeenabdullah 2fd2f88
test(builder): assert the manifest never declares admin
mobeenabdullah 5f8b92f
fix(builder): read jsdoc import tags in the layering guard
mobeenabdullah File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| --- | ||
| "nextly": patch | ||
| "create-nextly-app": patch | ||
| "@nextlyhq/admin": patch | ||
| "@nextlyhq/admin-css": patch | ||
| "@nextlyhq/blocks-engine": patch | ||
| "@nextlyhq/blocks-react": patch | ||
| "@nextlyhq/ui": patch | ||
| "@nextlyhq/adapter-drizzle": patch | ||
| "@nextlyhq/adapter-postgres": patch | ||
| "@nextlyhq/adapter-mysql": patch | ||
| "@nextlyhq/adapter-sqlite": patch | ||
| "@nextlyhq/storage-s3": patch | ||
| "@nextlyhq/storage-uploadthing": patch | ||
| "@nextlyhq/storage-vercel-blob": patch | ||
| "@nextlyhq/plugin-form-builder": patch | ||
| "@nextlyhq/plugin-page-builder": patch | ||
| "@nextlyhq/plugin-seo": patch | ||
| "@nextlyhq/plugin-sdk": patch | ||
| "@nextlyhq/eslint-config": patch | ||
| "@nextlyhq/prettier-config": patch | ||
| "@nextlyhq/telemetry": patch | ||
| "@nextlyhq/tsconfig": patch | ||
| "@nextlyhq/builder": patch | ||
| --- | ||
|
|
||
| Add the @nextlyhq/builder package, which will hold the visual page-builder editor. It ships no features yet, so there is nothing to install it for: it exists now so the editor arrives under a name that is already reserved and already versioned in lockstep with the rest. It requires React 19, matching the renderer it draws with (@nextlyhq/blocks-react). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| # @nextlyhq/builder | ||
|
|
||
| The visual page-builder editor: the shell, the canvas, and the op store that | ||
| everything in it either produces or reads. | ||
|
|
||
| **It ships no features yet.** The package exists ahead of them so its name is | ||
| claimed on npm — trusted publishing cannot perform a package's first publish, and | ||
| the bootstrap script will not claim a name that is not already a workspace | ||
| package. There is nothing to install it for until the editor lands. | ||
|
|
||
| ## What this package is not | ||
|
|
||
| **It is not a renderer.** The canvas draws documents through | ||
| `@nextlyhq/blocks-react` — the same renderer that serves published pages — and | ||
| re-implements nothing downstream of the document model. Read-path preparation, | ||
| condition gating and slot pruning are consumed from the engine's own entry | ||
| points, never reproduced here. | ||
|
|
||
| That rule is not stylistic. `plugin-page-builder` carries a second renderer of | ||
| its own, and the two disagree about condition gating in opposite directions: one | ||
| fails closed, the other does not evaluate conditions at all. Sharing a predicate | ||
| would not have prevented that, because sharing a predicate does not share the | ||
| decision to call it. Only sharing the entry point does. | ||
|
|
||
| ## The layering contract | ||
|
|
||
| `src/layering.test.ts` enforces two boundaries as a build failure: | ||
|
|
||
| **The strongest enforcement is not the scan.** `@nextlyhq/admin` is in no | ||
| dependency field of this package, and pnpm's `node_modules` is not hoisted, so it | ||
| does not resolve here at all — verified, `MODULE_NOT_FOUND`. Every spelling of a | ||
| direct admin import therefore fails to build, including spellings TypeScript has | ||
| not shipped yet. A test asserts the manifest stays that way, which is complete by | ||
| construction in a way a syntax scan can never be. | ||
|
|
||
| The scan is the second layer: it fails at test time naming the rule, rather than | ||
| at build time with a resolution error, and it is the **only** enforcement for the | ||
| subpath policy below — `blocks-react/next`, the `plugin-sdk` root and | ||
| `plugin-sdk/testing` all resolve perfectly, because their packages are legitimate | ||
| dependencies. The graph has nothing to say about which _entry_ of a dependency is | ||
| allowed. | ||
|
|
||
| 1. **Never `@nextlyhq/admin` directly.** Admin is reached only through | ||
| `@nextlyhq/plugin-sdk/admin`, a curated facade where every export is named | ||
| individually and carries a stability tag. A direct import bypasses the facade | ||
| and takes a dependency on internals nobody promised to keep. The pull toward | ||
| it is concrete: the editor wants admin's Lexical node set for inline rich | ||
| text. | ||
| 2. **Never the CMS runtime.** The allowlist is **exact specifiers**, not | ||
| packages, because subpaths are where the coupling lives — | ||
| `@nextlyhq/blocks-react/next` imports `nextly/runtime`, and the | ||
| `@nextlyhq/plugin-sdk` root re-exports runtime values from `nextly`. Only | ||
| `@nextlyhq/plugin-sdk/admin` is admitted, and only the root entry of | ||
| `blocks-react`. | ||
|
|
||
| The guard reads every shape that reaches a module — static imports and | ||
| re-exports, `import()`, bare `require()`, `import x = require()`, | ||
| `typeof import()`, and triple-slash type references — because each of those has, | ||
| at some point, walked straight past a narrower version of it. | ||
|
|
||
| **What the guard does NOT prove:** that the canvas renders _through_ | ||
| `blocks-react` rather than reimplementing rendering on React and | ||
| `blocks-engine`. Both spellings import exactly the same packages, so no import | ||
| scan can separate them. That rule is held by review. The allowlist makes the | ||
| shortcut inconvenient; it cannot make it impossible. | ||
|
|
||
| Adding an allowlist entry is a deliberate act with a reason recorded beside it. | ||
|
|
||
| ## Public surface | ||
|
|
||
| `BUILDER_PACKAGE_NAME` — this package's npm name, for diagnostics that report | ||
| which packages a host loaded. The name and not the version: a version literal in | ||
| source would be stale one release after it was written, because every release | ||
| bumps this package in lockstep with its siblings. | ||
|
|
||
| ## Development | ||
|
|
||
| Run these from this directory (`packages/builder`), not the repository root — | ||
| turbo swallows the summary line at the root. | ||
|
|
||
| ```bash | ||
| pnpm run test # vitest, including the layering guard | ||
| pnpm run check-types # tsc --noEmit; unlike some packages here, this DOES | ||
| # cover the test files (tsconfig has no test exclude) | ||
| pnpm run lint # eslint --max-warnings 0; a single warning fails | ||
| pnpm run build # tsup | ||
| ``` | ||
|
|
||
| ## Peer dependencies | ||
|
|
||
| React 19, matching the renderer it draws with. `@nextlyhq/blocks-react` requires | ||
| `react: ^19.0.0`, and it is a dependency here rather than a peer, so a React 18 | ||
| host would hit an unsatisfiable peer one level down. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| // The builder is a React package — it renders THROUGH `@nextlyhq/blocks-react` | ||
| // rather than being a renderer — so it layers the shared React rules over the | ||
| // repository base. The root config scopes those rules by path; this per-package | ||
| // config applies them unconditionally, matching how the other React packages | ||
|
mobeenabdullah marked this conversation as resolved.
|
||
| // lint when run directly. | ||
| import { config } from "@nextlyhq/eslint-config/react-internal"; | ||
|
|
||
| export default [...config]; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| { | ||
| "name": "@nextlyhq/builder", | ||
|
mobeenabdullah marked this conversation as resolved.
|
||
| "version": "0.0.2-alpha.55", | ||
| "description": "The visual page-builder editor for Nextly: shell, canvas and op store.", | ||
| "license": "MIT", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/nextlyhq/nextly.git", | ||
| "directory": "packages/builder" | ||
| }, | ||
| "homepage": "https://nextlyhq.com/docs", | ||
| "bugs": { | ||
| "url": "https://github.com/nextlyhq/nextly/issues" | ||
| }, | ||
| "type": "module", | ||
| "main": "dist/index.mjs", | ||
| "module": "dist/index.mjs", | ||
| "types": "dist/index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/index.d.ts", | ||
| "import": "./dist/index.mjs", | ||
| "default": "./dist/index.mjs" | ||
| } | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "scripts": { | ||
| "build": "tsup", | ||
| "dev": "tsup --watch", | ||
| "check-types": "tsc --noEmit", | ||
| "lint": "eslint . --max-warnings 0", | ||
| "lint:fix": "eslint . --fix", | ||
| "test": "vitest run", | ||
|
mobeenabdullah marked this conversation as resolved.
|
||
| "test:watch": "vitest", | ||
| "clean": "rimraf dist" | ||
| }, | ||
| "engines": { | ||
| "node": ">=20.0.0" | ||
| }, | ||
| "dependencies": { | ||
| "@nextlyhq/blocks-engine": "workspace:*", | ||
| "@nextlyhq/blocks-react": "workspace:*" | ||
| }, | ||
| "peerDependencies": { | ||
| "@nextlyhq/plugin-sdk": "workspace:*", | ||
| "@nextlyhq/ui": "workspace:^", | ||
| "react": "^19.0.0", | ||
| "react-dom": "^19.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@nextlyhq/eslint-config": "workspace:*", | ||
| "@nextlyhq/plugin-sdk": "workspace:*", | ||
| "@nextlyhq/tsconfig": "workspace:*", | ||
| "@nextlyhq/ui": "workspace:*", | ||
| "@types/node": "^20.19.17", | ||
| "@types/react": "19.2.0", | ||
| "@types/react-dom": "19.2.0", | ||
| "eslint": "^9.39.1", | ||
| "react": "19.2.0", | ||
| "react-dom": "19.2.0", | ||
| "rimraf": "^6.1.3", | ||
| "tsup": "^8.5.0", | ||
| "typescript": "^5.9.3", | ||
| "vitest": "^4.1.0" | ||
| }, | ||
| "publishConfig": { | ||
| "access": "public", | ||
| "registry": "https://registry.npmjs.org/", | ||
| "provenance": true | ||
| }, | ||
| "keywords": [ | ||
| "nextly", | ||
| "page-builder", | ||
| "visual-editor", | ||
| "canvas" | ||
| ] | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| /** | ||
| * `@nextlyhq/builder` — the visual page-builder editor. | ||
| * | ||
| * The editor half of the page builder: the shell, the canvas, and the op store | ||
| * that everything in it either produces or reads. It is deliberately NOT a | ||
| * renderer. | ||
| * | ||
| * **The invariant this package is built around**: the canvas renders documents | ||
| * through `@nextlyhq/blocks-react` — the same renderer that serves published | ||
| * pages — and re-implements nothing downstream of the document model. Read-path | ||
| * preparation, condition gating and slot pruning are consumed from the engine's | ||
| * own entry points, never reproduced here. | ||
| * | ||
| * It is held by review rather than by a test. Reimplementing rendering on React | ||
| * and the engine imports exactly the same packages as delegating to the | ||
| * renderer, so the layering guard cannot tell the two apart; it narrows what may | ||
| * be imported, which makes the shortcut inconvenient rather than impossible. | ||
| * | ||
| * That rule is not stylistic. `plugin-page-builder` carries a second renderer of | ||
| * its own, and the two disagree about condition gating in OPPOSITE directions — | ||
| * one failing closed, the other not evaluating conditions at all. Sharing a | ||
| * predicate would not have prevented that, because sharing a predicate does not | ||
| * share the decision to call it; only sharing the entry point does. | ||
| * | ||
| * This entry exports no features yet. The package exists ahead of them so its | ||
| * name is claimed on npm: trusted publishing cannot perform a package's first | ||
| * publish, and the bootstrap script will not claim a name that is not already a | ||
| * workspace package. | ||
| * | ||
| * @module @nextlyhq/builder | ||
| */ | ||
|
|
||
| /** | ||
| * This package's npm name, for diagnostics that report which packages a host | ||
| * has loaded. | ||
| * | ||
| * The name and not the version. A version literal in source would be stale one | ||
| * release after it was written, because every release bumps this package in | ||
| * lockstep with its siblings; reporting a version means injecting the manifest's | ||
| * value at build time, which belongs with the surface that displays it rather | ||
| * than with a constant nothing reads yet. | ||
| */ | ||
| export const BUILDER_PACKAGE_NAME = "@nextlyhq/builder" as const; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.