@stacksjs/defaults 0.75.40 → 0.75.42

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.
Files changed (100) hide show
  1. package/ai/skills/stacks-actions/SKILL.md +1 -1
  2. package/ai/skills/stacks-auto-imports/SKILL.md +1 -1
  3. package/ai/skills/stacks-chat/SKILL.md +43 -1
  4. package/ai/skills/stacks-cloud/SKILL.md +36 -0
  5. package/ai/skills/stacks-dashboard/SKILL.md +16 -2
  6. package/ai/skills/stacks-database/SKILL.md +24 -3
  7. package/ai/skills/stacks-deploy/SKILL.md +7 -1
  8. package/ai/skills/stacks-desktop/SKILL.md +22 -0
  9. package/ai/skills/stacks-development/SKILL.md +1 -1
  10. package/ai/skills/stacks-migrations/SKILL.md +2 -2
  11. package/ai/skills/stacks-models/SKILL.md +38 -2
  12. package/ai/skills/stacks-orm/SKILL.md +2 -2
  13. package/ai/skills/stacks-security/SKILL.md +28 -1
  14. package/app/Actions/Auth/DataExportAction.test.ts +84 -0
  15. package/app/Actions/Auth/DataExportAction.ts +40 -0
  16. package/app/Actions/Dashboard/Content/FileReprocessAction.ts +1 -1
  17. package/app/Actions/Dashboard/Content/file-manager.ts +120 -8
  18. package/app/Actions/Dashboard/Content/file-metadata.ts +108 -12
  19. package/app/Actions/Dashboard/Content/file-pipeline.test.ts +13 -9
  20. package/app/Actions/Dashboard/Content/file-preview.test.ts +487 -0
  21. package/app/Actions/Dashboard/Content/file-preview.ts +548 -0
  22. package/app/Actions/Payment/CreatePaymentIntentAction.ts +1 -1
  23. package/app/Actions/Payment/CreateSetupIntentAction.ts +1 -1
  24. package/app/Jobs/GenerateStoragePreviewJob.ts +47 -0
  25. package/app/Jobs/OptimizeStorageImageJob.ts +7 -3
  26. package/app/Jobs/PruneRetainedDataJob.ts +46 -0
  27. package/app/Models/Activity.ts +2 -0
  28. package/app/Models/CampaignSend.ts +2 -0
  29. package/app/Models/Card.ts +1 -0
  30. package/app/Models/CardComment.ts +1 -0
  31. package/app/Models/Comment.ts +5 -0
  32. package/app/Models/CommunicationSuppression.ts +2 -1
  33. package/app/Models/ConsentEvent.ts +4 -3
  34. package/app/Models/Content/Author.ts +5 -0
  35. package/app/Models/EmailListSubscriber.ts +1 -0
  36. package/app/Models/EmailSuppression.ts +2 -0
  37. package/app/Models/Forms/FormSubmission.ts +5 -0
  38. package/app/Models/GdprRequest.ts +77 -0
  39. package/app/Models/MagicLinkToken.ts +2 -0
  40. package/app/Models/Notification.ts +2 -0
  41. package/app/Models/NotificationDelivery.ts +4 -0
  42. package/app/Models/PaymentMethod.ts +5 -0
  43. package/app/Models/PaymentTransaction.ts +1 -0
  44. package/app/Models/PersonalAccessToken.ts +3 -0
  45. package/app/Models/Referral.ts +4 -1
  46. package/app/Models/ReferralCode.ts +4 -1
  47. package/app/Models/Request.ts +3 -0
  48. package/app/Models/SmsOptOut.ts +2 -0
  49. package/app/Models/SocialAccount.ts +3 -0
  50. package/app/Models/SocialPost.ts +2 -0
  51. package/app/Models/StorageItemTask.ts +14 -8
  52. package/app/Models/Subscriber.ts +2 -0
  53. package/app/Models/SubscriberEmail.ts +2 -0
  54. package/app/Models/Subscription.ts +1 -0
  55. package/app/Models/TeamInvitation.ts +2 -0
  56. package/app/Models/TeamMember.ts +1 -0
  57. package/app/Models/User.ts +5 -0
  58. package/app/Models/commerce/Bid.ts +3 -0
  59. package/app/Models/commerce/Cart.ts +2 -0
  60. package/app/Models/commerce/Courier.ts +7 -0
  61. package/app/Models/commerce/CourierPing.ts +3 -0
  62. package/app/Models/commerce/Customer.ts +5 -0
  63. package/app/Models/commerce/DeliveryStop.ts +7 -0
  64. package/app/Models/commerce/GiftCard.ts +4 -0
  65. package/app/Models/commerce/Order.ts +5 -0
  66. package/app/Models/commerce/OrderItem.ts +2 -0
  67. package/app/Models/commerce/Payment.ts +5 -0
  68. package/app/Models/commerce/Pledge.ts +3 -0
  69. package/app/Models/commerce/Transaction.ts +2 -0
  70. package/app/Models/commerce/WaitlistProduct.ts +5 -0
  71. package/app/Models/commerce/WaitlistRestaurant.ts +4 -0
  72. package/ide/vscode/.vscode/extensions.json +1 -1
  73. package/ide/vscode/.vscode/settings.json +8 -149
  74. package/ide/vscode/README.md +92 -22
  75. package/ide/vscode/build.ts +43 -0
  76. package/ide/vscode/package.json +368 -29
  77. package/ide/vscode/scripts/stx-contributes.ts +66 -0
  78. package/ide/vscode/src/env-file.ts +103 -0
  79. package/ide/vscode/src/env.ts +241 -0
  80. package/ide/vscode/src/extension.ts +153 -0
  81. package/ide/vscode/src/pickier-protocol.ts +51 -0
  82. package/ide/vscode/src/pickier-worker.ts +92 -0
  83. package/ide/vscode/src/pickier.ts +433 -0
  84. package/ide/vscode/src/project.ts +450 -0
  85. package/ide/vscode/src/stacks.ts +482 -0
  86. package/ide/vscode/tests/build.test.ts +58 -0
  87. package/ide/vscode/tests/env.test.ts +166 -0
  88. package/ide/vscode/tests/extension.test.ts +428 -0
  89. package/ide/vscode/tests/fake-vscode.ts +228 -0
  90. package/ide/vscode/tests/manifest.test.ts +88 -0
  91. package/ide/vscode/tests/pickier.test.ts +202 -0
  92. package/ide/vscode/tests/project.test.ts +268 -0
  93. package/ide/vscode/tsconfig.json +19 -0
  94. package/package.json +4 -4
  95. package/resources/components/Dashboard/Content/FileManagerDashboard.stx +2 -0
  96. package/resources/components/Dashboard/FileViewer/MediaCard.stx +14 -3
  97. package/routes/auth.ts +4 -0
  98. package/vcs/github/workflows/buddy-bot.yml +2 -2
  99. package/vcs/github/workflows/ci.yml +3 -3
  100. package/vcs/github/workflows/release.yml +1 -1
@@ -0,0 +1,268 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import {
3
+ argumentHint,
4
+ buddyCommandLine,
5
+ choosePreviewUrl,
6
+ configuredAppPath,
7
+ freeTextCommandLine,
8
+ isLoopbackUrl,
9
+ loadProjectEnv,
10
+ parseBuddyList,
11
+ parseBuddyListJson,
12
+ parseBuddyListText,
13
+ parseDevBannerUrl,
14
+ parseEnvFile,
15
+ pickProjectRoot,
16
+ planPreview,
17
+ resolvePrettyDomain,
18
+ } from '../src/project'
19
+
20
+ describe('parseEnvFile', () => {
21
+ it('reads plain, exported, quoted and commented values', () => {
22
+ const env = parseEnvFile([
23
+ '# a comment',
24
+ 'APP_NAME=Stacks',
25
+ 'export PORT=3100',
26
+ 'APP_URL="my-app.localhost"',
27
+ 'QUOTED=\'single # not a comment\'',
28
+ 'TRAILING=value # a comment',
29
+ 'EMPTY=',
30
+ 'not a pair',
31
+ '',
32
+ ].join('\n'))
33
+
34
+ expect(env).toEqual({
35
+ APP_NAME: 'Stacks',
36
+ PORT: '3100',
37
+ APP_URL: 'my-app.localhost',
38
+ QUOTED: 'single # not a comment',
39
+ TRAILING: 'value',
40
+ EMPTY: '',
41
+ })
42
+ })
43
+
44
+ it('handles CRLF line endings', () => {
45
+ expect(parseEnvFile('PORT=3200\r\nAPP_URL=x.test\r\n')).toEqual({ PORT: '3200', APP_URL: 'x.test' })
46
+ })
47
+ })
48
+
49
+ describe('loadProjectEnv', () => {
50
+ it('lets later dev env files override earlier ones, as @stacksjs/env loads them', () => {
51
+ const files: Record<string, string> = {
52
+ '.env': 'PORT=3000\nAPP_URL=stacks.localhost',
53
+ '.env.local': 'PORT=3100',
54
+ '.env.development.local': 'APP_URL=override.localhost',
55
+ }
56
+
57
+ expect(loadProjectEnv(file => files[file])).toEqual({ PORT: '3100', APP_URL: 'override.localhost' })
58
+ })
59
+
60
+ it('skips dotenvx ciphertext instead of returning it', () => {
61
+ const env = loadProjectEnv(file => file === '.env' ? 'PORT=3000\nAPP_URL=encrypted:BCx9abc' : undefined)
62
+ expect(env).toEqual({ PORT: '3000' })
63
+ })
64
+ })
65
+
66
+ describe('resolvePrettyDomain', () => {
67
+ it('mirrors buddy dev: a non-loopback APP_URL is a pretty domain', () => {
68
+ expect(resolvePrettyDomain('stacks.localhost')).toBe('stacks.localhost')
69
+ expect(resolvePrettyDomain('https://My-App.test/path')).toBe('my-app.test')
70
+ expect(resolvePrettyDomain('localhost:3000')).toBeNull()
71
+ expect(resolvePrettyDomain('http://127.0.0.1')).toBeNull()
72
+ expect(resolvePrettyDomain(undefined)).toBeNull()
73
+ })
74
+ })
75
+
76
+ describe('configuredAppPath', () => {
77
+ it('prefers APP_PATH, then appPath in config/app.ts, then /', () => {
78
+ const config = 'export default {\n name: \'x\',\n appPath: \'/dashboard\',\n}'
79
+ expect(configuredAppPath({ APP_PATH: 'admin' }, config)).toBe('/admin')
80
+ expect(configuredAppPath({}, config)).toBe('/dashboard')
81
+ expect(configuredAppPath({}, 'export default { name: \'x\' }')).toBe('/')
82
+ expect(configuredAppPath({})).toBe('/')
83
+ })
84
+ })
85
+
86
+ describe('planPreview', () => {
87
+ it('defaults to https://stacks.localhost with localhost:3000 behind it', () => {
88
+ expect(planPreview({ env: {} })).toEqual({
89
+ server: { host: 'localhost', port: 3000 },
90
+ candidates: [
91
+ { url: 'https://stacks.localhost', host: 'stacks.localhost', port: 443 },
92
+ { url: 'http://localhost:3000', host: 'localhost', port: 3000 },
93
+ ],
94
+ })
95
+ })
96
+
97
+ it('uses PORT, APP_URL and the entry path', () => {
98
+ const plan = planPreview({ env: { PORT: '3140', APP_URL: 'https://shop.test', APP_PATH: '/admin' } })
99
+ expect(plan.server).toEqual({ host: 'localhost', port: 3140 })
100
+ expect(plan.candidates.map(candidate => candidate.url)).toEqual(['https://shop.test/admin', 'http://localhost:3140/admin'])
101
+ })
102
+
103
+ it('stays on localhost for STACKS_DEV_LOCALHOST=1, a loopback APP_URL, or the setting', () => {
104
+ const only = (plan: ReturnType<typeof planPreview>) => plan.candidates.map(candidate => candidate.url)
105
+ expect(only(planPreview({ env: { STACKS_DEV_LOCALHOST: '1' } }))).toEqual(['http://localhost:3000'])
106
+ expect(only(planPreview({ env: { APP_URL: 'http://localhost:3000' } }))).toEqual(['http://localhost:3000'])
107
+ expect(only(planPreview({ env: {}, preferLocalhost: true }))).toEqual(['http://localhost:3000'])
108
+ })
109
+
110
+ it('ignores a PORT that is not a number', () => {
111
+ expect(planPreview({ env: { PORT: 'abc', STACKS_DEV_LOCALHOST: '1' } }).server.port).toBe(3000)
112
+ })
113
+ })
114
+
115
+ describe('choosePreviewUrl', () => {
116
+ const plan = planPreview({ env: {} })
117
+ const probeOf = (open: string[]) => async (host: string, port: number) => open.includes(`${host}:${port}`)
118
+
119
+ it('returns nothing when the frontend port is closed, even if the shared proxy answers', async () => {
120
+ expect(await choosePreviewUrl(plan, probeOf(['stacks.localhost:443']))).toBeUndefined()
121
+ })
122
+
123
+ it('prefers the pretty URL when both the server and the proxy answer', async () => {
124
+ expect(await choosePreviewUrl(plan, probeOf(['localhost:3000', 'stacks.localhost:443']))).toBe('https://stacks.localhost')
125
+ })
126
+
127
+ it('falls back to localhost when the proxy is down', async () => {
128
+ expect(await choosePreviewUrl(plan, probeOf(['localhost:3000']))).toBe('http://localhost:3000')
129
+ })
130
+ })
131
+
132
+ describe('parseDevBannerUrl', () => {
133
+ it('reads the App/Site line of the buddy dev banner, colors and all', () => {
134
+ const banner = [
135
+ '\u001B[32m➜\u001B[39m \u001B[1mApp \u001B[22m: \u001B[36mhttps://stacks.localhost/dashboard\u001B[39m',
136
+ ' ➜ Site: https://stacks.localhost',
137
+ ' ➜ API: https://stacks.localhost/api',
138
+ ].join('\n')
139
+
140
+ expect(parseDevBannerUrl(banner)).toBe('https://stacks.localhost/dashboard')
141
+ expect(parseDevBannerUrl(' ➜ Site : http://localhost:3001')).toBe('http://localhost:3001')
142
+ })
143
+
144
+ it('returns undefined before the banner is printed', () => {
145
+ expect(parseDevBannerUrl('Starting dev server...\n ➜ API: http://localhost:3008')).toBeUndefined()
146
+ })
147
+ })
148
+
149
+ const LIST_TEXT = `[env] loaded 33/49 variables from .env
150
+
151
+ Available Commands:
152
+
153
+ Development:
154
+ build Build any of your libraries (packages) for production use
155
+ dev Start development server
156
+
157
+ make:
158
+ make:model Create a new Model
159
+ make:action Create a new Action
160
+
161
+ upgrade:
162
+ upgrade:dependencies Upgrade your dependencies (pantry.yaml & package.json)
163
+ upgrade:bun Upgrade Bun to the latest version
164
+
165
+ Total: 6 commands
166
+ `
167
+
168
+ describe('parseBuddyListText', () => {
169
+ it('reads the indented command lines and skips headings and the total', () => {
170
+ expect(parseBuddyListText(LIST_TEXT).map(command => [command.name, command.description])).toEqual([
171
+ ['build', 'Build any of your libraries (packages) for production use'],
172
+ ['dev', 'Start development server'],
173
+ ['make:action', 'Create a new Action'],
174
+ ['make:model', 'Create a new Model'],
175
+ ['upgrade:bun', 'Upgrade Bun to the latest version'],
176
+ ['upgrade:dependencies', 'Upgrade your dependencies (pantry.yaml & package.json)'],
177
+ ])
178
+ })
179
+ })
180
+
181
+ const LIST_JSON = `[env] loaded 33/49 variables from .env
182
+ ${JSON.stringify({
183
+ commands: [
184
+ { name: 'make:model', description: 'Create a new Model', usage: '$ buddy make:model <name>', arguments: [{ name: 'name', required: true, variadic: false }], options: [] },
185
+ { name: 'dev', description: 'Start development server', usage: '$ buddy dev [server]', arguments: [{ name: 'server', required: false, variadic: false }], options: [] },
186
+ { name: 'rm -rf /; x', description: 'not a command name' },
187
+ ],
188
+ total: 3,
189
+ }, null, 2)}`
190
+
191
+ describe('parseBuddyListJson', () => {
192
+ it('reads the inventory past any leading log line and drops malformed names', () => {
193
+ expect(parseBuddyListJson(LIST_JSON)).toEqual([
194
+ { name: 'dev', description: 'Start development server', usage: 'buddy dev [server]', arguments: [{ name: 'server', required: false, variadic: false }] },
195
+ { name: 'make:model', description: 'Create a new Model', usage: 'buddy make:model <name>', arguments: [{ name: 'name', required: true, variadic: false }] },
196
+ ])
197
+ })
198
+
199
+ it('returns undefined for output that is not the JSON inventory', () => {
200
+ expect(parseBuddyListJson(LIST_TEXT)).toBeUndefined()
201
+ expect(parseBuddyListJson('{ not json')).toBeUndefined()
202
+ expect(parseBuddyListJson('{"something":"else"}')).toBeUndefined()
203
+ })
204
+
205
+ it('parseBuddyList falls back to the text listing', () => {
206
+ expect(parseBuddyList(LIST_TEXT)).toHaveLength(6)
207
+ expect(parseBuddyList(LIST_JSON)).toHaveLength(2)
208
+ })
209
+ })
210
+
211
+ describe('command lines', () => {
212
+ it('always runs the project ./buddy', () => {
213
+ expect(buddyCommandLine('dev')).toBe('./buddy dev')
214
+ expect(buddyCommandLine('make:model', 'Post')).toBe('./buddy make:model Post')
215
+ expect(buddyCommandLine('dev', ['frontend', '--with-localhost'])).toBe('./buddy dev frontend --with-localhost')
216
+ expect(buddyCommandLine('migrate', ' ')).toBe('./buddy migrate')
217
+ })
218
+
219
+ it('refuses a name that is not a buddy command', () => {
220
+ expect(() => buddyCommandLine('dev; rm -rf ~')).toThrow()
221
+ expect(() => buddyCommandLine('')).toThrow()
222
+ })
223
+
224
+ it('turns free text into a ./buddy line, dropping a typed buddy/bud/stacks prefix', () => {
225
+ expect(freeTextCommandLine('migrate --diff')).toBe('./buddy migrate --diff')
226
+ expect(freeTextCommandLine('buddy migrate --diff')).toBe('./buddy migrate --diff')
227
+ expect(freeTextCommandLine('./buddy make:model Post')).toBe('./buddy make:model Post')
228
+ expect(freeTextCommandLine('bud test')).toBe('./buddy test')
229
+ expect(freeTextCommandLine('stacks list')).toBe('./buddy list')
230
+ expect(freeTextCommandLine('buddyfile')).toBe('./buddy buddyfile')
231
+ expect(freeTextCommandLine('buddy')).toBe('./buddy')
232
+ expect(freeTextCommandLine(' ')).toBeUndefined()
233
+ })
234
+
235
+ it('hints positional arguments', () => {
236
+ expect(argumentHint({
237
+ name: 'x',
238
+ description: '',
239
+ arguments: [
240
+ { name: 'name', required: true, variadic: false },
241
+ { name: 'files', required: false, variadic: true },
242
+ ],
243
+ })).toBe('<name> [files...]')
244
+ })
245
+ })
246
+
247
+ describe('pickProjectRoot', () => {
248
+ const projects = new Set(['/work/app', '/work/other-app'])
249
+ const isProject = (folder: string) => projects.has(folder)
250
+
251
+ it('prefers the active editor folder when it is a Stacks project', () => {
252
+ expect(pickProjectRoot(['/work/docs', '/work/app', '/work/other-app'], '/work/other-app', isProject)).toBe('/work/other-app')
253
+ })
254
+
255
+ it('falls back to the first Stacks folder', () => {
256
+ expect(pickProjectRoot(['/work/docs', '/work/app'], '/work/docs', isProject)).toBe('/work/app')
257
+ expect(pickProjectRoot(['/work/docs'], undefined, isProject)).toBeUndefined()
258
+ })
259
+ })
260
+
261
+ describe('isLoopbackUrl', () => {
262
+ it('detects URLs that need port forwarding in a remote window', () => {
263
+ expect(isLoopbackUrl('http://localhost:3000')).toBe(true)
264
+ expect(isLoopbackUrl('http://[::1]:3000')).toBe(true)
265
+ expect(isLoopbackUrl('https://stacks.localhost')).toBe(false)
266
+ expect(isLoopbackUrl('not a url')).toBe(false)
267
+ })
268
+ })
@@ -0,0 +1,19 @@
1
+ {
2
+ // The extension is its own little package: a Node (CommonJS) bundle loaded by
3
+ // VS Code, not part of the app or the framework, so no other tsconfig
4
+ // includes it. `bun run typecheck` here checks it.
5
+ "compilerOptions": {
6
+ "target": "es2022",
7
+ "lib": ["es2022"],
8
+ "module": "esnext",
9
+ "moduleResolution": "bundler",
10
+ "types": ["bun", "vscode"],
11
+ "strict": true,
12
+ "noUncheckedIndexedAccess": true,
13
+ "noEmit": true,
14
+ "skipLibCheck": true,
15
+ "isolatedModules": true,
16
+ "verbatimModuleSyntax": true
17
+ },
18
+ "include": ["src/**/*.ts", "scripts/**/*.ts", "tests/**/*.ts"]
19
+ }
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.40",
5
+ "version": "0.75.42",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/stacksjs/stacks.git",
@@ -54,9 +54,9 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@iconify-json/f7": "^1.2.2",
57
- "@iconify-json/hugeicons": "^1.2.27",
58
- "@stacksjs/mobile": "^0.75.40",
59
- "@stacksjs/sanitizer": "^0.2.113",
57
+ "@iconify-json/hugeicons": "^1.2.35",
58
+ "@stacksjs/mobile": "^0.75.42",
59
+ "@stacksjs/sanitizer": "^0.2.348",
60
60
  "ts-qr-codes": "^0.1.8"
61
61
  }
62
62
  }
@@ -12,6 +12,8 @@ interface FileItem {
12
12
  mime_type?: string
13
13
  url?: string
14
14
  thumbnail?: string
15
+ preview?: string
16
+ previewText?: string
15
17
  starred: false
16
18
  shared: boolean
17
19
  items?: FileItem[]
@@ -7,6 +7,10 @@ interface FileItem {
7
7
  mime_type?: string
8
8
  url?: string
9
9
  thumbnail?: string
10
+ /** A generated preview image (stacksjs/stacks#308): a scaled photo, a waveform, a font specimen. */
11
+ preview?: string
12
+ /** The first lines of a text file, shown as text rather than an image. */
13
+ previewText?: string
10
14
  }
11
15
 
12
16
  const EMPTY_ITEM: FileItem = {
@@ -49,8 +53,14 @@ function kindOf(value: FileItem): string {
49
53
 
50
54
  const kind = derived(() => kindOf(currentItem()))
51
55
  const extension = derived(() => (currentItem().type || '').toUpperCase())
52
- const previewUrl = derived(() => currentItem().thumbnail || currentItem().url || '')
53
- const hasImage = derived(() => kind() === 'image' && Boolean(previewUrl()))
56
+ // The generated preview wins over the original: it is a small PNG for any kind
57
+ // that could be drawn, where the original may be a 40 MB photo or not an image
58
+ // at all. Only an image falls back to the original, because only an image can
59
+ // be shown by pointing an <img> at it.
60
+ const previewUrl = derived(() => currentItem().preview || (kind() === 'image' ? currentItem().thumbnail || currentItem().url || '' : ''))
61
+ const hasImage = derived(() => Boolean(previewUrl()))
62
+ const hasText = derived(() => !hasImage() && Boolean(currentItem().previewText))
63
+ const imageClass = derived(() => currentItem().preview && kind() !== 'image' ? 'object-contain h-full w-full' : 'object-cover h-full w-full')
54
64
 
55
65
  const backdropByKind: Record<string, string> = {
56
66
  image: 'bg-gradient-to-br from-sky-50 to-indigo-100 dark:from-neutral-800 dark:to-neutral-900',
@@ -79,7 +89,8 @@ const badgeClass = derived(() => kind() === 'video'
79
89
 
80
90
  <div class="flex flex-col">
81
91
  <div :class="previewClass()">
82
- <img :if="hasImage()" :src="previewUrl()" :alt="currentItem().name" loading="lazy" class="object-cover h-full w-full" />
92
+ <img :if="hasImage()" :src="previewUrl()" :alt="currentItem().name" loading="lazy" :class="imageClass()" />
93
+ <pre :else-if="hasText()" class="absolute inset-0 overflow-hidden m-0 pb-2 pt-7 px-3 font-mono leading-[1.35] text-[9px] text-left text-stone-600 whitespace-pre dark:text-neutral-300 bg-white/80 dark:bg-neutral-900/60">{{ currentItem().previewText }}</pre>
83
94
  <div :else-if="kind() === 'video'" class="flex items-center justify-center h-12 w-12 bg-white/90 rounded-full shadow-md"><span class="ml-0.5 h-5 w-5 text-neutral-900 i-hugeicons-play"></span></div>
84
95
  <span :else :class="glyphClass()"></span>
85
96
  <span :if="currentItem().type !== 'folder' && extension()" :class="badgeClass()">{{ extension() }}</span>
package/routes/auth.ts CHANGED
@@ -104,6 +104,10 @@ route.group({ middleware: 'auth' }, () => {
104
104
  route.get('/referrals', 'Actions/Auth/ReferralSummaryAction').rateLimit(60, 'minute')
105
105
  route.post('/referrals/code', 'Actions/Auth/CreateReferralCodeAction').rateLimit(10, 'minute')
106
106
  route.get('/me', 'Actions/Auth/AuthUserAction')
107
+ // The caller's own personal data as a JSON download (GDPR access and
108
+ // portability, stacksjs/stacks#365). Reads every table the user touches,
109
+ // so it is held to a few an hour; each one is recorded in gdpr_requests.
110
+ route.get('/me/data-export', 'Actions/Auth/DataExportAction').rateLimit(3, 'hour')
107
111
  route.post('/logout', 'Actions/Auth/LogoutAction')
108
112
  // Sign out everywhere: revoke every access/refresh token AND destroy
109
113
  // every session for the authenticated user (stacksjs/stacks#1957).
@@ -83,9 +83,9 @@ jobs:
83
83
  persist-credentials: true
84
84
 
85
85
  - name: Setup Pantry
86
- uses: pantry-pm/pantry/packages/action@bc2f37441f1a7ed160c201bbe6e0552b81ca931e # v0.11.65
86
+ uses: pantry-pm/pantry/packages/action@00f7f8d0f4b166db35d2e1405fdcefd442acd775 # v0.11.68
87
87
  with:
88
- version: '0.11.65'
88
+ version: '0.11.68'
89
89
  install: 'false'
90
90
 
91
91
  - name: Install Dependencies
@@ -30,7 +30,7 @@ jobs:
30
30
  uses: actions/checkout@v4
31
31
 
32
32
  - name: Setup Pantry
33
- uses: pantry-pm/pantry/packages/action@bc2f37441f1a7ed160c201bbe6e0552b81ca931e # v0.11.65
33
+ uses: pantry-pm/pantry/packages/action@00f7f8d0f4b166db35d2e1405fdcefd442acd775 # v0.11.68
34
34
  with:
35
35
  install: 'false'
36
36
 
@@ -63,7 +63,7 @@ jobs:
63
63
  uses: actions/checkout@v4
64
64
 
65
65
  - name: Setup Pantry
66
- uses: pantry-pm/pantry/packages/action@bc2f37441f1a7ed160c201bbe6e0552b81ca931e # v0.11.65
66
+ uses: pantry-pm/pantry/packages/action@00f7f8d0f4b166db35d2e1405fdcefd442acd775 # v0.11.68
67
67
  with:
68
68
  install: 'false'
69
69
 
@@ -99,7 +99,7 @@ jobs:
99
99
  uses: actions/checkout@v4
100
100
 
101
101
  - name: Setup Pantry
102
- uses: pantry-pm/pantry/packages/action@bc2f37441f1a7ed160c201bbe6e0552b81ca931e # v0.11.65
102
+ uses: pantry-pm/pantry/packages/action@00f7f8d0f4b166db35d2e1405fdcefd442acd775 # v0.11.68
103
103
  with:
104
104
  install: 'false'
105
105
 
@@ -21,7 +21,7 @@ jobs:
21
21
  fetch-depth: 0
22
22
 
23
23
  - name: Setup Pantry
24
- uses: pantry-pm/pantry/packages/action@bc2f37441f1a7ed160c201bbe6e0552b81ca931e # v0.11.65
24
+ uses: pantry-pm/pantry/packages/action@00f7f8d0f4b166db35d2e1405fdcefd442acd775 # v0.11.68
25
25
  with:
26
26
  install: 'false'
27
27