@appweaver/create-weaver-app 1.1.3 → 1.1.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appweaver/create-weaver-app",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Appweaver - the backend framework for AI-first development (@create-weaver-app)",
5
5
  "author": "Luka Matosevic",
6
6
  "license": "MIT",
@@ -285,6 +285,13 @@ export async function createAdminUser(): Promise<void> {
285
285
  | `weaver update` | Update all @appweaver/* packages |
286
286
  | `weaver openapi` | Generate OpenAPI specification |
287
287
 
288
+ ## Testing
289
+
290
+ - Test files must use the **`.test.ts`** extension.
291
+ - Unit tests live in `test/unit/`, end-to-end tests in `test/e2e/`; name each file after the module it covers.
292
+ - Add or update tests whenever a feature is added or existing behaviour changes, and run `npm run test` before
293
+ considering the task done.
294
+
288
295
  ## References
289
296
 
290
297
  - Application CLI (weaver): [cli.md](references/cli.md)
package/skill/SKILL.md CHANGED
@@ -569,6 +569,9 @@ npm run test # unit tests with coverage
569
569
  npm run e2e # e2e tests
570
570
  ```
571
571
 
572
+ Test files must use the **`.test.ts`** extension. Place unit tests in `test/unit/` and end-to-end tests in `test/e2e/`,
573
+ naming each file after its module. Add or update tests whenever a feature is added or existing behaviour changes.
574
+
572
575
  ### Format code
573
576
 
574
577
  ```sh
@@ -117,7 +117,7 @@ The config object is frozen with `Object.freeze()` after loading to prevent runt
117
117
  | `SERVER_TRUST_PROXY` | boolean | `true` | Trust `X-Forwarded-*` headers from reverse proxies. |
118
118
  | `SERVER_REQUEST_LOGGING_ENABLED` | boolean | `false` | Enable HTTP request/response logging. |
119
119
 
120
- ### Rate limiting (RATE*LIMIT*\*)
120
+ ### Rate limiting (RATE_LIMIT_\*)
121
121
 
122
122
  | Property | Type | Default | Description |
123
123
  |-------------------------|-----------|-----------|------------------------------------------------------------------|
@@ -135,7 +135,7 @@ The config object is frozen with `Object.freeze()` after loading to prevent runt
135
135
  | `SWAGGER_PATH` | string | `'/swagger'` | URL path for the Swagger UI. |
136
136
  | `SWAGGER_HIDE_UNTAGGED` | boolean | `false` | Hide untagged endpoints from documentation. |
137
137
 
138
- ### Health check (HEALTH*CHECK*\*)
138
+ ### Health check (HEALTH_CHECK_\*)
139
139
 
140
140
  | Property | Type | Default | Description |
141
141
  |-------------------------------|-----------|-------------|---------------------------------------------------------------|
@@ -306,13 +306,14 @@ The config object is frozen with `Object.freeze()` after loading to prevent runt
306
306
 
307
307
  ### File storage (STORAGE\_\*)
308
308
 
309
- | Property | Type | Default | Description |
310
- |------------------------------|---------|------------------------------------------------|-----------------------------------------------|
311
- | `STORAGE_PATH` | string | `'./storage'` | Base directory for file storage. |
312
- | `STORAGE_NAME_PATTERN` | string | `'{name}-{hash}.{extension}'` | File naming pattern for stored files. |
313
- | `STORAGE_CACHE_TTL` | integer | `86400000` | File storage cache TTL in milliseconds. (24h) |
314
- | `STORAGE_FILES_ROUTE_PREFIX` | string | `/files` | URL prefix for file access routes. |
315
- | `STORAGE_PROVIDER` | string | `'@appweaver/core/storage/filesystem-storage'` | Storage provider implementation path. |
309
+ | Property | Type | Default | Description |
310
+ |------------------------------|---------|------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
311
+ | `STORAGE_PATH` | string | `'./storage'` | Base directory for file storage. |
312
+ | `STORAGE_RESERVED_PATHS` | array | `['keys']` | Paths inside the storage directory that files can never be stored under. Each entry is a directory or a full file name relative to `STORAGE_PATH`, matched case-insensitively (no wildcards). Violations fail on startup for configured name patterns, and with a `400` error at runtime. |
313
+ | `STORAGE_NAME_PATTERN` | string | `'{name}-{hash}.{extension}'` | File naming pattern for stored files. |
314
+ | `STORAGE_CACHE_TTL` | integer | `86400000` | File storage cache TTL in milliseconds. (24h) |
315
+ | `STORAGE_FILES_ROUTE_PREFIX` | string | `/files` | URL prefix for file access routes. |
316
+ | `STORAGE_PROVIDER` | string | `'@appweaver/core/storage/filesystem-storage'` | Storage provider implementation path. |
316
317
 
317
318
  ### Redis (REDIS\_\*)
318
319
 
@@ -17,8 +17,8 @@ Appweaver supports four authentication methods that can be used independently or
17
17
  | API Key | `SECURITY_API_KEY_ENABLED` | `x-api-key: <id><delimiter><secret>` |
18
18
  | OAuth2 | Per-provider flags | Browser redirect flow |
19
19
 
20
- When multiple methods are enabled, the authentication middleware tries each in order. A request is authenticated if
21
- any one method succeeds. If no credentials are present for any method, a 401 error is returned.
20
+ When multiple methods are enabled, the authentication middleware tries each in order. A request is authenticated if any
21
+ one method succeeds. If no credentials are present for any method, a 401 error is returned.
22
22
 
23
23
  ### Route authentication configuration
24
24
 
@@ -53,9 +53,11 @@ if `SECURITY_JWT_SECRET` is set.
53
53
 
54
54
  ### Key management
55
55
 
56
- - RSA 2048-bit key pair generated automatically if `SECURITY_JWT_AUTO_GENERATE_KEYS` is `true` and key files are
57
- missing
56
+ - RSA 2048-bit key pair generated automatically if `SECURITY_JWT_AUTO_GENERATE_KEYS` is `true` and key files are missing
58
57
  - Keys stored at `SECURITY_JWT_PUBLIC_KEY_PATH` and `SECURITY_JWT_PRIVATE_KEY_PATH` (default: `./storage/keys/`)
58
+ - Generated keys are written with owner-only permissions (`0600` for the private key, `0700` for its directory)
59
+ - The default `STORAGE_RESERVED_PATHS` value (`['keys']`) makes the `keys` directory unusable by the file storage layer,
60
+ so uploads can never overwrite the default key location
59
61
  - If `SECURITY_JWT_SECRET` is set, HMAC signing is used instead of RSA
60
62
 
61
63
  ### Token types and scopes
@@ -121,8 +123,8 @@ When enabled, requests with an `Authorization: Basic` header are authenticated a
121
123
  }
122
124
  ```
123
125
 
124
- Uses `@fastify/basic-auth` plugin. Extracts Base64-encoded `username:password` from the header and validates against
125
- the user's stored password hash.
126
+ Uses `@fastify/basic-auth` plugin. Extracts Base64-encoded `username:password` from the header and validates against the
127
+ user's stored password hash.
126
128
 
127
129
  ---
128
130
 
@@ -185,8 +187,8 @@ Default delimiter is `AK`, so a key looks like: `42AKa1b2c3d4e5f6...`
185
187
 
186
188
  ## OAuth2 authentication
187
189
 
188
- Appweaver supports OAuth2 login with Google, Facebook, and a custom OpenID Connect provider. All OAuth2 providers
189
- follow the same flow pattern.
190
+ Appweaver supports OAuth2 login with Google, Facebook, and a custom OpenID Connect provider. All OAuth2 providers follow
191
+ the same flow pattern.
190
192
 
191
193
  ### OAuth2 flow
192
194
 
@@ -385,9 +387,9 @@ export default createAuthService({
385
387
  });
386
388
  ```
387
389
 
388
- **User avatar** — the provider's avatar/picture URL is passed to `registrationData` as `additionalData.avatarUrl`.
389
- When `SECURITY_OAUTH2_FETCH_AVATAR_ENABLED=true` (JSON: `security.oauth2.fetchAvatarEnabled`), the avatar image is
390
- also downloaded during registration and passed as `additionalData.avatarFile`
390
+ **User avatar** — the provider's avatar/picture URL is passed to `registrationData` as `additionalData.avatarUrl`. When
391
+ `SECURITY_OAUTH2_FETCH_AVATAR_ENABLED=true` (JSON: `security.oauth2.fetchAvatarEnabled`), the avatar image is also
392
+ downloaded during registration and passed as `additionalData.avatarFile`
391
393
  (`{ name, mimeType, size, data: Buffer }`), so it can be mapped to a model field or stored via the file service. The
392
394
  download is best-effort: failures are logged and registration proceeds without the file.
393
395
 
@@ -716,8 +718,8 @@ Password validation is configurable via `SECURITY_PASSWORD_*` properties:
716
718
 
717
719
  ### Token invalidation on password change
718
720
 
719
- Both `change-password` and `reset-password` set `logoutAt` to the current timestamp, which invalidates all existing
720
- JWT tokens across all devices. The `change-password` endpoint returns new tokens so the current session stays active.
721
+ Both `change-password` and `reset-password` set `logoutAt` to the current timestamp, which invalidates all existing JWT
722
+ tokens across all devices. The `change-password` endpoint returns new tokens so the current session stays active.
721
723
 
722
724
  ---
723
725
 
@@ -125,23 +125,42 @@ Available placeholders:
125
125
 
126
126
  Example: storing `photo.jpg` produces `photo-c7d3a1f2.jpg`.
127
127
 
128
+ Only the pattern itself may contain directory separators (e.g. `photos/{resourceId}/{name}.{extension}`). Every value
129
+ substituted into it — including the uploaded file name — is reduced to a single path segment, so an upload can never
130
+ introduce a subdirectory, a `..` traversal or a hidden file.
131
+
132
+ ---
133
+
134
+ ## Reserved paths
135
+
136
+ `STORAGE_RESERVED_PATHS` (default `['keys']`, protecting the default JWT key location) lists paths inside
137
+ `STORAGE_PATH` that files can never use. An entry is a directory (blocking everything inside it) or a full file name,
138
+ relative to the storage root, and whole segments are matched case-insensitively.
139
+
140
+ Configured `namePattern`s are validated on startup and abort the start; file paths resolved at upload, stream, or delete
141
+ time are rejected with a `400` error.
142
+
128
143
  ---
129
144
 
130
145
  ## Configuration
131
146
 
132
- | Key | Type | Default | Description |
133
- |------------------------------|----------|------------------------------------------------|-------------------------------------------------------|
134
- | `STORAGE_PATH` | `string` | `'./storage'` | Root directory where files are stored |
135
- | `STORAGE_NAME_PATTERN` | `string` | `'{name}-{hash}.{extension}'` | Template for deriving the stored file name |
136
- | `STORAGE_CACHE_TTL` | `int` | `86400000` | Cache TTL for storage responses in milliseconds (24h) |
137
- | `STORAGE_FILES_ROUTE_PREFIX` | `string` | `/files` | URL prefix for file access routes. |
138
- | `STORAGE_PROVIDER` | `string` | `'@appweaver/core/storage/filesystem-storage'` | Path to the Storage implementation |
147
+ | Key | Type | Default | Description |
148
+ |------------------------------|------------|------------------------------------------------|-------------------------------------------------------|
149
+ | `STORAGE_PATH` | `string` | `'./storage'` | Root directory where files are stored |
150
+ | `STORAGE_RESERVED_PATHS` | `string[]` | `['keys']` | Paths inside the storage root that files cannot use |
151
+ | `STORAGE_NAME_PATTERN` | `string` | `'{name}-{hash}.{extension}'` | Template for deriving the stored file name |
152
+ | `STORAGE_CACHE_TTL` | `int` | `86400000` | Cache TTL for storage responses in milliseconds (24h) |
153
+ | `STORAGE_FILES_ROUTE_PREFIX` | `string` | `/files` | URL prefix for file access routes. |
154
+ | `STORAGE_PROVIDER` | `string` | `'@appweaver/core/storage/filesystem-storage'` | Path to the Storage implementation |
139
155
 
140
156
  **`appweaver.json` example:**
141
157
 
142
158
  ```json
143
159
  {
144
160
  "STORAGE_PATH": "./uploads",
161
+ "STORAGE_RESERVED_PATHS": [
162
+ "keys"
163
+ ],
145
164
  "STORAGE_NAME_PATTERN": "{name}-{hash}.{extension}"
146
165
  }
147
166
  ```
@@ -230,8 +249,8 @@ processing), so it can be used at any later point to verify that the file on dis
230
249
 
231
250
  The checksum is included in file API responses, so clients can verify downloaded content against it.
232
251
 
233
- To verify a file's integrity, recalculate the checksum with the `makeHash` utility from `@appweaver/common` and
234
- compare it with the stored value:
252
+ To verify a file's integrity, recalculate the checksum with the `makeHash` utility from `@appweaver/common` and compare
253
+ it with the stored value:
235
254
 
236
255
  ```ts
237
256
  import { createReadStream } from 'node:fs';
@@ -2,8 +2,7 @@
2
2
  "$schema": "./node_modules/@appweaver/common/config/schema.json",
3
3
  "config": {
4
4
  "log": {
5
- "level": "error",
6
- "pretty": true
5
+ "level": "silent"
7
6
  },
8
7
  "database": {
9
8
  "url": "{{DATABASE_TEST_URL}}",
@@ -2,7 +2,7 @@
2
2
  "moduleFileExtensions": ["js", "json", "ts"],
3
3
  "rootDir": ".",
4
4
  "testEnvironment": "node",
5
- "testMatch": ["**/?(*.)+(spec|test).ts"],
5
+ "testMatch": ["**/*.test.ts"],
6
6
  "testPathIgnorePatterns": ["/test/e2e/"],
7
7
  "transform": {
8
8
  "^.+\\.(t|j)s$": ["@swc/jest"]
@@ -2,7 +2,7 @@
2
2
  "moduleFileExtensions": ["js", "json", "ts"],
3
3
  "rootDir": "../../",
4
4
  "testEnvironment": "node",
5
- "testMatch": ["**/test/e2e/?(*.)+(spec|test).ts"],
5
+ "testMatch": ["**/test/e2e/**/*.test.ts"],
6
6
  "transform": {
7
7
  "^.+\\.(t|j)s$": ["@swc/jest"]
8
8
  },
@@ -4,7 +4,7 @@
4
4
  "node_modules",
5
5
  "test",
6
6
  "dist",
7
- "**/*(spec|test).ts",
7
+ "**/*.test.ts",
8
8
  "prisma.config.ts"
9
9
  ]
10
10
  }