| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
Update broken path references across all docs | 8 个月前 | |
refactor(cli): use pathe for cross-platform path operations Replace node:path with pathe in contract-emit.ts for consistent behavior across Windows/macOS/Linux and non-Node runtimes. Document this as a codebase convention in .cursor/rules and docs/onboarding/Conventions.md. | 7 个月前 | |
refactor: rename every user-facing prisma-next identifier to Prisma 8 (#30262) ## Linked issue n/a — no Linear ticket. Completes the rename that #30248 started for prose; builds on #30261. ## At a glance Every `prisma-next` identifier a user can see is renamed. Before and after, for a scaffolded project: ```text // use prisma-next → // use prisma-8 (schema header) prisma-next.md → prisma-8.md (primer at the project root) PRISMA_NEXT_DISABLE_TELEMETRY → PRISMA_DISABLE_TELEMETRY (and every other PRISMA_NEXT_* variable) ~/.config/prisma-next/ → ~/.config/prisma-8/ (per-user telemetry config) prisma-next contract emit → prisma contract emit (CLI invocations in docs, fixtures, recordings) ``` ## Summary After #30248 the product was called Prisma 8 in prose, but the working name was still written into user projects and printed by the CLI: the schema header, the primer file, the environment variables, the per-user config directory, the language-server diagnostic source, the Standard Schema vendor string, the contract brand symbol, the advisory-lock domain, and about 650 fixture and doc files that spelled out `prisma-next …` commands. This PR renames all of it in one pass and tightens the legacy-name lint so the only occurrences left are the ones with a reason. ## Decision One commit. The mapping: | Surface | Before | After | |---|---|---| | Schema header | `// use prisma-next` | `// use prisma-8` | | Primer file `init` writes | `prisma-next.md` | `prisma-8.md` | | CLI environment variables | `PRISMA_NEXT_*` | `PRISMA_*` | | Per-user config directory | `prisma-next/` | `prisma-8/` | | Language-server diagnostic source | `prisma-next` | `prisma` | | Standard Schema vendor, VS Code publisher | `prisma-next` | `prisma` | | Contract brand symbol | `__prisma_next_brand__` | `__prisma_8_brand__` | | Postgres advisory-lock domain | `prisma_next.contract.marker` | `prisma_8.contract.marker` | | Example database names | `prisma_next_*` | `prisma_8_*` | | README banner image | `images/prisma-next.png` | `images/prisma-8.png` | | Telemetry docs URL | `prisma-next.dev/docs/…` | `www.prisma.io/docs/…` | | New-issue links | `github.com/prisma/prisma-next/issues/new` | `github.com/prisma/orm/issues/new` | | CLI invocations in prose, fixtures, and recordings | `prisma-next db verify` | `prisma db verify` | `prisma-8` is the slug the repo already uses for the skill, the examples, and the upgrade directories, so it is the slug for everything that needs one. Environment variables drop the infix entirely because `PRISMA_*` is what users expect and nothing else in the repo claims those names. What keeps the old name, each with a lint allowance that says why: - **Dated records**: changelog, release notes, ADRs, shipped upgrade instructions, gotcha logs, the framework-gaps review, and the `projects/` and `drive/` write-ups. - **Pinned links** into the old repository by number, Linear slugs, and links to ADRs whose filenames carry the name. - **`@cipherstash/prisma-next`**, a third party's published package name. - **Retirement proofs**: the list of old skill directories `init` deletes, and the tests asserting that no `prisma-next` bin or skill directory is installed any more. ## Behavior changes & evidence - **Schema header.** The inferred-schema printer and the `init` templates write `// use prisma-8`. The language server accepts both headers, so existing schemas keep their diagnostics and completion, and its Format action rewrites the old header to the new one. [packages/1-framework/3-tooling/language-server/src/schema-directive.ts](packages/1-framework/3-tooling/language-server/src/schema-directive.ts), [packages/1-framework/2-authoring/psl-printer/src/ast-to-print-document.ts](packages/1-framework/2-authoring/psl-printer/src/ast-to-print-document.ts). Evidence: the `renameLegacyDirective` tests, the server test that formats a legacy-headed schema, and the psl-printer tests. - **Environment variables.** Telemetry gating, the endpoint override, and the debug switch read the new names. `PRISMA_NEXT_DISABLE_TELEMETRY` is still honoured as an opt-out so nobody is silently opted back in; the endpoint and debug spellings are not. [packages/1-framework/3-tooling/cli-telemetry/src/gating.ts](packages/1-framework/3-tooling/cli-telemetry/src/gating.ts). Evidence: cli-telemetry gating tests. - **Per-user config directory.** [packages/1-framework/3-tooling/cli-telemetry/src/user-config.ts](packages/1-framework/3-tooling/cli-telemetry/src/user-config.ts). Existing users see the telemetry consent prompt once more; nothing else is lost. - **Primer file.** [packages/1-framework/3-tooling/cli/src/orm/init-scaffold.ts](packages/1-framework/3-tooling/cli/src/orm/init-scaffold.ts). Evidence: init-scaffold tests and template snapshots. - **Advisory-lock domain.** A CLI on this version and one on the previous version take different locks for the same marker. Both versions running migrations against one database at the same moment is already unsupported. - **Upgrade instructions.** Entries for the header, the environment variables, and the primer file are recorded in the rc.9 → rc.10 app and extension instructions with detection patterns, so the published upgrade skill applies the rename. ## Testing performed - `pnpm test` in cli (1437), cli-telemetry (112), language-server (312), psl-printer (63), framework-components (672), target-postgres (1607), vite-plugin-contract-emit (31), emitter (231), and `pnpm test:scripts` (507): all pass after `pnpm build`. The language-server tests hard-coded the old header's length in semantic-token arrays and span offsets; those expectations are updated. - Committed migration steps and their content-addressed contract snapshots are left untouched, since rewriting them would break their hashes; the lint treats them as dated records. - `pnpm lint:legacy-name` passes with the tightened allowances; `node --test scripts/lint-legacy-name.test.mjs` passes (14 tests, including new negative cases for the header, primer, and skill names). - `pnpm check:upgrade-coverage --mode pr --prev origin/main` passes. ## Skill update `skills/prisma-8` references and the two rc.9 → rc.10 upgrade instruction files are updated in this PR. ## Checklist - [x] All commits are signed off (`git commit -s`) per the [DCO](../CONTRIBUTING.md#developer-certificate-of-origin-dco). - [x] I read [CONTRIBUTING.md](../CONTRIBUTING.md) and the change is scoped to one logical concern. - [x] Tests are updated. - [ ] The PR title is in `TML-NNNN: <sentence-case title>` form. No Linear ticket exists for this change. - [x] The **Skill update** section above is filled in. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 12 天前 | |
feat(sql): Prisma 7 users point Prisma 8 at their existing schema.prisma instead of running contract infer and hand-fixing it (#30287) ## At a glance A Prisma 7 project has a `prisma/schema.prisma`. To run Prisma 8 beside it today, the user runs `prisma contract infer` against the database, deletes the `PrismaMigrations` model it picks up, adds `@@map` to every model, loses relation field names, ORM-side defaults and `@updatedAt`, and does it all again after every Prisma 7 migration. With this PR, `prisma.config.ts` points at the file they already have: ```ts // prisma.config.ts import { definePrismaConfig } from 'prisma/config'; import { defineConfig as ormConfig, prisma7Schema } from '@prisma/orm-postgres/config'; export default definePrismaConfig({ orm: ormConfig({ contract: prisma7Schema('prisma/schema.prisma'), db: { connection: process.env['DATABASE_URL']! }, }), }); ``` The normal commands then work unchanged: ```bash prisma contract emit # reads schema.prisma, writes contract.json + contract.d.ts prisma db sign # verifies against the database Prisma 7 built: zero findings ``` After every Prisma 7 migration the user runs `contract emit` and `db sign` again. Nothing is hand-edited. ## The decision Prisma 8 accepts the Prisma 7 schema language as a first-class contract source, interpreted straight into the contract. Prisma 7 keeps owning the database and its migrations; Prisma 8 adopts it read-only. [ADR 252](<docs/architecture docs/adrs/ADR 252 - An earlier Prisma version's schema is a contract source.md>) records the decision and the rules below. 1. **Fidelity means `db verify` reports nothing** against the database Prisma 7 built. `db verify` compares column types and nullability, defaults, primary key columns, foreign key actions, unique columns, index and check names, and native enums with member order. The interpreter reproduces exactly that, and leaves key, foreign key, and unique constraint names to Prisma 8. 2. **Every construct is either described exactly or a hard error.** No warnings, nothing dropped silently. An error names the construct, its line, and an edit that is valid in Prisma 7, with what that edit does to Prisma 7's next migration and to the Prisma 7 client. 3. **Prisma 8's own checks are not relaxed to admit a construct.** Where Prisma 8 PSL cannot spell something — an optional generated field, `@updatedAt` with a storage default — the source reports a hard error. Those refusals are a list of features to build, recorded in the project spec; nothing here loosens the parser or the interpreter to hide them. ## What changed for every Prisma 8 user Most of this PR is a new surface, but running real Prisma 7 DDL through `db verify` exposed defects on shared paths. Each fix is here with a regression test. - **The parser change is off by default.** Attributes on enum members and field lines inside a `view` block are read only under `grammar: 'prisma7'`, which only this source passes. A Prisma 8 PSL schema still rejects exactly what it rejected before, and the formatter now throws on a node kind it cannot print instead of writing a truncated block. - **`db verify` reads more default spellings.** A negative or cast numeral (`'-1'::integer`), an enum literal cast to a type in another schema, a zoneless `timestamp` literal, and `ARRAY[...]` list defaults are now read as the values they are. Columns that were reported as drift now verify clean; nothing that verified before starts failing. A schema-qualified mixed-case type name such as `audit."AuditAction"` also compares correctly now. Of 41 existing Prisma 8 contracts planned, applied and verified, 24 went from a false mismatch to clean and none went the other way. - **Introspection reads in a pinned session.** Defaults, check constraints, index predicates and policy text are read with `TimeZone = UTC`, `DateStyle = ISO, MDY`, `IntervalStyle = postgres`, and the caller's settings are restored. A contract inferred earlier from a server outside UTC that holds a `timestamptz` constant in such text shows that text once as a difference; the new text is stable. - **Migration planning renders a list literal default with its cast** (`ARRAY['1', '-2']::int8[]`), the same rendering the adapter uses for column DDL. - **`contract infer` prints each default in the form `contract emit` accepts:** quoted decimal text for `Decimal` and `Numeric`, plain digits for a large `BigInt`, quoted `NaN` and `Infinity`, and `dbgenerated(...)` for a list default holding a `NULL` element, which was previously dropped in silence. - **PSL number defaults keep every digit.** A `@default` number is read from its source text, so `Decimal @default(1.50)` stays `"1.50"` and a `BigInt` past 2^53 emits instead of failing. Contracts with such defaults get a new storage hash and need re-signing; the app upgrade instructions have the steps. - **A failing contract source reports findings, not just JSON.** `CONTRACT.SOURCE_LOAD_FAILED` now carries a `diagnostics` array — one entry per finding, with its code, its summary and, where known, its file and line — and the terminal prints them. A dotted source code travels as itself; an undotted legacy `PSL_*` code is wrapped as `CONTRACT.SOURCE_DIAGNOSTIC` with the original in `meta.code`. `meta.diagnostics` and `meta.issues` are unchanged. - **An ORM-side "now" default on a zoneless `timestamp` column no longer fails at write time.** The generator produced an instant where the codec encodes a plain date-time; Prisma 8's own `temporal.timestamp(onUpdate: now)` had the same defect. Every temporal preset now takes its generator from one codec-to-"now" lookup. ## How it works **Interpreting.** `@internal/sql-contract-prisma7` holds the dialect's rules: blocks, attributes, relation pairing, junction tables, defaults, and the diagnostics. It knows nothing about a particular database. Everything a target must answer arrives through `Prisma7TargetBinding` — the provider names it accepts, the type map, the native enum entity kind, index types, the identifier byte limit, junction relation field names, the `@updatedAt` generator per codec, and how literal defaults are read. The Postgres target exports one instance, `prisma7PostgresBinding`, and the Postgres facade wires the two together as `prisma7Schema(path)`. `defineConfig` accepts `contract: string | ContractConfig`; artifacts land beside the schema file or directory, and `output` on `defineConfig` overrides. The rules were not written from memory. `prisma@7.10.0 migrate diff` generated the SQL for every fixture that produces a contract, and that SQL is committed beside it. The ones that matter most: - Table and column names are the Prisma 7 names verbatim; `String` is `text`, `DateTime` is `timestamp(3)`, `Json` is `jsonb`, `Decimal` is `numeric(65,30)`, with `@db.*` overriding per Prisma 7's own table. - List columns are nullable, because Prisma 7 creates them without `NOT NULL`. - Native enums keep mapped values in declared order, in their `@@schema`. - `onDelete` and `onUpdate` are always written, with Prisma 7's defaults (`Restrict` or `SetNull`, and `Cascade`). - An implicit many-to-many relation becomes the junction Prisma 7 creates: table `_AToB`, primary key `(A, B)`, index `_AToB_B_index`, cascading foreign keys. Its two relation fields are named the way `contract infer` names the same table's foreign keys, so the model reads the same before and after cutover. - `@unique` becomes a unique **index** named `{table}_{cols}_key`, cut to 63 bytes as Prisma 7 cuts it, because that is what Prisma 7 creates and `db verify` tells indexes and constraints apart. - `@updatedAt`, `uuid()`, `cuid()`, `ulid()` and `nanoid()` become ORM-side generators; `cuid()` maps to cuid2, since Prisma 8 ships no cuid v1 and the column type and the opacity of the ids are the same. **Hard errors.** 22 codes, all dotted `PSL.PRISMA7_*`, each with a fixture and an entry in `docs/reference/error-reference.md`: views, `Unsupported(...)`, `@db.*` types with no Prisma 8 codec, `relationMode = "prisma"`, an enum used from another `@@schema`, index `sort`/`length`/`ops` arguments, a JSON `null` default, referential actions a required field cannot take, table and junction name collisions, an `@ignore`d field a key or relation uses, and the optional or `@default`-combined generated fields above. ## The example app `examples/prisma7-adoption` runs the public upgrade guide's story for real, in one vitest run against a `@prisma/dev` database: Prisma 7 installed as `@prisma/prisma7@7.10.0` with its own binary and config, `prisma7 migrate deploy`, Prisma 8 emitting from the same `schema.prisma`, `db sign`, `db verify` with zero findings, rows written through one client and read through the other (including tags through `_PostToTag`), then a second Prisma 7 migration, emit and sign again. Its README records two things a user following the guide meets that are not this PR's to fix: Prisma 7's peer dependency on `prisma` makes pnpm resolve the `prisma` binary to Prisma 7 unless a Prisma 8 `prisma` dev dependency is explicit, and pnpm's `no-downgrade` trust policy refuses `prisma@7.10.0` because it carries no provenance. ## How it is tested - 77 package fixtures, one per rule and per error code, run through the real Postgres pack with the expected contract or diagnostics committed; the 32 that expect a contract each carry the `migration.sql` Prisma 7.10.0 wrote, and a table-driven integration test applies each and verifies against it. - The `supported` proof schema covers every scalar, the `@db.*` overrides, number, list and temporal defaults, native enums, implicit many-to-many, `@updatedAt` and multiSchema. Prisma 7's SQL for it is applied unchanged and verified in **strict** mode, so a dropped column, index or foreign key fails the test; the expected extras are exactly what Prisma 7 creates for `@ignore` and `@@ignore`. One `timestamptz` default is checked with the session time zone outside UTC. - CLI journeys for `contract emit`, `db sign` and `db verify` from a user-shaped config, plus a journey for the hard-error exit (code 2, one diagnostic, nothing written). - The example's own test, in the examples CI job, with Prisma 7's schema engine fetched in a step before the tests. ## Known gaps Recorded, not hidden, in `projects/prisma7-contract-source/spec.md` § Deferred gaps. In short: cross-`@@schema` enum references need a feature (ADR 226 covers `@relation` only); `Bytes` and `DateTime` literal defaults are carried as the SQL literal of the default Postgres stores, which verifies exactly but prints as `dbgenerated("...")` at cutover; the Mongo PSL interpreter still ignores unknown top-level blocks, which the Mongo slice fixes. The same section lists the pre-existing defects this work found and did not fix — `contract infer` printing PascalCase tables without `@@map` and nullable lists as required, several `db init` failures, and a handful of list-default spellings that still fail verify — each with where it lives and the note that it exists on `main`. ## Alternatives considered - **A one-shot converter to a Prisma 8 file** (the original design). Rejected: the converted file drifts on every Prisma 7 migration, and every Prisma 8 spelling gap would become a lossy conversion rule. The converter survives as the cutover step on top of this source. - **Prisma 7's own parser** (`@prisma/get-dmmf`, the WebAssembly build). Rejected: its output deletes `@ignore` fields and `@@ignore` models and lists views as models, and it is a 3 MB synchronous load on the emit path. This repository depends on no Prisma 7 package. - **Porting Prisma 7's parser to TypeScript.** Unnecessary; the Prisma 8 parser needed two small grammar additions. - **`contract infer` plus hand edits.** The status quo this replaces. ## Notes for reviewers - The user-visible changes above are covered by entries in `skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.11-to-8.0.0-rc.12/`. The extension notes gain one entry: `parseRawDefault` left the `family/psl-infer` subpath for `parsePostgresDefault` on `@prisma/orm-postgres/target/default-normalizer`, same signature. - The branch merges `origin/main` at rc.11. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added Prisma 7 schema support for generating Prisma 8 PostgreSQL contracts. - Added `prisma7Schema` configuration, contract signing and verification workflows, and a Prisma 7 adoption example. - Added support for Prisma 7 models, relations, indexes, enums, defaults, native types, and multi-schema projects. - Added improved temporal, numeric, array, and enum default handling. - **Improvements** - Contract source failures now include detailed, location-aware diagnostics. - PostgreSQL introspection is more consistent across session settings. - **Documentation** - Expanded adoption guidance, error references, upgrade notes, and troubleshooting documentation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 7 天前 | |
feat: an application depends on one Prisma package (ADR 242) (#29864) ## What changes for someone using Prisma Today, an application that talks to Postgres installs a long list of our packages: ```jsonc { "dependencies": { "@prisma-next/postgres": "...", "@prisma-next/sql-runtime": "...", "@prisma-next/sql-orm-client": "...", "@prisma-next/target-postgres": "...", "@prisma-next/adapter-postgres": "...", "@prisma-next/sql-contract": "..." // ...a dozen more } } ``` After this PR, it installs one: ```jsonc { "dependencies": { "@prisma/orm-postgres": "0.16.0" } } ``` Everything else arrives as that package's own dependencies. Three of our example apps are converted in this PR to prove it — one per database — and each has exactly one Prisma package in its `dependencies`. This implements [ADR 242](https://github.com/prisma/prisma/pull/29852), which is already merged. ## What gets published 17 packages, all under the `@prisma` scope: - **3 database packages** — `@prisma/orm-postgres`, `orm-sqlite`, `orm-mongo`. An application installs exactly one. We call these *facades*: each is a small package that wires its database together and re-exports everything an application needs. - **6 extension packs** — PostGIS, pgvector, ParadeDB, Supabase, arktype-json, middleware-cache. Optional, installed alongside a database package. - **7 platform packages** — the framework, the toolchain, one per database family, one per database target. Applications never install these directly; they arrive as dependencies. Extension authors do install them. - **the `prisma` command**, as a bin-only package. Every other workspace package — around 50 of them — stops being published. They still exist in the repo as the unit we organise code in; they just stop having a life on the registry. **This PR does not make that switch yet.** It builds and proves the new surface while leaving today's publish list exactly as it is. Flipping it is a separate change. ## The problem this design has to avoid A published package can't depend on packages that won't exist on the registry. So each published package *contains a compiled copy* of the internal packages it covers. That creates a trap. If one application ends up with the same code twice — once inside a published package, once as its own package — then classes, registries, and anything compared by reference exist twice too. An `instanceof` check quietly returns false. Nothing crashes, nothing fails to compile, and both copies behave identically in isolation. You find out much later, somewhere unrelated. So the rule the whole design follows is: **every piece of internal code is published from exactly one package.** Concretely, that means: - Each published package is built in one pass, so code shared between its own entry points exists once. Verified from the build's source maps: no module appears in more than one chunk, in any published package. - When one published package needs code from another, it imports it as a real dependency rather than compiling in a second copy. - A facade re-exports from the platform packages; it never carries its own copy. `@prisma/orm-postgres/orm-client` and `@prisma/orm-family-sql/orm-client` are two names for the same object, and there's a test that asserts exactly that from installed tarballs. - One table in `packages/0-shared/publish-surface` maps every internal package to where it's published. The build, the code generator, and the lint checks all read it, so there's one answer to "where does this live" rather than three that can drift. ## Generated code follows the application Prisma writes imports into your project — contract types and migration files. Those imports have to name packages your project actually depends on, or they won't resolve. So the generator now reads the `package.json` next to the config it's generating for. A project that depends on `@prisma/orm-postgres` gets imports from that package. A project on today's names keeps today's names. Nothing to configure, because the manifest already says which it is. Contract hashes are unaffected, and that isn't an assumption — hashes are computed from a structure that import text never enters, and there's a test asserting the hash is identical across naming schemes *while* the emitted imports demonstrably differ. ## What stops the trap coming back Two checks, because the failure is silent and won't show up in a test suite: - Every example app and test project must use one naming scheme, not a mix. `lint-single-import-root` scans them and fails the build if any project imports from both, since that's the situation that loads code twice. - `lint-consumer-internal-imports` counts how many internal-package imports remain in those projects and compares against a committed number. It fails if the number goes up (someone added one) and also if it goes down without the number being updated (so improvements get locked in). Target is zero. The build itself also refuses to proceed if the published-package map would put one module in two places, or if a published package's `package.json` no longer matches what its code actually needs. ## Reading this PR It's large — 257 files — because it's a migration. The commits are grouped and meant to be read in order: 1. **Platform packages** — the build mechanism, and the seven platform packages it produces. 2. **Database packages, extension packs, the `prisma` command** — completes the set of 17. 3. **Generated imports become configurable** — one place decides which names get written, with today's names still the default. 4. **Database-family symmetry, publishing the map, the identity checks.** 5. **One package per application** — the three converted examples, the re-exports they proved necessary, and the counting check. 6. **Migration files follow the project too.** One thing worth knowing while reading: re-exporting a package republishes all of its sub-paths, not just the one that was needed. This PR adds 115 published sub-paths across the three database packages. Two candidates were dropped for exactly that reason — see below. ## Alternatives considered **Let an application install platform packages alongside its facade.** Nothing would need re-exporting and the facades would stay thinner. Rejected: an application would again juggle several Prisma dependencies whose correct combination it maintains by hand, and getting it wrong — upgrading one and not the other — produces the silent two-copies failure above. Re-exporting costs a generated line and nothing at runtime. **Re-export everything an application might plausibly want.** Rejected in review: because re-exporting brings a package's entire sub-path surface, generosity is expensive and hard to undo. Migration tooling (54 sub-paths) was dropped because its only users are extension packs, which install platform packages anyway; the SQL driver re-export was dropped because nothing imported it at all. What remains is what a converted example actually needed. **Flip the publish list in this same PR.** Rejected: it would mix "does the new surface work" with "is it safe to stop publishing 50 packages" in one review. The switch is mechanical once this lands, and gets its own change. ## Verification `build`, `typecheck` (156 tasks), `test:packages` (1077 files / 14087 tests), `test:e2e`, `lint`, `lint:deps`, `lint:docs`, `lint:manifests`, `check:publish-deps`, `check:clean-tree`, `lint:casts` and `lint:throws` (no new instances), `test:scripts`, coverage, the tarball-install suites, and regenerating every committed artifact leaves the tree unchanged. Known-unstable and unrelated to this change: the `relation-mode-gh-*` port suites (TML-3140), and several test timeouts that are too tight under load. ## Follow-ups TML-3124 switch the publish list · TML-3127 build cache can validate a stale published package on CI · TML-3140 unstable port suites · TML-3141 a test-helper sub-path reaches a package that is never published. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added consolidated public ORM packages for PostgreSQL, MongoDB, SQLite, framework tooling, database targets, and extensions. - Generated contracts, migrations, and scaffolds now adapt imports to the consuming project’s package surface. - Added facade-provided `prisma-next` CLI access and consolidated migration entrypoints. - **Documentation** - Updated installation, package naming, public entrypoint, and migration scaffolding guidance. - **Tests** - Added coverage for package installation, exports, CLI behavior, module identity, and import compatibility. - **Chores** - Added checks preventing incompatible internal and public package imports. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 1 个月前 | |
ci: shard package tests without weakening coverage gates (#30156) ## Linked issue n/a — infrastructure change without a Linear ticket. ## At a glance ```yaml # Package Tests (1/4 ... 4/4) - run: pnpm coverage:packages --reporter=blob --shard=${{ matrix.index }}/4 - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # Coverage - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: pattern: package-coverage-* merge-multiple: true - run: pnpm coverage:packages:merge - run: pnpm coverage:report ``` Package tests now execute on four runners, while a separate `Coverage` job evaluates thresholds once against the combined native Vitest report. `Test Examples` runs concurrently, and a lightweight final `Test` job preserves the required status name. ## Summary The package-test portion of `Test` was the remaining serial CI bottleneck. This change shards that work horizontally while keeping one authoritative coverage gate and the existing required `Test` status context. ## Decision This PR ships three connected CI changes: 1. Run package tests and V8 coverage across four native Vitest shards, each uploading one uniquely named blob artifact. 2. Download and require all four blob reports in a separate `Coverage` fan-in job, merge their coverage counters with Vitest, and only then apply the existing package-level thresholds and warning policy. 3. Run examples concurrently and preserve assertion failures, inert-diff behavior, and the required `Test` check name through a lightweight final gate. ## Reviewer notes - Shards use SHA-pinned `actions/upload-artifact`; `Coverage` uses SHA-pinned `actions/download-artifact` with `pattern: package-coverage-*` and `merge-multiple: true`. Both are GitHub-created actions in the `actions` organization, so the existing GitHub-actions category permits them without individual allow-list entries. - Uploads set `include-hidden-files: true` because Vitest writes blobs below `.vitest/blob`, and `if-no-files-found: error` prevents a shard from silently publishing nothing. - GitHub does not expose artifacts from an earlier workflow attempt to rerun jobs. Use **Re-run all jobs**, not **Re-run failed jobs**; a partial rerun fails safely when `Coverage` verifies the four expected files. - Coverage thresholds and file reporters are deliberately disabled only in partial shard processes. The merge process runs without the shard marker and therefore restores the complete policy. - The stable required status remains `Test`; the four `Package Tests (N/4)` jobs, `Coverage`, and `Test Examples` are implementation details behind its final result. - The hosted four-runner transport can only execute in GitHub Actions. A local two-shard smoke test proved Vitest's blob names, merged counters, and final-only 100% threshold behavior. ## How it fits together 1. [`vitest.config.ts`](vitest.config.ts) recognizes shard collection through `VITEST_COVERAGE_SHARD`, keeps the full include/exclude policy, and suppresses only partial-run thresholds and coverage output. 2. [`.github/workflows/ci.yml`](.github/workflows/ci.yml) runs `vitest --coverage --reporter=blob --shard=N/4` on four PostgreSQL-backed runners. Each shard still reports test failures, uploads exactly one hidden blob file, and explicitly propagates a failing outcome after the upload. 3. `Coverage` downloads all `package-coverage-*` artifacts into `.vitest/blob` and checks for `blob-1-4.json` through `blob-4-4.json` before doing any merge. 4. [`pnpm coverage:packages:merge`](package.json) invokes Vitest's native `--merge-reports` path, which combines Istanbul counters rather than averaging percentages and replays failed tests. 5. The existing [`pnpm coverage:report`](scripts/coverage-report.mjs) attributes merged source entries to packages and enforces their thresholds. `Test Examples` runs concurrently, while the final `Test` job fails if any package shard, coverage, example, or prerequisite job failed. ## Behavior changes & evidence - **Package tests execute across four CI runners instead of one.** The matrix and fan-in are in [`.github/workflows/ci.yml`](.github/workflows/ci.yml), with the expected orchestration locked by [`scripts/coverage-config.test.mjs`](scripts/coverage-config.test.mjs). - **Coverage gates see the complete combined run.** Shard-aware configuration lives in [`vitest.config.ts`](vitest.config.ts), while the native merge command is declared in [`package.json`](package.json) and existing package aggregation remains in [`scripts/coverage-report.mjs`](scripts/coverage-report.mjs). - **Missing shards, assertion failures, and partial reruns cannot silently pass.** Unique artifact names, hidden-file uploads, all four required filenames, and the final failure fan-in are asserted by [`scripts/coverage-config.test.mjs`](scripts/coverage-config.test.mjs). - **The CI contract is documented for future changes.** The rationale and operational flow are recorded in [`docs/oss/ci-pipeline.md`](docs/oss/ci-pipeline.md) and the package coverage guides. ## Testing performed - `pnpm build` — 85 tasks passed - `pnpm test:scripts` — 498 tests passed - `node --test scripts/coverage-config.test.mjs scripts/coverage-report.test.mjs` — 34 tests passed - `pnpm lint:workflows` - `pnpm exec biome check vitest.config.ts scripts/coverage-config.test.mjs package.json turbo.json` - `pnpm exec turbo run build --dry=json` - Parsed `.github/workflows/ci.yml` with the installed `yaml` package - `git diff --check` - Synthetic two-shard Vitest 5 smoke test — generated both expected blob files, merged both source maps, replayed both tests, and passed combined 100% thresholds ## Skill update n/a — internal CI orchestration only; no user-facing CLI, API, configuration, error, or terminology changes. ## Alternatives considered - **Use cache transport:** cache prefix matching restores only one matching entry rather than all shard outputs, which would require four explicit restores. Cache fallback also suggests cross-attempt reuse that GitHub's artifact model intentionally avoids. - **Merge raw JSON manually:** Vitest's blob merger already preserves test failures, project metadata, and Istanbul hit counters, avoiding a custom coverage-merging implementation. - **Apply thresholds in every shard:** each shard sees only partial execution, so this would create false failures and would not represent repository coverage. - **Keep the single runner and increase workers:** package coverage is already worker-capped to protect PGlite/PostgreSQL stability; horizontal runners improve wall time without oversubscribing one machine. ## Checklist - [x] All commits are signed off (`git commit -s`) per the DCO. - [x] I read `CONTRIBUTING.md` and the change is scoped to one logical concern. - [x] Tests are updated. - [ ] The PR title is in `TML-NNNN: <sentence-case title>` form — n/a, this infrastructure change has no Linear ticket and follows the repository's conventional-title precedent. - [x] The **Skill update** section is filled in. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Package tests now run across four parallel CI shards. * Added coverage report merging for sharded test runs. * Coverage thresholds are applied after all shard results are combined. * Example tests now run as a dedicated CI check. * **Documentation** * Updated testing and CI guides with the new sharded coverage workflow and command. * **Chores** * Excluded Vitest cache files from version control. * Improved CI checks and diagnostics for incomplete or failed test shards. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Steven McClankerton <tatarintsev@prisma.io> Co-authored-by: Steven McClankerton <tatarintsev@prisma.io> | 26 天前 | |
TML-3233: emit Models namespace + models const; Scalars, Shape, and ResultType on ORM queries (#30231) ## Linked issue Refs [TML-3233](https://linear.app/prisma-company/issue/TML-3233) · Linear project [Model and result types](https://linear.app/prisma-company/project/model-and-result-types-080d7caa544f). Design: [ADR 250](docs/architecture%20docs/adrs/ADR%20250%20-%20Models%20and%20views%20are%20emitted%20from%20the%20contract.md). ## At a glance ```ts import type { models, Models } from '../prisma/contract'; import type { Scalars, Shape } from '@prisma/orm-postgres/family-contract/types'; import type { ResultType } from '@prisma/orm-postgres/components/runtime'; // Models types are directly available in two forms. This one is a bit nicer to type: type User = typeof models.public.User; // And the raw model type which doesn't require `typeof`: type User2 = Models.public_User; // A plain query returns the scalar form of the model, ie. all its properties without relations type UserRow = Scalars<User>; // But you often need a selection of your model properties and its relations. In Prisma 7 and below // you'd use GetPayload<> for this. In Prisma 8, it looks like the following. Mark fields or // relations for inclusion with `'+'`, or exclusion with `'-'`. Naming only relations in `'+'` keeps every scalar. type UserResponse = Shape<User, { '-': 'passwordHash'; posts: { '+': 'id' | 'title' | 'comments' }; }>; // { id; name; email; ...; posts: { id; title; comments: Scalars<Comment>[] }[] } // This is useful in situations where you need to declare a type derived from your models, eg an // API response type (real code: examples/prisma-8-demo/src/orm-client/get-user-profile.ts). TypeScript will then enforce that the result of your function, which will // involve Prisma queries, matches the expected output shape. If you change the contract, the // resulting shape will update. export async function getUserWithPosts(id: string): Promise<UserResponse | null> { const user = await db.orm.public.User.where({ id }).include('posts', (p) => p.include('comments')).first(); if (user === null) return null; const { passwordHash, ...rest } = user; return { ...rest, posts: user.posts.map(({ id, title, comments }) => ({ id, title, comments })) }; } // And if you just want the exact type of a query you already wrote: const usersWithPosts = db.orm.public.User.include('posts'); type UserWithPosts = ResultType<typeof usersWithPosts>; ``` Before this PR, `contract.d.ts` exported no model type, and `ResultType` returned `never` on ORM queries. ## Summary Two Prisma 7 users asked where `Prisma.User` and `Prisma.BookGetPayload<{ include: { author: true } }>` went. Prisma 8 had no answer: models were reachable only as `FieldOutputTypes['public']['User']`, and naming a query result meant `NonNullable<Awaited<ReturnType<typeof q.first>>>`. This PR gives users the model type, the scalar row a default fetch returns, a data structure derived from the model with picked or dropped scalars and nested relations, and the result type of any ORM query. ## Skill update `skills/prisma-8/references/queries.md` gains a "Naming model and result types" section and the routing table in `skills/prisma-8/SKILL.md` routes "type of my model", `GetPayload`, `ResultType`, `Scalars`, `Shape`, and `Models` prompts to it. ## Decision The model is the whole row plus its relations, as written in PSL. A query result is a view on it. Selection belongs to the query, not the model. [ADR 250](docs/architecture%20docs/adrs/ADR%20250%20-%20Models%20and%20views%20are%20emitted%20from%20the%20contract.md) records this. Concretely, this PR ships: 1. `contract.d.ts` emits `export namespace Models` with one member per model, the namespace folded into the member name (`public_User`, `unbound_Audit`; bare names on targets without namespaces), an `Any<Base>` union per polymorphic base, and `export declare const models` for dotted type-only access. 2. `RelationKeys`, `Scalars<M>`, and `Shape<M, Spec>` in `framework-components`, re-exported by both family contract packages. `Shape` takes an object spec: `'+'` keeps named scalars and relations, `'-'` drops scalars, any other key is a relation with a nested spec, and wrong names or `'+'` beside `'-'` are compile errors on the offending key (`projects/model-and-result-types/shape-design-brief.md`). 3. A `_row` phantom on both ORM collections so the existing `ResultType` works on ORM queries. 4. Type tests proving the ORM's rows equal `Scalars` and `Shape` of the emitted models (plain includes, projections, select plus include, nested includes, polymorphism) for SQL and for Mongo, and a demo test that declares an endpoint response with `Shape` and returns a transformed query result from a function with that return type. 5. Relation nullability is recorded on the contract. Every one-to-one and many-to-one relation carries `nullable`, set from the `?` on the relation field in PSL or from the TypeScript builder, and read by the emitter and both ORMs. A contract written before this change loads unchanged: a missing flag is derived at hydration from the foreign-key columns' storage nullability, and a present flag is checked against storage on load. The emitter requires the flag on the contracts it consumes. The storage-plane reconstruction in the ORM types and the emitter hook that mirrored it are gone. 6. Every emitted fixture regenerated, a reference page, and the user skill update. ## Reviewer notes - The largest diff is fixture regeneration: every emitted `contract.d.ts` in the repo gains the `Models` block, including the 204 port fixtures and the migration snapshot stores that `pnpm fixtures:check` did not cover before. The check now covers them through `test/integration/scripts/emit-fixture-configs.mjs` and `scripts/refresh-contract-snapshot.mjs`. 143 `contract.json` files change, and every changed line is the `_generated` banner, which those stale fixtures had never picked up. Spot-check `packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts` and the polymorphism fixture under `test/integration/test/sql-orm-client/fixtures/polymorphism/`. - A default fetch returns `Scalars<Model>`, not the model. This is the opposite of Prisma 7, where the generated `User` is scalars-only, and the reference page says so in its first paragraph. - The namespace is in the member name wherever a collision is possible: on Postgres a default-schema model is `Models.unbound_User`, because `public.User` can sit beside it, matching `db.enums` and the contract views. A target whose descriptor declares `namespaceSupport: 'none'` (SQLite) emits bare names, `Models.User` and `models.User`. The choice is a declared target capability, never a count of the namespaces in a particular contract. - A relation whose target is a polymorphic base is emitted as the `Any<Base>` union, because that is what the ORM returns for such includes. - Relation nullability comes from the schema, not storage. `author User?` gives `nullable: true`, `author User` gives `false`; authoring rejects a required relation field over a nullable foreign key and the reverse. The side of a one-to-one that does not own the foreign key is always nullable, in PSL and in the builder, because nothing guarantees the related row exists. A refined to-one include is `| null` regardless, because the refinement can exclude the row. - Mongo follows the schema too. Its ORM used to type every to-one include as `| null`; now it reads the flag. A required reference whose document is missing comes back with the key absent at runtime, which the type no longer admits. That is a general read-time concern on Mongo, recorded in `projects/model-and-result-types/deferred.md`, not changed here. - `contract.json` gains one boolean per to-one relation. The domain section is canonicalized as empty for the storage hash, so no contract hash changes, and migration snapshots written before this change still load because a missing flag is derived at hydration. Seven ported `.prisma` schemas had an optional relation field over a required foreign key, which authoring now rejects; the `?` was removed and no emitted type changed. - The to-one nullability rule lives once, in `@internal/contract-authoring`, and the two PSL interpreters and two TypeScript builders call it. `Scalars` and `RelationNamesOf` infer the relation keys without a constraint fallback so they work in projects without `exactOptionalPropertyTypes`; a test compiles the fixture with that flag off. - Mongo embedded models carry no `RelationKeys` phantom, so `Scalars` of an owner's embed field equals the ORM's embed row. - `Shape` flattens each level so `toEqualTypeOf` can compare it with ORM rows and hover text shows one object. - Adjacent fixes in the second commit: `examples/prisma-8-demo` now passes `pnpm lint` (every bare throw uses a named `Error` subclass from `src/errors.ts` or `TypeError`), and the `no-bare-cast`, `no-bare-throw`, and `no-family-vocabulary` plugins exclude `.test.tsx`, `.test.mts`, and `.test.cts` the same way they exclude `.test.ts`. Forty orphan `contract.*` copies under `relation-mode-gh-*` fixtures, produced by an older emit and referenced by nothing, are deleted. - Left as they are, with reasons in the commit: two hand-authored minimal `contract.d.ts` test inputs, two telemetry-backend snapshots whose PSL no longer exists, 44 snapshots under `examples/prisma-8-demo/fixtures/*/migrations` that no script produces, and two vendored extension snapshots whose refresh belongs to the extension install flow. - Pre-existing flakes, not caused here: `@prisma/orm-framework test/module-identity.test.ts` races on `pnpm pack`'s skill sync and passes on rerun; `driver-adapters-error-forwarding › correctly forwards error for queryRaw` is a `test.fails` port that now passes, identically with the old fixture restored. - This PR is also the project's close-out: the transient artifacts under `projects/model-and-result-types/` are deleted in the last commit, ADR 250 and the reference page are the durable record, the final retro is in `drive/retro/findings.md`, and every deferred item has a Linear issue (TML-3235, 3236, 3237, 3242 to 3246). ## How it fits together 1. **Framework types.** `packages/1-framework/1-core/framework-components/src/execution/model-types.ts` declares the `RelationKeys` unique symbol and the two utilities. Both are distributive, so they work on `Any<Base>` unions. `Shape` validates the spec through a mapped constraint over the spec's own keys and reads each relation's wrapper from the model's own field type. 2. **Emission.** `packages/1-framework/3-tooling/emitter/src/model-types-emission.ts` renders the block from `contract.domain`, sharing the field-type resolver with `FieldOutputTypes` via `resolveModelFieldType`, and reads each to-one relation's `nullable` for the `| null` wrapper. Name collisions, non-identifier names, and unresolvable same-space relation targets throw structured errors before anything is written. 3. **ORM phantoms.** One `declare readonly _row?: Row` line on `CollectionImpl` and one `readonly _row?: SimplifyDeep<IncludedRow<...>>` member on `MongoCollection`. 4. **Proof.** Type tests in both ORM packages and the demo assert equality between the ORM's rows and the emitted types for every fixture model, every cardinality, polymorphic roots and variants, projections, nested and refined includes, and every `Shape` refusal as a `@ts-expect-error`. 5. **Docs.** `docs/reference/model-and-result-types.md`, with every snippet copied from a passing type test; ADR 250; the subsystem doc paragraph; README links; the user skill. ## Behavior changes & evidence - **`contract.d.ts` exports `Models` and `models`.** `packages/1-framework/3-tooling/emitter/src/model-types-emission.ts`, `packages/1-framework/3-tooling/emitter/src/generate-contract-dts.ts`. Evidence: `packages/1-framework/3-tooling/emitter/test/model-types-emission.test.ts`, `packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts`. - **`Scalars` and `Shape` are exported from both families' contract types.** `packages/1-framework/1-core/framework-components/src/execution/model-types.ts`, `packages/2-sql/1-core/contract/src/exports/types.ts`, `packages/2-mongo-family/1-foundation/mongo-contract/src/exports/index.ts`. Evidence: `packages/1-framework/1-core/framework-components/test/model-types.test-d.ts`. - **`ResultType` works on ORM collections.** `packages/3-extensions/sql-orm-client/src/collection.ts`, `packages/2-mongo-family/5-query-builders/orm/src/collection.ts`. Evidence: `packages/3-extensions/sql-orm-client/test/model-types.test-d.ts`, `packages/2-mongo-family/5-query-builders/orm/test/model-types.test-d.ts`. - **Relation nullability is a contract fact.** `packages/1-framework/0-foundation/contract/src/domain-types.ts`, `packages/1-framework/0-foundation/contract/src/validate-domain.ts`, `packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts`. Evidence: the validator tests beside `validate-domain.ts`, the PSL interpreter tests in both families, and `packages/3-extensions/sql-orm-client/test/include-cardinality.test-d.ts`. ## Project close-out Project definition of done, from the spec: | Item | Evidence | | --- | --- | | Repo checks, `pnpm fixtures:check`, Linear close-out | CI green on the previous head; `fixtures:check` stable across two runs; TML-3233 In Review, TML-3234 Done | | Every test in the spec's test list exists and passes | `packages/1-framework/1-core/framework-components/test/shape.test-d.ts`, `packages/3-extensions/sql-orm-client/test/model-types.test-d.ts`, `packages/2-mongo-family/5-query-builders/orm/test/model-types.test-d.ts`, `packages/1-framework/3-tooling/emitter/test/model-types-emission.test.ts`, `examples/prisma-8-demo/test/demo-dx.types.test.ts` | | All fixtures regenerated; `contract.json` changes limited to the new field | `pnpm fixtures:check` covers the port fixtures and snapshot stores; JSON diffs are `nullable` flags and the `_generated` banner only | | Docs page, index and README links, subsystem paragraph, ADR | `docs/reference/model-and-result-types.md`, `docs/README.md`, both ORM READMEs, subsystem doc 2, ADR 250 | | One PR over 1,000 lines on this branch | This PR | Migration: nothing to migrate; ADR 250 and the reference page were written in place during the project. Reference strip: no file outside the project folder referenced it. Deleted: `projects/model-and-result-types/` (spec, plan, design notes, design brief, Shape brief, slice 2 brief, deferred). Retro: `drive/retro/findings.md` 2026-09-10, with F20 in `drive/calibration/failure-modes.md` and a new project-DoD item in `drive/calibration/dod.md`. ## Testing performed - `pnpm build` (root) - `pnpm test:packages` (1186 files; one pre-existing race in `module-identity.test.ts`, passes on rerun) - `pnpm test` in framework-components (635), emitter (222), SQL emitter (184), Mongo emitter (64), sql-orm-client (787), Mongo ORM (235), prisma-8-demo (74) - `pnpm typecheck` and `pnpm lint` in every touched package (demo lint failure pre-existing, see reviewer notes) - `pnpm lint:deps`, `pnpm lint:skills` - `pnpm fixtures:check` (stable across two runs; `contract.json` diffs are the `_generated` banner only) - `pnpm test:integration` (372/373 files; the one failure is the pre-existing `test.fails` port noted above) - `pnpm lint:casts`, `pnpm lint:throws`, `pnpm lint:framework-vocabulary` ## Follow-ups - Rename `ResultType` to `Result` (open question in the brief; not done here). - `Scalars` versus `Row` naming, and the `_` separator, are open in the brief and can be changed before release. ## Alternatives considered - **Emit `GetPayload`-style types per query.** Ties the contract to one lane's vocabulary and grows `contract.d.ts` without bound. - **`With<M, 'rel'>`, a model plus a union of relation names.** The first draft. Replaced by `Shape` before merge: it could not drop or narrow scalars or nest, so an endpoint response still needed hand-written types. `With<User, 'posts'>` is `Shape<User, { posts: {} }>`. - **Prisma 7's boolean form, `{ id: true; posts: { title: true } }`.** Verbose: every scalar must be listed for the wide case. `'+'`/`'-'` sigils were chosen over words because words collide with field names. - **A relation-selection parameter on the contract, `Model<Contract, 'User', { posts: { comments: true } }>`.** Takes the contract rather than the model and reads as a query. `Shape` is a pure utility over the model type and describes end states, not queries. - **A parameter mapping relation name to the model type that sits there.** Makes the user import and restate what the contract already knows. - **`Models.public.User` as nested TypeScript namespaces.** `namespace public` does not compile; `public` is reserved in strict mode and is the default Postgres schema. Folding the schema into the member name gives the importable form; the declared constant gives the dotted form. - **Flat `export type User` at the top level.** Adding a second `User` in another schema would silently remove the alias and break every import of it. - **A runtime `db.models` accessor.** Either an object pretending to be a row or a definition object whose `typeof` is not the model. Dotted access already exists with no runtime. - **A separate `RowOf` helper for ORM queries.** `ResultType` exists and is documented; the collections now carry the marker it reads. ## Checklist - [x] All commits are signed off (`git commit -s`) per the [DCO](../CONTRIBUTING.md#developer-certificate-of-origin-dco). - [x] I read [CONTRIBUTING.md](../CONTRIBUTING.md) and the change is scoped to one logical concern. - [x] Tests are updated. - [x] The PR title is in `TML-NNNN: <sentence-case title>` form. - [x] The **Skill update** section above is filled in. ## Notes for the reviewer See Reviewer notes above. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 13 天前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 8 个月前 | ||
| 7 个月前 | ||
| 12 天前 | ||
| 7 天前 | ||
| 1 个月前 | ||
| 26 天前 | ||
| 13 天前 |