@harperfast/skills 1.6.1 → 1.8.0
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/dist/index.d.ts +1 -0
- package/dist/index.js +14 -12
- package/harper-best-practices/AGENTS.md +1127 -422
- package/harper-best-practices/SKILL.md +25 -20
- package/harper-best-practices/rules/automatic-apis.md +140 -19
- package/harper-best-practices/rules/caching.md +133 -22
- package/harper-best-practices/rules/checking-authentication.md +138 -149
- package/harper-best-practices/rules/creating-harper-apps.md +5 -2
- package/harper-best-practices/rules/deploying-to-harper-fabric.md +96 -78
- package/harper-best-practices/rules/load-env.md +100 -0
- package/harper-best-practices/rules/logging.md +153 -78
- package/harper-best-practices/rules/querying-rest-apis.md +189 -16
- package/harper-best-practices/rules/real-time-apps.md +79 -22
- package/harper-best-practices/rules/schema-design-tooling.md +132 -41
- package/harper-best-practices/rules/typescript-type-stripping.md +47 -17
- package/harper-best-practices/rules/vector-indexing.md +12 -12
- package/harper-best-practices/rules.manifest.yaml +135 -13
- package/package.json +1 -1
|
@@ -2,200 +2,189 @@
|
|
|
2
2
|
name: checking-authentication
|
|
3
3
|
description: How to handle user authentication and sessions in Harper Resources.
|
|
4
4
|
metadata:
|
|
5
|
-
mode:
|
|
5
|
+
mode: generate
|
|
6
|
+
sources:
|
|
7
|
+
- >-
|
|
8
|
+
reference/v5/resources/resource-api.md#`getCurrentUser(): User |
|
|
9
|
+
undefined`
|
|
10
|
+
- reference/v5/resources/resource-api.md#Session and Login from a Resource
|
|
11
|
+
- reference/v5/security/jwt-authentication.md
|
|
12
|
+
sourceCommit: b7fbddadd42eb4487190b650a9abc4bcfeef5819
|
|
13
|
+
inputHash: fdd9ec3b11011490
|
|
6
14
|
---
|
|
7
15
|
|
|
8
16
|
# Checking Authentication
|
|
9
17
|
|
|
10
|
-
Instructions for the agent to follow when handling authentication and
|
|
18
|
+
Instructions for the agent to follow when handling user authentication and session management inside Harper Resources.
|
|
11
19
|
|
|
12
20
|
## When to Use
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
Apply this rule when implementing authentication checks, login/logout flows, or token issuance inside a custom Resource. Use it any time a Resource needs to identify the current user, establish a session, or issue JWTs to clients. See [custom-resources.md](custom-resources.md) for the general Resource authoring pattern.
|
|
15
23
|
|
|
16
24
|
## How It Works
|
|
17
25
|
|
|
18
|
-
1. **
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
2. **Implement Sign In**: Use `this.getContext().login(username, password)` to create a session:
|
|
25
|
-
```typescript
|
|
26
|
-
async post(_target, data) {
|
|
27
|
-
const context = this.getContext();
|
|
28
|
-
try {
|
|
29
|
-
await context.login(data.username, data.password);
|
|
30
|
-
} catch {
|
|
31
|
-
return new Response('Invalid credentials', { status: 403 });
|
|
32
|
-
}
|
|
33
|
-
return new Response('Logged in', { status: 200 });
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
3. **Identify Current User**: Use `this.getCurrentUser()` to access session data:
|
|
37
|
-
```typescript
|
|
38
|
-
async get() {
|
|
39
|
-
const user = this.getCurrentUser?.();
|
|
26
|
+
1. **Check the current user** with `getCurrentUser()`. Call it inside any Resource method to retrieve the authenticated user or `undefined` if no user is authenticated. Guard protected endpoints by returning a `401` when the result is `undefined`.
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
async get(target) {
|
|
30
|
+
const user = this.getCurrentUser();
|
|
40
31
|
if (!user) return new Response(null, { status: 401 });
|
|
41
32
|
return { username: user.username, role: user.role };
|
|
42
33
|
}
|
|
43
34
|
```
|
|
44
|
-
4. **Implement Sign Out**: Use `this.getContext().logout()` or delete the session from context:
|
|
45
|
-
```typescript
|
|
46
|
-
async post() {
|
|
47
|
-
const context = this.getContext();
|
|
48
|
-
await context.session?.delete?.(context.session.id);
|
|
49
|
-
return new Response('Logged out', { status: 200 });
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
5. **Protect Routes**: In your Resource, use `allowRead()`, `allowUpdate()`, etc., to enforce authorization logic based on `this.getCurrentUser()`. For privileged actions, verify `user.role.permission.super_user`.
|
|
53
|
-
|
|
54
|
-
## Examples
|
|
55
|
-
|
|
56
|
-
### Sign In Implementation
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
async post(_target, data) {
|
|
60
|
-
const context = this.getContext();
|
|
61
|
-
try {
|
|
62
|
-
await context.login(data.username, data.password);
|
|
63
|
-
} catch {
|
|
64
|
-
return new Response('Invalid credentials', { status: 403 });
|
|
65
|
-
}
|
|
66
|
-
return new Response('Logged in', { status: 200 });
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
35
|
|
|
70
|
-
|
|
36
|
+
The returned object exposes `username`, `role`, and `role.permission` flags.
|
|
71
37
|
|
|
72
|
-
|
|
73
|
-
async get() {
|
|
74
|
-
const user = this.getCurrentUser?.();
|
|
75
|
-
if (!user) return new Response(null, { status: 401 });
|
|
76
|
-
return { username: user.username, role: user.role };
|
|
77
|
-
}
|
|
78
|
-
```
|
|
38
|
+
2. **Enable sessions** before using session-based login. Set `authentication.enableSessions: true` in `harperdb-config.yaml`:
|
|
79
39
|
|
|
80
|
-
|
|
40
|
+
```yaml
|
|
41
|
+
authentication:
|
|
42
|
+
enableSessions: true
|
|
43
|
+
```
|
|
81
44
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
```
|
|
45
|
+
3. **Access login and session helpers** via `getContext()`. The context object exposes `context.login` and `context.session` for sign-in/out flows.
|
|
46
|
+
- Call `context.login(username, password)` to verify credentials and establish a session cookie on success.
|
|
47
|
+
- To end a session, delete it via `context.session.delete(context.session.id)`.
|
|
48
|
+
|
|
49
|
+
4. **Implement sign-in and sign-out Resources** using the context helpers:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
export class SignIn extends Resource {
|
|
53
|
+
async post(_target, data) {
|
|
54
|
+
const context = this.getContext();
|
|
55
|
+
try {
|
|
56
|
+
await context.login(data.username, data.password);
|
|
57
|
+
} catch {
|
|
58
|
+
return new Response('Invalid credentials', { status: 403 });
|
|
59
|
+
}
|
|
60
|
+
return new Response('Logged in', { status: 200 });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
89
63
|
|
|
90
|
-
|
|
64
|
+
export class SignOut extends Resource {
|
|
65
|
+
async post() {
|
|
66
|
+
const context = this.getContext();
|
|
67
|
+
if (!context.session) return new Response(null, { status: 401 });
|
|
68
|
+
await context.session.delete(context.session.id);
|
|
69
|
+
return new Response('Logged out', { status: 200 });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
91
73
|
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
74
|
+
5. **Issue JWTs for non-browser clients** (CLI tools, mobile apps, service-to-service). Cookie-based sessions are intended for browser clients. For other clients, mint tokens programmatically using `server.operation()`:
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
import { Resource, server } from 'harper';
|
|
78
|
+
|
|
79
|
+
export class IssueTokens extends Resource {
|
|
80
|
+
static async get(_target, context) {
|
|
81
|
+
const { operation_token, refresh_token } = await server.operation(
|
|
82
|
+
{ operation: 'create_authentication_tokens' },
|
|
83
|
+
context,
|
|
84
|
+
true,
|
|
85
|
+
);
|
|
86
|
+
return { operation_token, refresh_token };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
static async post(_target, data) {
|
|
90
|
+
const { username, password } = await data;
|
|
91
|
+
if (!username || !password) {
|
|
92
|
+
return new Response('username and password required', { status: 400 });
|
|
93
|
+
}
|
|
94
|
+
const { operation_token, refresh_token } = await server.operation({
|
|
95
|
+
operation: 'create_authentication_tokens',
|
|
96
|
+
username,
|
|
97
|
+
password,
|
|
98
|
+
});
|
|
99
|
+
return { operation_token, refresh_token };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
96
102
|
|
|
97
|
-
|
|
103
|
+
export class RefreshJWT extends Resource {
|
|
104
|
+
static async post(_target, data) {
|
|
105
|
+
const { refresh_token } = await data;
|
|
106
|
+
if (!refresh_token) {
|
|
107
|
+
return new Response('refresh_token required', { status: 400 });
|
|
108
|
+
}
|
|
109
|
+
const { operation_token } = await server.operation({
|
|
110
|
+
operation: 'refresh_operation_token',
|
|
111
|
+
refresh_token,
|
|
112
|
+
});
|
|
113
|
+
return { operation_token };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
98
117
|
|
|
99
|
-
|
|
100
|
-
- If developing locally, double-check the server config still has `authentication.authorizeLocal: false` to avoid accidental superuser bypass.
|
|
118
|
+
Pass `true` as the third argument to `server.operation()` when the operation should run as the current authenticated user. Omit it or pass `false` when the operation supplies its own credentials.
|
|
101
119
|
|
|
102
|
-
|
|
120
|
+
6. **Configure JWT token expiry** in `harperdb-config.yaml` under the `authentication` section:
|
|
103
121
|
|
|
104
|
-
|
|
122
|
+
```yaml
|
|
123
|
+
authentication:
|
|
124
|
+
operationTokenTimeout: 1d
|
|
125
|
+
refreshTokenTimeout: 30d
|
|
126
|
+
```
|
|
105
127
|
|
|
106
|
-
|
|
107
|
-
- **Refresh token (`refresh_token`)**: longer-lived token used to mint a new JWT when it expires.
|
|
128
|
+
Duration strings follow the `jsonwebtoken` package format (e.g., `1d`, `12h`, `60m`).
|
|
108
129
|
|
|
109
|
-
|
|
130
|
+
## Examples
|
|
110
131
|
|
|
111
|
-
|
|
132
|
+
**Protecting a resource endpoint and returning user info:**
|
|
112
133
|
|
|
113
|
-
|
|
134
|
+
```javascript
|
|
135
|
+
async get(target) {
|
|
136
|
+
const user = this.getCurrentUser();
|
|
137
|
+
if (!user) return new Response(null, { status: 401 });
|
|
138
|
+
return { username: user.username, role: user.role };
|
|
139
|
+
}
|
|
140
|
+
```
|
|
114
141
|
|
|
115
|
-
-
|
|
116
|
-
- from an explicit `{ username, password }` payload (useful for direct “login” from a CLI/mobile client).
|
|
142
|
+
**Full session-based sign-in/sign-out flow:**
|
|
117
143
|
|
|
118
144
|
```javascript
|
|
119
|
-
export class
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.getContext(),
|
|
127
|
-
);
|
|
128
|
-
return { refreshToken, jwt };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async post(target, data) {
|
|
132
|
-
if (!data.username || !data.password) {
|
|
133
|
-
throw new Error('username and password are required');
|
|
145
|
+
export class SignIn extends Resource {
|
|
146
|
+
async post(_target, data) {
|
|
147
|
+
const context = this.getContext();
|
|
148
|
+
try {
|
|
149
|
+
await context.login(data.username, data.password);
|
|
150
|
+
} catch {
|
|
151
|
+
return new Response('Invalid credentials', { status: 403 });
|
|
134
152
|
}
|
|
153
|
+
return new Response('Logged in', { status: 200 });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
135
156
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return { refreshToken, jwt };
|
|
157
|
+
export class SignOut extends Resource {
|
|
158
|
+
async post() {
|
|
159
|
+
const context = this.getContext();
|
|
160
|
+
if (!context.session) return new Response(null, { status: 401 });
|
|
161
|
+
await context.session.delete(context.session.id);
|
|
162
|
+
return new Response('Logged out', { status: 200 });
|
|
143
163
|
}
|
|
144
164
|
}
|
|
145
165
|
```
|
|
146
166
|
|
|
147
|
-
**
|
|
148
|
-
|
|
149
|
-
- `GET` variant: intended for “I already have an Authorization token, give me new tokens”.
|
|
150
|
-
- `POST` variant: intended for “I have credentials, give me tokens”.
|
|
151
|
-
- Response shape:
|
|
152
|
-
- `refreshToken`: store securely (long-lived).
|
|
153
|
-
- `jwt`: attach to requests (short-lived).
|
|
154
|
-
|
|
155
|
-
### Refreshing a JWT: `RefreshJWT`
|
|
156
|
-
|
|
157
|
-
**Description / use case:** When the JWT expires, the client uses the refresh token to get a new JWT without re-supplying username/password.
|
|
167
|
+
**JWT token refresh endpoint:**
|
|
158
168
|
|
|
159
169
|
```javascript
|
|
160
170
|
export class RefreshJWT extends Resource {
|
|
161
|
-
static
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
throw new Error('refreshToken is required');
|
|
171
|
+
static async post(_target, data) {
|
|
172
|
+
const { refresh_token } = await data;
|
|
173
|
+
if (!refresh_token) {
|
|
174
|
+
return new Response('refresh_token required', { status: 400 });
|
|
166
175
|
}
|
|
167
|
-
|
|
168
|
-
const { operation_token: jwt } = await databases.system.hdb_user.operation({
|
|
176
|
+
const { operation_token } = await server.operation({
|
|
169
177
|
operation: 'refresh_operation_token',
|
|
170
|
-
refresh_token
|
|
178
|
+
refresh_token,
|
|
171
179
|
});
|
|
172
|
-
return {
|
|
180
|
+
return { operation_token };
|
|
173
181
|
}
|
|
174
182
|
}
|
|
175
183
|
```
|
|
176
184
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
- Requires `refreshToken` in the request body.
|
|
180
|
-
- Returns a new `{ jwt }`.
|
|
181
|
-
- If refresh fails (expired/revoked), client must re-authenticate (e.g., call `IssueTokens.post` again).
|
|
182
|
-
|
|
183
|
-
### Suggested client flow (high-level)
|
|
184
|
-
|
|
185
|
-
1. **Sign in (token flow)**
|
|
186
|
-
- POST /IssueTokens/ with a body of `{ "username": "your username", "password": "your password" }` or GET /IssueTokens/ with an existing Authorization token.
|
|
187
|
-
- Receive `{ jwt, refreshToken }` in the response
|
|
188
|
-
2. **Call protected APIs**
|
|
189
|
-
- Send the JWT with each request in the Authorization header (as your auth mechanism expects)
|
|
190
|
-
3. **JWT expires**
|
|
191
|
-
- POST /RefreshJWT/ with a body of `{ "refreshToken": "your refresh token" }`.
|
|
192
|
-
- Receive `{ jwt }` in the response and continue
|
|
193
|
-
|
|
194
|
-
## Quick checklist
|
|
185
|
+
## Notes
|
|
195
186
|
|
|
196
|
-
-
|
|
197
|
-
-
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
- [ ] `authentication.authorizeLocal` is `false` and `enableSessions` is `true` in Harper config.
|
|
201
|
-
- [ ] If using tokens: `IssueTokens` issues `{ jwt, refreshToken }`, `RefreshJWT` refreshes `{ jwt }` with a `refreshToken`.
|
|
187
|
+
- `getCurrentUser()` and `getContext()` are instance methods; call them with `this` inside non-static Resource methods.
|
|
188
|
+
- `enableSessions` must be `true` in config before `context.login` or `context.session` will function.
|
|
189
|
+
- Cookie-based sessions target browser clients. Use JWT issuance via `server.operation()` for all other client types.
|
|
190
|
+
- When both `operation_token` and `refresh_token` have expired, the client must call `create_authentication_tokens` again with credentials.
|
|
@@ -7,11 +7,14 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Creating Harper Applications
|
|
9
9
|
|
|
10
|
-
The fastest way to start a new Harper project is using the `create-harper` CLI tool. This command
|
|
10
|
+
The fastest way to start a new Harper project is using the `create-harper` CLI tool. This command
|
|
11
|
+
initializes a project with a standard folder structure, essential configuration files, and basic
|
|
12
|
+
schema definitions.
|
|
11
13
|
|
|
12
14
|
## When to Use
|
|
13
15
|
|
|
14
|
-
Use this command when starting a new Harper application or adding a new Harper microservice to an
|
|
16
|
+
Use this command when starting a new Harper application or adding a new Harper microservice to an
|
|
17
|
+
existing architecture.
|
|
15
18
|
|
|
16
19
|
## Commands
|
|
17
20
|
|
|
@@ -2,103 +2,121 @@
|
|
|
2
2
|
name: deploying-to-harper-fabric
|
|
3
3
|
description: How to deploy a Harper application to the Harper Fabric cloud.
|
|
4
4
|
metadata:
|
|
5
|
-
mode:
|
|
5
|
+
mode: generate
|
|
6
|
+
sources:
|
|
7
|
+
- reference/v5/components/applications.md#Remote Management
|
|
8
|
+
- >-
|
|
9
|
+
fabric/cluster-creation-management.md#Connecting the Harper CLI to a
|
|
10
|
+
Cluster
|
|
11
|
+
sourceCommit: b7fbddadd42eb4487190b650a9abc4bcfeef5819
|
|
12
|
+
inputHash: ccdefbbf8f3b8657
|
|
6
13
|
---
|
|
7
14
|
|
|
8
15
|
# Deploying to Harper Fabric
|
|
9
16
|
|
|
10
|
-
Instructions for the agent to follow when deploying to Harper Fabric.
|
|
17
|
+
Instructions for the agent to follow when deploying a Harper application to the Harper Fabric cloud using the Harper CLI.
|
|
11
18
|
|
|
12
19
|
## When to Use
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
Apply this rule when deploying a Harper application to a remote Harper instance or Harper Fabric cluster. This covers interactive deployments, CI/CD pipelines, and any scenario where the agent must push a local or remote package to a target environment.
|
|
15
22
|
|
|
16
23
|
## How It Works
|
|
17
24
|
|
|
18
|
-
1. **
|
|
19
|
-
|
|
25
|
+
1. **Authenticate with the remote target**: Run `harper login` once to store an authentication token. The CLI writes `HARPER_CLI_TARGET` to a local `.env` so subsequent commands do not need credentials repeated. Find the **Application URL** on the cluster's **Config → Overview** page (see [creating-a-fabric-account-and-cluster.md](creating-a-fabric-account-and-cluster.md)).
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
harper login <Application URL>
|
|
29
|
+
# Provide cluster username and password when prompted
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
2. **Deploy the application**: Run `harper deploy` with the required parameters. After logging in, no credentials are needed inline.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
harper deploy \
|
|
36
|
+
project=<name> \
|
|
37
|
+
package=<package> \
|
|
38
|
+
target=<remote> \
|
|
39
|
+
restart=true \
|
|
40
|
+
replicated=true
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
3. **Choose a package source**: Set the `package` parameter to any valid npm dependency value, or omit it to package and deploy the current local directory.
|
|
44
|
+
|
|
45
|
+
| Value | Effect |
|
|
46
|
+
| ---------------------------------------------------- | ------------------------------------------------ |
|
|
47
|
+
| _(omitted)_ | Packages and deploys the current local directory |
|
|
48
|
+
| `"@harperdb/status-check"` | npm package |
|
|
49
|
+
| `"HarperDB/status-check"` | GitHub repo (short form) |
|
|
50
|
+
| `"https://github.com/HarperDB/status-check"` | GitHub repo (full URL) |
|
|
51
|
+
| `"git+ssh://git@github.com:HarperDB/secret-app.git"` | Private repo via SSH |
|
|
52
|
+
| `"https://example.com/application.tar.gz"` | Remote tarball |
|
|
53
|
+
|
|
54
|
+
For git tags, use the `semver` directive for reliable versioning:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
HarperDB/application-template#semver:v1.0.0
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
4. **Authenticate for CI/CD pipelines**: Use environment variables instead of interactive login. Set credentials before running `harper deploy`.
|
|
61
|
+
|
|
20
62
|
```bash
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
63
|
+
export HARPER_CLI_USERNAME=<username>
|
|
64
|
+
export HARPER_CLI_PASSWORD=<password>
|
|
65
|
+
harper deploy \
|
|
66
|
+
project=<name> \
|
|
67
|
+
package=<package> \
|
|
68
|
+
target=<remote> \
|
|
69
|
+
restart=true \
|
|
70
|
+
replicated=true
|
|
24
71
|
```
|
|
25
|
-
3. **Deploy From Local Environment**: Run `npm run deploy`.
|
|
26
|
-
4. **Set up CI/CD**: Configure `.github/workflows/deploy.yaml` and set repository secrets for automated deployments.
|
|
27
72
|
|
|
28
|
-
|
|
73
|
+
5. **Register SSH keys for private repos**: Before deploying from an SSH-based private repository, use the Add SSH Key operation to register the key with the remote instance.
|
|
74
|
+
|
|
75
|
+
## Examples
|
|
29
76
|
|
|
30
|
-
|
|
77
|
+
**Interactive login then deploy (recommended):**
|
|
31
78
|
|
|
32
|
-
|
|
79
|
+
```bash
|
|
80
|
+
# Log in once
|
|
81
|
+
harper login <remote>
|
|
82
|
+
# Provide your username and password when prompted
|
|
33
83
|
|
|
34
|
-
|
|
84
|
+
# Subsequently deploy without credentials
|
|
85
|
+
harper deploy \
|
|
86
|
+
project=<name> \
|
|
87
|
+
package=<package> \
|
|
88
|
+
target=<remote> \
|
|
89
|
+
restart=true \
|
|
90
|
+
replicated=true
|
|
91
|
+
```
|
|
35
92
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
93
|
+
**Deploy with inline credentials (not recommended for production):**
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
harper deploy \
|
|
97
|
+
project=<name> \
|
|
98
|
+
package=<package> \
|
|
99
|
+
username=<username> \
|
|
100
|
+
password=<password> \
|
|
101
|
+
target=<remote> \
|
|
102
|
+
restart=true \
|
|
103
|
+
replicated=true
|
|
47
104
|
```
|
|
48
105
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
### 2. Configure GitHub Actions
|
|
59
|
-
|
|
60
|
-
Create a `.github/workflows/deploy.yaml` file with the following content:
|
|
61
|
-
|
|
62
|
-
```yaml
|
|
63
|
-
name: Deploy to Harper Fabric
|
|
64
|
-
on:
|
|
65
|
-
workflow_dispatch:
|
|
66
|
-
# push:
|
|
67
|
-
# branches:
|
|
68
|
-
# - main
|
|
69
|
-
concurrency:
|
|
70
|
-
group: main
|
|
71
|
-
cancel-in-progress: false
|
|
72
|
-
jobs:
|
|
73
|
-
deploy:
|
|
74
|
-
runs-on: ubuntu-latest
|
|
75
|
-
steps:
|
|
76
|
-
- name: Checkout code
|
|
77
|
-
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
78
|
-
with:
|
|
79
|
-
fetch-depth: 0
|
|
80
|
-
fetch-tags: true
|
|
81
|
-
- name: Set up Node.js
|
|
82
|
-
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
|
83
|
-
with:
|
|
84
|
-
cache: 'npm'
|
|
85
|
-
node-version-file: '.nvmrc'
|
|
86
|
-
- name: Install dependencies
|
|
87
|
-
run: npm ci
|
|
88
|
-
- name: Run unit tests
|
|
89
|
-
run: npm test
|
|
90
|
-
- name: Run lint
|
|
91
|
-
run: npm run lint
|
|
92
|
-
- name: Deploy
|
|
93
|
-
run: npm run deploy
|
|
94
|
-
env:
|
|
95
|
-
CLI_TARGET: ${{ secrets.CLI_TARGET }}
|
|
96
|
-
CLI_TARGET_USERNAME: ${{ secrets.CLI_TARGET_USERNAME }}
|
|
97
|
-
CLI_TARGET_PASSWORD: ${{ secrets.CLI_TARGET_PASSWORD }}
|
|
106
|
+
**Deploy a specific GitHub release by semver tag:**
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
harper deploy \
|
|
110
|
+
project=my-app \
|
|
111
|
+
package="HarperDB/application-template#semver:v1.0.0" \
|
|
112
|
+
target=<remote> \
|
|
113
|
+
restart=true \
|
|
114
|
+
replicated=true
|
|
98
115
|
```
|
|
99
116
|
|
|
100
|
-
|
|
117
|
+
## Notes
|
|
101
118
|
|
|
102
|
-
- `
|
|
103
|
-
- `
|
|
104
|
-
- `
|
|
119
|
+
- Always prefer `harper login` for interactive use and environment variables (`HARPER_CLI_USERNAME`, `HARPER_CLI_PASSWORD`) for CI/CD. Avoid inline `username`/`password` parameters in production.
|
|
120
|
+
- Omitting `package` causes the CLI to package the current local directory. Specifying a local file path creates a symlink, so changes are picked up between restarts without redeploying.
|
|
121
|
+
- Harper generates a `package.json` from component configurations and resolves dependencies using a form of `npm install`.
|
|
122
|
+
- For SSH-based private repos, register keys with the Add SSH Key operation before deploying.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: load-env
|
|
3
|
+
description: >-
|
|
4
|
+
How to load environment variables from .env files into a Harper application
|
|
5
|
+
using the loadEnv plugin.
|
|
6
|
+
metadata:
|
|
7
|
+
mode: generate
|
|
8
|
+
sources:
|
|
9
|
+
- reference/v5/environment-variables/overview.md
|
|
10
|
+
sourceCommit: b7fbddadd42eb4487190b650a9abc4bcfeef5819
|
|
11
|
+
inputHash: c73a0caf28a2b833
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Load Environment Variables with loadEnv
|
|
15
|
+
|
|
16
|
+
Instructions for the agent to follow when loading environment variables from `.env` files into a Harper application using the `loadEnv` plugin.
|
|
17
|
+
|
|
18
|
+
## When to Use
|
|
19
|
+
|
|
20
|
+
Apply this rule when a Harper application needs to load secrets or configuration values from `.env` files into `process.env` at startup. Use it whenever you need to configure `loadEnv` in `config.yaml`, control load order, handle multiple files, or manage override behavior.
|
|
21
|
+
|
|
22
|
+
## How It Works
|
|
23
|
+
|
|
24
|
+
1. **Declare `loadEnv` in `config.yaml`**: Add `loadEnv` to your `config.yaml` with a `files` key pointing to the `.env` file. `loadEnv` is built into Harper and does not need to be installed separately.
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
loadEnv:
|
|
28
|
+
files: '.env'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This loads the specified file from the root of your component directory into `process.env`.
|
|
32
|
+
|
|
33
|
+
2. **Place `loadEnv` first**: Always declare `loadEnv` before any other components in `config.yaml` so environment variables are available before dependent components start. Because Harper is single-process, variables loaded onto `process.env` are shared across all components.
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
# config.yaml — loadEnv must come first
|
|
37
|
+
loadEnv:
|
|
38
|
+
files: '.env'
|
|
39
|
+
|
|
40
|
+
rest: true
|
|
41
|
+
|
|
42
|
+
myApp:
|
|
43
|
+
files: './src/*.js'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
3. **Control override behavior**: By default, existing shell or container environment variables take precedence over values in `.env` files. To force `.env` values to overwrite existing variables, set `override: true`.
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
loadEnv:
|
|
50
|
+
files: '.env'
|
|
51
|
+
override: true
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
4. **Load multiple files**: Provide a list of files or a glob pattern under `files`. Files are loaded in the order specified.
|
|
55
|
+
```yaml
|
|
56
|
+
loadEnv:
|
|
57
|
+
files:
|
|
58
|
+
- '.env'
|
|
59
|
+
- '.env.local'
|
|
60
|
+
```
|
|
61
|
+
Or using a glob pattern:
|
|
62
|
+
```yaml
|
|
63
|
+
loadEnv:
|
|
64
|
+
files: 'env-vars/*'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Examples
|
|
68
|
+
|
|
69
|
+
A complete `config.yaml` using `loadEnv` with multiple files and override enabled:
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
# config.yaml — loadEnv must come first
|
|
73
|
+
loadEnv:
|
|
74
|
+
files:
|
|
75
|
+
- '.env'
|
|
76
|
+
- '.env.local'
|
|
77
|
+
override: true
|
|
78
|
+
|
|
79
|
+
rest: true
|
|
80
|
+
|
|
81
|
+
myApp:
|
|
82
|
+
files: './src/*.js'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
A minimal setup loading a single `.env` file:
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
loadEnv:
|
|
89
|
+
files: '.env'
|
|
90
|
+
|
|
91
|
+
myApp:
|
|
92
|
+
files: './src/*.js'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Notes
|
|
96
|
+
|
|
97
|
+
- `loadEnv` is built into Harper — declare it in `config.yaml` only; do not install it as a separate package.
|
|
98
|
+
- The `files` value accepts either a single string, a list of strings, or a glob pattern.
|
|
99
|
+
- Without `override: true`, variables already present in the environment are never overwritten by values in `.env` files.
|
|
100
|
+
- `process.env` is shared across all Harper components in the same process, so load order in `config.yaml` determines availability.
|