@stacksjs/defaults 0.75.8 → 0.75.10

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.
@@ -1,18 +1,23 @@
1
1
  import { Action } from '@stacksjs/actions'
2
- import { config } from '@stacksjs/config'
2
+ import { resolveDefaultsResources } from '@stacksjs/actions/dev/defaults-resources'
3
+ import { config, feature, overridesReady, resolveViewPatterns } from '@stacksjs/config'
3
4
  import { db } from '@stacksjs/database/runtime'
4
- import { response } from '@stacksjs/router'
5
+ import { mountedDefaultRouteBundles, response } from '@stacksjs/router'
5
6
  import { existsSync, readdirSync, statSync } from 'node:fs'
6
- import { join } from 'node:path'
7
+ import { join, resolve, sep } from 'node:path'
7
8
 
8
9
  /**
9
10
  * Generate the top-level sitemap.xml.
10
11
  *
11
12
  * Strategy:
12
- * 1. Walk resources/views and storage/framework/defaults/resources/views
13
- * collecting static pages — file paths that don't contain `[`
14
- * (dynamic) or `[...]` (catch-all). The static set covers the
15
- * home page, /about, /faq, /products listing, etc.
13
+ * 1. Walk the view roots the views server serves - the app's views, and
14
+ * whichever framework default views `ui.defaultViews` and the mounted
15
+ * route bundles leave in (`resolveViewPatterns`) - collecting static
16
+ * pages: file paths that don't contain `[` (dynamic) or `[...]`
17
+ * (catch-all). The static set covers the home page, /about, /faq,
18
+ * /products listing, etc. This walked the whole defaults tree whatever
19
+ * the app had turned off, so a sitemap could advertise a page the
20
+ * server answers 404 for.
16
21
  * 2. For dynamic patterns (e.g. resources/views/products/[slug].stx)
17
22
  * fan out using the database — currently the storefront's
18
23
  * `products` table by `slug`. Apps with other dynamic models
@@ -30,7 +35,6 @@ import { join } from 'node:path'
30
35
  */
31
36
 
32
37
  const PROJECT_VIEWS = 'resources/views'
33
- const FRAMEWORK_VIEWS = 'storage/framework/defaults/resources/views'
34
38
 
35
39
  // Path prefixes that should never be indexed. Matched as a literal
36
40
  // prefix on the URL path (so '/cart' matches '/cart' and '/cart/x').
@@ -151,12 +155,40 @@ async function discoverProducts(): Promise<SitemapEntry[]> {
151
155
  }
152
156
  }
153
157
 
154
- function staticEntries(): SitemapEntry[] {
158
+ /**
159
+ * The view roots the views server serves, and the files under them it does
160
+ * not, resolved exactly as `dev/views.ts` and `production-server.ts` resolve
161
+ * them. Package view roots are left out: this sitemap never listed them.
162
+ */
163
+ async function servedViewRoots(): Promise<{ roots: string[], isExcluded: (file: string) => boolean }> {
164
+ await overridesReady
165
+ const resolution = resolveViewPatterns(
166
+ PROJECT_VIEWS,
167
+ join(resolveDefaultsResources(), 'views'),
168
+ config.ui?.defaultViews,
169
+ existsSync,
170
+ [],
171
+ mountedDefaultRouteBundles(feature),
172
+ )
173
+ const excluded = resolution.exclude.map(entry => resolve(entry))
174
+ return {
175
+ roots: resolution.patterns,
176
+ isExcluded: (file) => {
177
+ const full = resolve(file)
178
+ return excluded.some(entry => full === entry || full.startsWith(`${entry}${sep}`))
179
+ },
180
+ }
181
+ }
182
+
183
+ async function staticEntries(): Promise<SitemapEntry[]> {
155
184
  const seen = new Set<string>()
156
185
  const entries: SitemapEntry[] = []
186
+ const { roots, isExcluded: isWithheld } = await servedViewRoots()
157
187
 
158
- for (const root of [PROJECT_VIEWS, FRAMEWORK_VIEWS]) {
188
+ for (const root of roots) {
159
189
  for (const file of walkViews(root)) {
190
+ if (isWithheld(file))
191
+ continue
160
192
  const urlPath = fileToUrl(root, file)
161
193
  if (!urlPath)
162
194
  continue
@@ -223,7 +255,7 @@ export default new Action({
223
255
  async handle() {
224
256
  const siteUrl = (config as any).app?.url || 'http://localhost:3000'
225
257
  const all: SitemapEntry[] = [
226
- ...staticEntries(),
258
+ ...(await staticEntries()),
227
259
  ...(await discoverProducts()),
228
260
  ]
229
261
 
package/bootstrap.ts CHANGED
@@ -23,7 +23,7 @@
23
23
 
24
24
  import { feature } from '@stacksjs/config'
25
25
  import { frameworkPath } from '@stacksjs/path'
26
- import { route } from '@stacksjs/router'
26
+ import { bundleMounts, DEFAULT_ROUTE_BUNDLE_FEATURES, route } from '@stacksjs/router'
27
27
  import MaintenanceMiddleware from './app/Middleware/Maintenance'
28
28
 
29
29
  // Global maintenance / coming-soon gate. Registered first so the
@@ -59,18 +59,16 @@ const selection = (globalThis as Record<string, unknown>).__stacksDefaultRouteBu
59
59
  { bundles: Set<string>, explicit: boolean } | undefined
60
60
 
61
61
  /**
62
- * Whether a bundle mounts.
62
+ * Whether a bundle mounts: named by the app, or, when the app named none, its
63
+ * feature flag is on.
63
64
  *
64
- * An app that NAMED its bundles has already answered the question, so the
65
- * feature flag does not get a second veto - otherwise `STACKS_DEFAULT_ROUTES=auth`
66
- * would still be withheld from an app running with `dashboard` off, which is
67
- * the exact case this exists for. When nothing was named, the flags gate
68
- * precisely as they did before.
65
+ * The rule and the flag each bundle rides on live in the router
66
+ * (`bundleMounts`, `DEFAULT_ROUTE_BUNDLE_FEATURES`), because the views server
67
+ * asks the same question from another process - it serves `/login` only when
68
+ * this file mounts `POST /login` - and two copies of one rule drift.
69
69
  */
70
- function mounts(bundle: string, featureEnabled: boolean): boolean {
71
- if (selection && !selection.bundles.has(bundle))
72
- return false
73
- return selection?.explicit ? true : featureEnabled
70
+ function mounts(bundle: keyof typeof DEFAULT_ROUTE_BUNDLE_FEATURES): boolean {
71
+ return bundleMounts(selection, bundle, feature(DEFAULT_ROUTE_BUNDLE_FEATURES[bundle]))
74
72
  }
75
73
 
76
74
  // Auth: login, registration, logout, refresh/revoke, passkeys, TOTP 2FA and
@@ -88,7 +86,7 @@ function mounts(bundle: string, featureEnabled: boolean): boolean {
88
86
  // `/generate-two-factor-secret`, `/logout-all` and `/auth/tokens` — in apps
89
87
  // currently running with `dashboard` off. Widening an app's public surface on
90
88
  // upgrade is not something a refactor gets to do silently.
91
- if (mounts('auth', feature('dashboard')))
89
+ if (mounts('auth'))
92
90
  await route.register(frameworkPath('defaults/routes/auth.ts'))
93
91
 
94
92
  // The rest of dashboard.ts: email subscribe, storefront cart/checkout,
@@ -100,7 +98,7 @@ if (mounts('auth', feature('dashboard')))
100
98
  // Apps that need only a slice can also define the routes they want directly
101
99
  // in `routes/api.ts` — first-registration-wins means the user version takes
102
100
  // priority.
103
- if (mounts('dashboard', feature('dashboard'))) {
101
+ if (mounts('dashboard')) {
104
102
  await route.register(frameworkPath('defaults/routes/dashboard.ts'))
105
103
  // JSON endpoints for the dev dashboard UI. Kept separate from the view
106
104
  // routes above so the data layer is one obvious file to grep.
@@ -137,21 +135,21 @@ if (mounts('dashboard', feature('dashboard'))) {
137
135
  // non-default mount path register their own routes in `routes/api.ts`
138
136
  // and the framework's mount silently no-ops since user routes
139
137
  // register first.
140
- if (mounts('email', feature('email'))) {
138
+ if (mounts('email')) {
141
139
  await route.register(frameworkPath('defaults/routes/email.ts'))
142
140
  }
143
141
 
144
142
  // Form-builder public endpoints (`@stacksjs/forms`). Gated on the `forms`
145
143
  // feature, which defaults OFF - an app opts in with config/forms.ts
146
144
  // (`buddy forms:install`) and only then do the public submit routes exist.
147
- if (mounts('forms', feature('forms'))) {
145
+ if (mounts('forms')) {
148
146
  await route.register(frameworkPath('defaults/routes/forms.ts'))
149
147
  }
150
148
 
151
149
  // Courier delivery endpoints: position ingest and the stop/route lifecycle a
152
150
  // courier's device drives. Gated with the rest of the commerce surface, since
153
151
  // they are meaningless without the delivery models behind them.
154
- if (mounts('delivery', feature('commerce'))) {
152
+ if (mounts('delivery')) {
155
153
  await route.register(frameworkPath('defaults/routes/delivery.ts'))
156
154
  }
157
155
 
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.75.8",
5
+ "version": "0.75.10",
6
6
  "description": "A modern Stacks development environment.",
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.8",
5
+ "version": "0.75.10",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/stacksjs/stacks.git",
@@ -55,7 +55,7 @@
55
55
  "dependencies": {
56
56
  "@iconify-json/f7": "^1.2.2",
57
57
  "@iconify-json/hugeicons": "^1.2.27",
58
- "@stacksjs/mobile": "^0.75.8",
58
+ "@stacksjs/mobile": "^0.75.10",
59
59
  "@stacksjs/sanitizer": "^0.2.113",
60
60
  "ts-qr-codes": "^0.1.8"
61
61
  }
@@ -1,3 +1,19 @@
1
+ <script server>
2
+ /**
3
+ * A development tool: it lists scenarios that throw on purpose. The routes
4
+ * that fill it (`/test-error/*`, TestErrorAction) are only mounted for a local
5
+ * deployment, but the view fallback served this file on its own, so every
6
+ * deployed app answered 200 at /errors/tester with an empty "Error Page
7
+ * Tester" shell. Same gate as those routes.
8
+ */
9
+ import { isLocalDeployment } from '@stacksjs/env'
10
+
11
+ if (!isLocalDeployment())
12
+ notFound()
13
+
14
+ const scenarios = typeof context !== 'undefined' && Array.isArray(context?.scenarios) ? context.scenarios : []
15
+ </script>
16
+
1
17
  <div class="mx-auto px-6 py-12 max-w-3xl">
2
18
  <h1 class="mb-2 font-semibold text-3xl text-neutral-950 dark:text-white">Error Page Tester</h1>
3
19
  <p class="mb-8 text-neutral-500 text-sm dark:text-neutral-400">