@stacksjs/defaults 0.75.465 → 0.75.468

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,6 +1,6 @@
1
1
  ---
2
2
  name: stacks-actions
3
- description: Use when working with Stacks server actions - creating actions in app/Actions/, auto-generated API actions from the useApi model trait, the 675 default framework actions (auth, dashboard, commerce, content, deployment, jobs), action request/response handling, or action registration. Covers @stacksjs/actions and storage/framework/defaults/app/Actions/.
3
+ description: Use when working with Stacks server actions - creating actions in app/Actions/, auto-generated API actions from the useApi model trait, the 676 default framework actions (auth, dashboard, commerce, content, deployment, jobs), action request/response handling, or action registration. Covers @stacksjs/actions and storage/framework/defaults/app/Actions/.
4
4
  license: MIT
5
5
  compatibility: Bun >= 1.3.0, TypeScript
6
6
  allowed-tools: Read Edit Write Bash Grep Glob
@@ -175,6 +175,18 @@ component rather than duplicate it. Compile and test the actual screen bundle
175
175
  for both targets. External authorization or checkout routes may use the system
176
176
  browser with an explicit return path.
177
177
 
178
+ A named route parameter may need to exclude website paths. Configure
179
+ `nativeScreenConstraints` on both platforms using the exact route key, for
180
+ example `{ '/:username': { username: { pattern: '[a-z]+', exclude: ['terms'], normalize: 'lowercase' } } }`.
181
+ Normalization lowercases decoded parameters and excluded values independently
182
+ of device locale; the match returns the normalized parameter. Patterns then
183
+ match the complete value case-sensitively; use syntax shared by JavaScript,
184
+ Foundation and Kotlin. Constraints apply before query items and preserve static
185
+ route priority. The build requires Craft's `nativeRouteConstraints` capability
186
+ and stops if an installed host would ignore the configuration. For application
187
+ resolution, use the published browser-safe `craft-native/mobile`
188
+ `matchNativePath(screens, path, constraints)` rather than copying the matcher.
189
+
178
190
  For the web renderer, choose a primary content source:
179
191
 
180
192
  - `url`: load the deployed Stacks application and keep server-rendered routes.
@@ -1,7 +1,8 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions/runtime'
3
3
  import { response } from '@stacksjs/router'
4
- import { DashboardFileError, deleteDashboardFile } from './file-manager'
4
+ import { deleteDashboardFile } from './file-manager'
5
+ import { dashboardFileFailure } from './file-response'
5
6
 
6
7
  export default new Action({
7
8
  name: 'FileDestroyAction',
@@ -16,9 +17,7 @@ export default new Action({
16
17
  return response.json(deleted)
17
18
  }
18
19
  catch (error) {
19
- if (error instanceof DashboardFileError)
20
- return response.json({ message: error.message, fields: error.fields }, error.status)
21
- throw error
20
+ return dashboardFileFailure(error, 'The file could not be deleted.', 'FileDestroyAction')
22
21
  }
23
22
  },
24
23
  })
@@ -1,7 +1,8 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions/runtime'
3
3
  import { response } from '@stacksjs/router'
4
- import { createDashboardDirectory, DashboardFileError } from './file-manager'
4
+ import { createDashboardDirectory } from './file-manager'
5
+ import { dashboardFileFailure } from './file-response'
5
6
 
6
7
  export default new Action({
7
8
  name: 'FileDirectoryStoreAction',
@@ -17,9 +18,7 @@ export default new Action({
17
18
  return response.json(directory, 201)
18
19
  }
19
20
  catch (error) {
20
- if (error instanceof DashboardFileError)
21
- return response.json({ message: error.message, fields: error.fields }, error.status)
22
- throw error
21
+ return dashboardFileFailure(error, 'The folder could not be created.', 'FileDirectoryStoreAction')
23
22
  }
24
23
  },
25
24
  })
@@ -1,7 +1,8 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions/runtime'
3
3
  import { response } from '@stacksjs/router'
4
- import { DashboardFileError, duplicateDashboardFile } from './file-manager'
4
+ import { duplicateDashboardFile } from './file-manager'
5
+ import { dashboardFileFailure } from './file-response'
5
6
 
6
7
  export default new Action({
7
8
  name: 'FileDuplicateAction',
@@ -17,9 +18,7 @@ export default new Action({
17
18
  return response.json(duplicated)
18
19
  }
19
20
  catch (error) {
20
- if (error instanceof DashboardFileError)
21
- return response.json({ message: error.message, fields: error.fields }, error.status)
22
- throw error
21
+ return dashboardFileFailure(error, 'The file could not be duplicated.', 'FileDuplicateAction')
23
22
  }
24
23
  },
25
24
  })
@@ -1,7 +1,8 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions/runtime'
3
3
  import { response } from '@stacksjs/router'
4
- import { DashboardFileError, setDashboardFileFavorite } from './file-manager'
4
+ import { setDashboardFileFavorite } from './file-manager'
5
+ import { dashboardFileFailure } from './file-response'
5
6
 
6
7
  export default new Action({
7
8
  name: 'FileFavoriteAction',
@@ -17,9 +18,7 @@ export default new Action({
17
18
  return response.json(result)
18
19
  }
19
20
  catch (error) {
20
- if (error instanceof DashboardFileError)
21
- return response.json({ message: error.message, fields: error.fields }, error.status)
22
- throw error
21
+ return dashboardFileFailure(error, 'The favorite could not be saved.', 'FileFavoriteAction')
23
22
  }
24
23
  },
25
24
  })
@@ -1,7 +1,7 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions/runtime'
3
- import { response } from '@stacksjs/router'
4
- import { DashboardFileError, getDashboardFileSnapshot, normalizeDashboardFileLimit } from './file-manager'
3
+ import { getDashboardFileSnapshot, normalizeDashboardFileLimit } from './file-manager'
4
+ import { dashboardFileFailure } from './file-response'
5
5
 
6
6
  export default new Action({
7
7
  name: 'FileIndexAction',
@@ -16,9 +16,7 @@ export default new Action({
16
16
  })
17
17
  }
18
18
  catch (error) {
19
- if (error instanceof DashboardFileError)
20
- return response.json({ message: error.message, fields: error.fields }, error.status)
21
- throw error
19
+ return dashboardFileFailure(error, 'Files could not be loaded.', 'FileIndexAction')
22
20
  }
23
21
  },
24
22
  })
@@ -1,7 +1,8 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions/runtime'
3
3
  import { response } from '@stacksjs/router'
4
- import { DashboardFileError, renameDashboardFile } from './file-manager'
4
+ import { renameDashboardFile } from './file-manager'
5
+ import { dashboardFileFailure } from './file-response'
5
6
 
6
7
  export default new Action({
7
8
  name: 'FileRenameAction',
@@ -17,9 +18,7 @@ export default new Action({
17
18
  return response.json(renamed)
18
19
  }
19
20
  catch (error) {
20
- if (error instanceof DashboardFileError)
21
- return response.json({ message: error.message, fields: error.fields }, error.status)
22
- throw error
21
+ return dashboardFileFailure(error, 'The file could not be renamed.', 'FileRenameAction')
23
22
  }
24
23
  },
25
24
  })
@@ -1,7 +1,8 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions/runtime'
3
3
  import { response } from '@stacksjs/router'
4
- import { DashboardFileError, reprocessDashboardFile } from './file-manager'
4
+ import { reprocessDashboardFile } from './file-manager'
5
+ import { dashboardFileFailure } from './file-response'
5
6
 
6
7
  export default new Action({
7
8
  name: 'FileReprocessAction',
@@ -23,9 +24,7 @@ export default new Action({
23
24
  return response.json(result)
24
25
  }
25
26
  catch (error) {
26
- if (error instanceof DashboardFileError)
27
- return response.json({ message: error.message, fields: error.fields }, error.status)
28
- throw error
27
+ return dashboardFileFailure(error, 'The file could not be reprocessed.', 'FileReprocessAction')
29
28
  }
30
29
  },
31
30
  })
@@ -1,7 +1,8 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions/runtime'
3
3
  import { response } from '@stacksjs/router'
4
- import { DashboardFileError, setDashboardFileTags } from './file-manager'
4
+ import { setDashboardFileTags } from './file-manager'
5
+ import { dashboardFileFailure } from './file-response'
5
6
 
6
7
  export default new Action({
7
8
  name: 'FileTagsAction',
@@ -19,9 +20,7 @@ export default new Action({
19
20
  return response.json(result)
20
21
  }
21
22
  catch (error) {
22
- if (error instanceof DashboardFileError)
23
- return response.json({ message: error.message, fields: error.fields }, error.status)
24
- throw error
23
+ return dashboardFileFailure(error, 'The tags could not be saved.', 'FileTagsAction')
25
24
  }
26
25
  },
27
26
  })
@@ -2,7 +2,8 @@ import type { RequestInstance } from '@stacksjs/types'
2
2
  import type { UploadedFileLike } from '@stacksjs/storage'
3
3
  import { Action } from '@stacksjs/actions/runtime'
4
4
  import { response } from '@stacksjs/router'
5
- import { DashboardFileError, uploadDashboardFiles } from './file-manager'
5
+ import { uploadDashboardFiles } from './file-manager'
6
+ import { dashboardFileFailure } from './file-response'
6
7
 
7
8
  const MAX_FILES = 20
8
9
  const MAX_FILE_BYTES = 10 * 1024 * 1024
@@ -34,9 +35,7 @@ export default new Action({
34
35
  return response.json({ uploaded }, 201)
35
36
  }
36
37
  catch (error) {
37
- if (error instanceof DashboardFileError)
38
- return response.json({ message: error.message, fields: error.fields }, error.status)
39
- throw error
38
+ return dashboardFileFailure(error, 'The upload could not be completed.', 'FileUploadAction')
40
39
  }
41
40
  },
42
41
  })
@@ -1,7 +1,8 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions/runtime'
3
3
  import { response } from '@stacksjs/router'
4
- import { DashboardFileError, setDashboardFileVisibility } from './file-manager'
4
+ import { setDashboardFileVisibility } from './file-manager'
5
+ import { dashboardFileFailure } from './file-response'
5
6
 
6
7
  export default new Action({
7
8
  name: 'FileVisibilityAction',
@@ -17,9 +18,7 @@ export default new Action({
17
18
  return response.json(updated)
18
19
  }
19
20
  catch (error) {
20
- if (error instanceof DashboardFileError)
21
- return response.json({ message: error.message, fields: error.fields }, error.status)
22
- throw error
21
+ return dashboardFileFailure(error, 'The visibility could not be changed.', 'FileVisibilityAction')
23
22
  }
24
23
  },
25
24
  })
@@ -0,0 +1,61 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
2
+ import { afterEach, describe, expect, spyOn, test } from 'bun:test'
3
+ import FileIndexAction from './FileIndexAction'
4
+ import { DashboardFileError } from './file-manager'
5
+ import { dashboardFileFailure } from './file-response'
6
+
7
+ /**
8
+ * File actions answer a failure they did not write with a safe 503
9
+ * (stacksjs/stacks#3174). They rethrew it, and the router put the driver's own
10
+ * message - `relation "storage_items" does not exist` - in the response body
11
+ * in every environment, which the file manager then showed in its banner.
12
+ */
13
+ const driverMessage = 'relation "storage_items" does not exist'
14
+
15
+ let quiet: ReturnType<typeof spyOn> | null = null
16
+ afterEach(() => {
17
+ quiet?.mockRestore()
18
+ quiet = null
19
+ })
20
+
21
+ describe('dashboardFileFailure', () => {
22
+ test('keeps the status, message and fields of an error written to be shown', async () => {
23
+ const reply = dashboardFileFailure(
24
+ new DashboardFileError('Choose a limit between 1 and 5000.', 422, { limit: 'Too high.' }),
25
+ 'Files could not be loaded.',
26
+ 'FileIndexAction',
27
+ )
28
+
29
+ expect(reply.status).toBe(422)
30
+ expect(await reply.json()).toEqual({ message: 'Choose a limit between 1 and 5000.', fields: { limit: 'Too high.' } })
31
+ })
32
+
33
+ test('answers any other failure 503 with the safe message and logs the real one', async () => {
34
+ quiet = spyOn(console, 'error').mockImplementation(() => {})
35
+ const failure = new Error(driverMessage)
36
+
37
+ const reply = dashboardFileFailure(failure, 'Files could not be loaded.', 'FileIndexAction')
38
+
39
+ expect(reply.status).toBe(503)
40
+ expect(await reply.json()).toEqual({ message: 'Files could not be loaded.' })
41
+ expect(quiet).toHaveBeenCalledWith('[dashboard/api] FileIndexAction failed:', failure)
42
+ })
43
+ })
44
+
45
+ describe('FileIndexAction', () => {
46
+ test('does not send a driver error to the browser', async () => {
47
+ quiet = spyOn(console, 'error').mockImplementation(() => {})
48
+ const request = {
49
+ get: () => {
50
+ throw new Error(driverMessage)
51
+ },
52
+ } as unknown as RequestInstance
53
+
54
+ const reply = await FileIndexAction.handle(request) as Response
55
+
56
+ expect(reply.status).toBe(503)
57
+ const body = await reply.text()
58
+ expect(body).not.toContain('storage_items')
59
+ expect(JSON.parse(body)).toEqual({ message: 'Files could not be loaded.' })
60
+ })
61
+ })
@@ -0,0 +1,18 @@
1
+ import { response } from '@stacksjs/router'
2
+ import { dashboardOperationalError } from '../dashboard-response'
3
+ import { DashboardFileError } from './file-manager'
4
+
5
+ /**
6
+ * The response for a file action that threw.
7
+ *
8
+ * A `DashboardFileError` is written to be shown, so it keeps its status,
9
+ * message and fields. Anything else - the database behind `storage_items`
10
+ * being down or not migrated, a driver failing - is logged and answered 503
11
+ * with `message`, because rethrowing it let the router send the raw driver
12
+ * message to the browser in every environment (stacksjs/stacks#3174).
13
+ */
14
+ export function dashboardFileFailure(error: unknown, message: string, action: string): Response {
15
+ if (error instanceof DashboardFileError)
16
+ return response.json({ message: error.message, fields: error.fields }, error.status)
17
+ return dashboardOperationalError(error, message, action)
18
+ }
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.75.465",
5
+ "version": "0.75.468",
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.465",
5
+ "version": "0.75.468",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/stacksjs/stacks.git",
@@ -45,7 +45,7 @@
45
45
  "dependencies": {
46
46
  "@iconify-json/f7": "^1.2.2",
47
47
  "@iconify-json/hugeicons": "^1.2.35",
48
- "@stacksjs/mobile": "0.75.465",
48
+ "@stacksjs/mobile": "0.75.468",
49
49
  "@stacksjs/sanitizer": "0.2.798",
50
50
  "@stacksjs/stx": "0.2.798",
51
51
  "@xterm/addon-fit": "^0.11.0",