@stacksjs/defaults 0.75.229 → 0.75.231

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -652,3 +652,18 @@ or a fake success response.
652
652
  - Preserve the existing sidebar information architecture and styling when redesigning page content
653
653
  - Use `dashboard-modal-layer` for every fixed dashboard dialog or drawer
654
654
  - The live terminal component polls real deployment output and pauses while the document is hidden
655
+
656
+ ## Generic model exposure
657
+
658
+ Set `dashboard: { access: false }` on a model to exclude it server-side from
659
+ the generic model catalog and its totals, legacy highlighted model counts,
660
+ User/Subscriber count Actions, global model search, and generic model-browser
661
+ read/store/update/destroy Actions. Direct model-browser requests return 404
662
+ before row queries. Omission preserves existing exposure.
663
+
664
+ `dashboard.enabled: false` only hides a sidebar row and still permits generic
665
+ model access. `access` does not disable direct ORM calls, named domain Actions,
666
+ or separate routes generated by `useApi`. Configure `security.api.models` or
667
+ `STACKS_MODEL_APIS`, middleware, ownership and policies for those APIs. The
668
+ new boundary does not provide row ownership or permission checks for models
669
+ that remain exposed.
@@ -104,13 +104,19 @@ A Stacks project commits two lockfiles, and they describe one tree:
104
104
  **and** every npm package, which `pantry/` holds and the framework source
105
105
  resolves from.
106
106
 
107
- Since pantry 0.11.65 an npm range resolves to `bun.lock`'s pin whenever that
108
- pin satisfies it, so the two agree on every package they both name. Since
109
- 0.11.60 the file is platform-independent: `pantry install` on macOS writes what
110
- CI writes on Linux, foreign-platform records included.
107
+ Pantry follows `bun.lock`'s pin when it satisfies the declared npm range, so
108
+ shared npm records must agree. Native platform records can differ between
109
+ hosts. Review the generated difference and use the repository's lock guards:
110
+ an allowed platform addition does not excuse a missing or changed declared
111
+ dependency, stale workspace ranges, or a lost system-toolchain block.
112
+
113
+ **Regenerating.** Use the repository's declared, Pantry-managed Bun. Change the
114
+ ranges, run `bun install`, then `pantry install --ignore-scripts --quiet`, and
115
+ commit both lockfiles with the manifests. This is the metadata-only mode the
116
+ framework release action uses. A plain `pantry install` can activate project
117
+ services and run `postDatabaseSetup` or `postSetup` hooks, including migrations
118
+ and seeding; those are separate from maintaining dependency locks.
111
119
 
112
- **Regenerating.** Change the ranges, run `bun install`, then `pantry install`,
113
- on any platform, and commit `bun.lock` and `pantry.lock` with the manifests.
114
120
  Check `head -2 bun.lock` says `lockfileVersion: 2`; an older Bun on PATH
115
121
  downgrades it.
116
122
 
@@ -123,7 +123,12 @@ of forcing a cast. See `orm/tests/inference-attributes.test-d.ts`.
123
123
 
124
124
  Also at the top level: `indexes: [{ name, columns, unique?, where? }]` for
125
125
  composite and partial-unique indexes, and `dashboard: { highlight: true }` to
126
- feature the model in the admin UI.
126
+ feature the model in the admin UI. Use `dashboard: { access: false }` to
127
+ exclude a model from generic dashboard catalogs/counts/search and the generic
128
+ model-browser CRUD Actions before database work. Its default is `true`;
129
+ `dashboard.enabled: false` still only hides the sidebar row. `access` leaves
130
+ direct ORM calls, named domain Actions and generated `useApi` routes available.
131
+ Those APIs require their own route selection, middleware, ownership and policies.
127
132
 
128
133
  `useApi` is an API capability, not a dashboard-view generator. Its generated
129
134
  routes are registered from the merged model registry. Framework defaults are
@@ -11,7 +11,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
11
11
  | Package | Task guidance | Source entry | Retained tests |
12
12
  |---|---|---|---|
13
13
  | `@stacksjs/action-runner` | [`stacks-actions`](../stacks-actions/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/action-runner/src/index.ts) | 1 |
14
- | `@stacksjs/actions` | [`stacks-actions`](../stacks-actions/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/index.ts) | 72 |
14
+ | `@stacksjs/actions` | [`stacks-actions`](../stacks-actions/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/index.ts) | 73 |
15
15
  | `@stacksjs/ai` | [`stacks-ai`](../stacks-ai/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/ai/src/index.ts) | 17 |
16
16
  | `@stacksjs/alias` | [`stacks-alias`](../stacks-alias/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/alias/src/index.ts) | 1 |
17
17
  | `@stacksjs/analytics` | [`stacks-analytics`](../stacks-analytics/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/analytics/src/index.ts) | 6 |
@@ -21,7 +21,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
21
21
  | `@stacksjs/auth` | [`stacks-auth`](../stacks-auth/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/index.ts) | 118 |
22
22
  | `@stacksjs/browser` | [`stacks-browser`](../stacks-browser/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser/src/index.ts) | 8 |
23
23
  | `@stacksjs/browser-extension` | [`stacks-browser-extension`](../stacks-browser-extension/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser-extension/src/index.ts) | 7 |
24
- | `@stacksjs/buddy` | [`stacks-buddy`](../stacks-buddy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/index.ts) | 150 |
24
+ | `@stacksjs/buddy` | [`stacks-buddy`](../stacks-buddy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/index.ts) | 151 |
25
25
  | `@stacksjs/build` | [`stacks-build`](../stacks-build/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/build/src/index.ts) | 2 |
26
26
  | `@stacksjs/cache` | [`stacks-cache`](../stacks-cache/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/cache/src/index.ts) | 11 |
27
27
  | `@stacksjs/calendar-api` | [`stacks-calendar`](../stacks-calendar/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/calendar-api/src/index.ts) | 2 |
@@ -119,7 +119,7 @@ Declared runtime exports: `BlogAdminError`, `DEFAULT_IOS_DEPENDENCIES`, `DEFAULT
119
119
 
120
120
  Local source modules: [src/action.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/action.ts), [src/add.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/add.ts), [src/blog-admin.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/blog-admin.ts), [src/build/ios-toolchain.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/build/ios-toolchain.ts), [src/commit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/commit.ts), [src/dev/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/dev/index.ts), [src/discover-packages.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/discover-packages.ts), [src/generate/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/generate/index.ts), [src/github-release.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/github-release.ts), [src/helpers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/helpers/index.ts), [src/library/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/library/index.ts), [src/lint/lint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/lint/lint.ts), [src/lint/stx-gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/lint/stx-gate.ts), [src/make-command.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-command.ts), [src/make-job.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-job.ts), [src/make-policy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-policy.ts), [src/make-resource.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-resource.ts), [src/make.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make.ts), [src/scaffold-crud.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/scaffold-crud.ts), [src/setup/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/setup/index.ts), [src/stacks.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/stacks.ts), [src/upgrade/package-project.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/upgrade/package-project.ts), [src/upgrade/route-actions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/upgrade/route-actions.ts).
121
121
 
122
- Evidence directory: [72 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
122
+ Evidence directory: [73 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
123
123
 
124
124
  ### @stacksjs/ai
125
125
 
@@ -215,7 +215,7 @@ Read the entrypoint and package export map for re-exported or type-only surfaces
215
215
 
216
216
  Local source modules: [src/commands/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/commands/index.ts).
217
217
 
218
- Evidence directory: [150 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
218
+ Evidence directory: [151 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
219
219
 
220
220
  ### @stacksjs/build
221
221
 
@@ -3,6 +3,7 @@ import { Glob } from 'bun'
3
3
  import { Action } from '@stacksjs/actions/runtime'
4
4
  import { path } from '@stacksjs/path'
5
5
  import { countRows } from '../../../../resources/functions/dashboard/data'
6
+ import { resolveDashboardModel } from '../../../../resources/functions/dashboard/model-access'
6
7
  import { dashboardOperationalError } from '../dashboard-response'
7
8
 
8
9
  export default new Action({
@@ -16,23 +17,27 @@ export default new Action({
16
17
  const scanOptions = { cwd: path.userModelsPath(), onlyFiles: true }
17
18
  const displayModels: Model[] = []
18
19
 
19
- for await (const file of glob.scan(scanOptions)) {
20
- const model = (await import(path.userModelsPath(file))).default as Model
21
- if (model.dashboard?.highlight !== undefined && model.dashboard?.highlight !== false)
22
- displayModels.push(model)
23
- }
24
-
25
- if (!displayModels.length) {
26
- // If no models should be highlighted, we can just return up to 8 models in alphabetical order
27
20
  for await (const file of glob.scan(scanOptions)) {
28
- const model = (await import(path.userModelsPath(file))).default as Model
29
- displayModels.push(model)
21
+ const candidate = (await import(path.userModelsPath(file))).default as Model
22
+ const model = await resolveDashboardModel(candidate.name || '', candidate)
23
+ if (!model) continue
24
+ if (model.dashboard?.highlight !== undefined && model.dashboard?.highlight !== false)
25
+ displayModels.push(model)
26
+ }
27
+
28
+ if (!displayModels.length) {
29
+ // If no models should be highlighted, return up to 8 accessible models.
30
+ for await (const file of glob.scan(scanOptions)) {
31
+ const candidate = (await import(path.userModelsPath(file))).default as Model
32
+ const model = await resolveDashboardModel(candidate.name || '', candidate)
33
+ if (!model) continue
34
+ displayModels.push(model)
35
+ }
30
36
  }
31
- }
32
37
 
33
- if (displayModels.length > 8)
34
- displayModels.length = 8
35
- displayModels.sort((a, b) => (a.name || '').localeCompare(b.name || ''))
38
+ if (displayModels.length > 8)
39
+ displayModels.length = 8
40
+ displayModels.sort((a, b) => (a.name || '').localeCompare(b.name || ''))
36
41
 
37
42
  return await Promise.all(displayModels.map(async model => ({
38
43
  model: model.name,
@@ -1,5 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions/runtime'
2
- import { Subscriber } from '@stacksjs/orm'
2
+ import { response } from '@stacksjs/router'
3
+ import { resolveDashboardModel } from '../../../../resources/functions/dashboard/model-access'
3
4
  import { dashboardOperationalError } from '../dashboard-response'
4
5
 
5
6
  export default new Action({
@@ -10,7 +11,9 @@ export default new Action({
10
11
 
11
12
  async handle() {
12
13
  try {
13
- return await Subscriber.count()
14
+ const Model = await resolveDashboardModel('Subscriber')
15
+ if (!Model) return response.json({ message: 'Model not found.' }, 404)
16
+ return await Model.count()
14
17
  }
15
18
  catch (error) {
16
19
  return dashboardOperationalError(error, 'Subscriber total could not be loaded.', 'GetSubscriberCount')
@@ -1,5 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions/runtime'
2
- import { User } from '@stacksjs/orm'
2
+ import { response } from '@stacksjs/router'
3
+ import { resolveDashboardModel } from '../../../../resources/functions/dashboard/model-access'
3
4
  import { dashboardOperationalError } from '../dashboard-response'
4
5
 
5
6
  export default new Action({
@@ -10,7 +11,9 @@ export default new Action({
10
11
 
11
12
  async handle() {
12
13
  try {
13
- return await User.count()
14
+ const Model = await resolveDashboardModel('User')
15
+ if (!Model) return response.json({ message: 'Model not found.' }, 404)
16
+ return await Model.count()
14
17
  }
15
18
  catch (error) {
16
19
  return dashboardOperationalError(error, 'User total could not be loaded.', 'GetUserCount')
@@ -1,7 +1,8 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions/runtime'
3
3
  import { response as routerResponse } from '@stacksjs/router'
4
- import { loadModelIfExists, safeGet } from '../../../../resources/functions/dashboard/data'
4
+ import { safeGet } from '../../../../resources/functions/dashboard/data'
5
+ import { resolveDashboardModel } from '../../../../resources/functions/dashboard/model-access'
5
6
  import { dashboardOperationalError } from '../dashboard-response'
6
7
  import { isValidModelSlug, type ModelCreateField, type ModelWriteCapabilities, modelCreateFields, modelSchemaColumns, modelWritableColumns, modelWriteCapabilities, slugToPascal } from './model-write'
7
8
 
@@ -159,15 +160,6 @@ export function parseFilters(raw: string | null): Record<string, string> {
159
160
  return out
160
161
  }
161
162
 
162
- function resolveOrmModel(modelName: string): any {
163
- const injected = (globalThis as Record<string, any>)[modelName]
164
- // The ORM injects Proxies that answer `undefined` for every property
165
- // until their deferred load lands, so probe for a real query method
166
- // rather than trusting the name to be bound.
167
- if (injected && typeof injected.where === 'function') return injected
168
- return null
169
- }
170
-
171
163
  export default new Action({
172
164
  name: 'Dashboard Model Show',
173
165
  description: 'Queries a single model by URL slug with paging, sorting, search and column filters.',
@@ -221,7 +213,7 @@ export default new Action({
221
213
 
222
214
  let Model: any
223
215
  try {
224
- Model = resolveOrmModel(modelName) ?? await loadModelIfExists(modelName)
216
+ Model = await resolveDashboardModel(modelName)
225
217
  }
226
218
  catch (error) {
227
219
  return dashboardOperationalError(error, `${modelName} could not be loaded.`, 'ModelShowAction.resolve')
@@ -4,6 +4,7 @@ import { join } from 'node:path'
4
4
  import process from 'node:process'
5
5
  import { countRows } from '../../../../resources/functions/dashboard/data'
6
6
  import { dashboardOperationalError, dashboardOperationalIssue } from '../dashboard-response'
7
+ import { resolveDashboardModel } from '../../../../resources/functions/dashboard/model-access'
7
8
  import { modelApiConfiguration } from './model-api'
8
9
 
9
10
  /**
@@ -127,7 +128,7 @@ export default new Action({
127
128
  let userland: Array<Omit<DiscoveredModel, 'source'>>
128
129
  let defaults: Array<Omit<DiscoveredModel, 'source'>>
129
130
  try {
130
- userland = walkModels(userlandDir, false)
131
+ userland = walkModels(userlandDir, true)
131
132
  defaults = walkModels(defaultsDir, true)
132
133
  }
133
134
  catch (error) {
@@ -144,11 +145,12 @@ export default new Action({
144
145
  }
145
146
  }
146
147
 
147
- const enriched = await Promise.all(merged.map(async (m): Promise<ModelRow> => {
148
+ const candidates = await Promise.all(merged.map(async (m): Promise<ModelRow | null> => {
148
149
  let Model: any
149
150
  try {
150
151
  const module = await import(m.file)
151
- Model = module.default ?? module
152
+ Model = await resolveDashboardModel(m.name, module.default ?? module)
153
+ if (!Model) return null
152
154
  }
153
155
  catch (error) {
154
156
  return unavailableModelRow(
@@ -190,6 +192,7 @@ export default new Action({
190
192
  }
191
193
  }))
192
194
 
195
+ const enriched = candidates.filter((model): model is ModelRow => model !== null)
193
196
  enriched.sort((a, b) => a.name.localeCompare(b.name))
194
197
 
195
198
  const totalModels = enriched.length
@@ -6,7 +6,7 @@
6
6
  * casts and no observers; writing to it from a generic admin screen would
7
7
  * bypass every guarantee the ORM makes. Those rows stay read-only.
8
8
  */
9
- import { loadModelIfExists } from '../../../../resources/functions/dashboard/data'
9
+ import { resolveDashboardModel } from '../../../../resources/functions/dashboard/model-access'
10
10
  import { dashboardOperationalIssue } from '../dashboard-response'
11
11
  import { modelApiConfiguration } from './model-api'
12
12
 
@@ -301,10 +301,9 @@ export async function resolveWritableModel(slug: string, operation?: ModelWriteO
301
301
  return { error: 'Model slug must be lowercase kebab-case.', status: 400 }
302
302
 
303
303
  const modelName = slugToPascal(slug)
304
- const injected = (globalThis as Record<string, any>)[modelName]
305
304
  let Model: any
306
305
  try {
307
- Model = (injected && typeof injected.where === 'function') ? injected : await loadModelIfExists(modelName)
306
+ Model = await resolveDashboardModel(modelName)
308
307
  }
309
308
  catch (error) {
310
309
  return {
@@ -4,6 +4,7 @@ import { env } from '@stacksjs/env'
4
4
  import { readdirSync } from 'node:fs'
5
5
  import { basename, extname, join } from 'node:path'
6
6
  import process from 'node:process'
7
+ import { resolveDashboardModel } from '../../../../resources/functions/dashboard/model-access'
7
8
  import { dashboardOperationalError } from '../dashboard-response'
8
9
 
9
10
  interface SearchableModel {
@@ -126,7 +127,8 @@ async function loadSearchableModels(): Promise<SearchableModel[]> {
126
127
  for (const [fallbackName, file] of merged) {
127
128
  try {
128
129
  const module = await import(file)
129
- const definition = module.default ?? module
130
+ const definition = await resolveDashboardModel(fallbackName, module.default ?? module)
131
+ if (!definition) continue
130
132
  const name = String(definition.name || fallbackName)
131
133
  const search = definition.traits?.useSearch
132
134
  if (!search || typeof search !== 'object' || !Array.isArray(search.searchable)) continue
@@ -197,13 +199,15 @@ export default new Action({
197
199
  if (!q) return { results: {} }
198
200
 
199
201
  try {
202
+ const models = await catalog()
203
+ if (models.length === 0) return { results: {}, unavailable: [] }
200
204
  const { Database } = await import('bun:sqlite')
201
205
  const db = new Database(env.DB_DATABASE_PATH || 'database/stacks.sqlite', { readonly: true })
202
206
  const results: Record<string, SearchResult[]> = {}
203
207
  const unavailable: SearchUnavailable[] = []
204
208
 
205
209
  try {
206
- for (const model of await catalog()) {
210
+ for (const model of models) {
207
211
  if (Object.keys(results).length >= MAX_GROUPS) break
208
212
 
209
213
  const columns = db.query(`PRAGMA table_info(${model.table})`).all() as Array<{ name: string }>
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.75.229",
5
+ "version": "0.75.231",
6
6
  "description": "Stacks, built into VS Code: stx templates, pickier linting and formatting, encrypted env files, the dev server preview and every buddy command. No other extensions required.",
7
7
  "license": "MIT",
8
8
  "funding": "https://github.com/sponsors/chrisbbreuer",
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/defaults",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.229",
5
+ "version": "0.75.231",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/stacksjs/stacks.git",
@@ -44,7 +44,7 @@
44
44
  "dependencies": {
45
45
  "@iconify-json/f7": "^1.2.2",
46
46
  "@iconify-json/hugeicons": "^1.2.35",
47
- "@stacksjs/mobile": "0.75.229",
47
+ "@stacksjs/mobile": "0.75.231",
48
48
  "@stacksjs/sanitizer": "^0.2.534",
49
49
  "@xterm/addon-fit": "^0.11.0",
50
50
  "@xterm/xterm": "^6.0.0",
@@ -0,0 +1,15 @@
1
+ /** Server-side exposure boundary for the generic dashboard model surfaces. */
2
+ import type { DashboardModelOptions } from '@stacksjs/types'
3
+ import { loadModelIfExists } from './data'
4
+
5
+ /** Omission preserves the existing dashboard exposure. `enabled` only controls navigation. */
6
+ export function hasDashboardModelAccess(Model: { dashboard?: DashboardModelOptions } | null | undefined): boolean {
7
+ return Model?.dashboard?.access !== false
8
+ }
9
+
10
+ /** Resolve the native model, then exclude it before callers inspect metadata or query rows. */
11
+ export async function resolveDashboardModel(name: string, candidate?: any): Promise<any | null> {
12
+ const injected = (globalThis as Record<string, any>)[name]
13
+ const Model = (injected && typeof injected.where === 'function') ? injected : (candidate ?? await loadModelIfExists(name))
14
+ return Model && hasDashboardModelAccess(Model) ? Model : null
15
+ }
@@ -60,6 +60,9 @@ const fastCommands = [
60
60
  'build',
61
61
  'test',
62
62
  'lint',
63
+ // Repository documentation tools inspect source without application globals.
64
+ 'docs',
65
+ 'release',
63
66
  '--version',
64
67
  '-v',
65
68
  'version',