@harperfast/template-vue-ts-studio 1.6.3 → 1.6.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/.agents/skills/harper-best-practices/AGENTS.md +1425 -300
- package/.agents/skills/harper-best-practices/SKILL.md +24 -20
- package/.agents/skills/harper-best-practices/rules/adding-tables-with-schemas.md +2 -0
- package/.agents/skills/harper-best-practices/rules/automatic-apis.md +141 -18
- package/.agents/skills/harper-best-practices/rules/caching.md +134 -21
- package/.agents/skills/harper-best-practices/rules/checking-authentication.md +139 -148
- package/.agents/skills/harper-best-practices/rules/creating-a-fabric-account-and-cluster.md +2 -0
- package/.agents/skills/harper-best-practices/rules/creating-harper-apps.md +2 -0
- package/.agents/skills/harper-best-practices/rules/custom-resources.md +2 -0
- package/.agents/skills/harper-best-practices/rules/defining-relationships.md +2 -0
- package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +97 -77
- package/.agents/skills/harper-best-practices/rules/extending-tables.md +2 -0
- package/.agents/skills/harper-best-practices/rules/handling-binary-data.md +2 -0
- package/.agents/skills/harper-best-practices/rules/logging.md +154 -77
- package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +32 -26
- package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +190 -15
- package/.agents/skills/harper-best-practices/rules/real-time-apps.md +80 -21
- package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +2 -0
- package/.agents/skills/harper-best-practices/rules/serving-web-content.md +2 -0
- package/.agents/skills/harper-best-practices/rules/typescript-type-stripping.md +2 -0
- package/.agents/skills/harper-best-practices/rules/using-blob-datatype.md +2 -0
- package/.agents/skills/harper-best-practices/rules/vector-indexing.md +85 -120
- package/.agents/skills/harper-best-practices/rules.manifest.yaml +258 -0
- package/package.json +1 -1
- package/skills-lock.json +1 -1
|
@@ -1,199 +1,190 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: checking-authentication
|
|
3
3
|
description: How to handle user authentication and sessions in Harper Resources.
|
|
4
|
+
metadata:
|
|
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
|
|
4
14
|
---
|
|
5
15
|
|
|
6
16
|
# Checking Authentication
|
|
7
17
|
|
|
8
|
-
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.
|
|
9
19
|
|
|
10
20
|
## When to Use
|
|
11
21
|
|
|
12
|
-
|
|
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.
|
|
13
23
|
|
|
14
24
|
## How It Works
|
|
15
25
|
|
|
16
|
-
1. **
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
2. **Implement Sign In**: Use `this.getContext().login(username, password)` to create a session:
|
|
23
|
-
```typescript
|
|
24
|
-
async post(_target, data) {
|
|
25
|
-
const context = this.getContext();
|
|
26
|
-
try {
|
|
27
|
-
await context.login(data.username, data.password);
|
|
28
|
-
} catch {
|
|
29
|
-
return new Response('Invalid credentials', { status: 403 });
|
|
30
|
-
}
|
|
31
|
-
return new Response('Logged in', { status: 200 });
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
3. **Identify Current User**: Use `this.getCurrentUser()` to access session data:
|
|
35
|
-
```typescript
|
|
36
|
-
async get() {
|
|
37
|
-
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();
|
|
38
31
|
if (!user) return new Response(null, { status: 401 });
|
|
39
32
|
return { username: user.username, role: user.role };
|
|
40
33
|
}
|
|
41
34
|
```
|
|
42
|
-
4. **Implement Sign Out**: Use `this.getContext().logout()` or delete the session from context:
|
|
43
|
-
```typescript
|
|
44
|
-
async post() {
|
|
45
|
-
const context = this.getContext();
|
|
46
|
-
await context.session?.delete?.(context.session.id);
|
|
47
|
-
return new Response('Logged out', { status: 200 });
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
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`.
|
|
51
|
-
|
|
52
|
-
## Examples
|
|
53
|
-
|
|
54
|
-
### Sign In Implementation
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
async post(_target, data) {
|
|
58
|
-
const context = this.getContext();
|
|
59
|
-
try {
|
|
60
|
-
await context.login(data.username, data.password);
|
|
61
|
-
} catch {
|
|
62
|
-
return new Response('Invalid credentials', { status: 403 });
|
|
63
|
-
}
|
|
64
|
-
return new Response('Logged in', { status: 200 });
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
35
|
|
|
68
|
-
|
|
36
|
+
The returned object exposes `username`, `role`, and `role.permission` flags.
|
|
69
37
|
|
|
70
|
-
|
|
71
|
-
async get() {
|
|
72
|
-
const user = this.getCurrentUser?.();
|
|
73
|
-
if (!user) return new Response(null, { status: 401 });
|
|
74
|
-
return { username: user.username, role: user.role };
|
|
75
|
-
}
|
|
76
|
-
```
|
|
38
|
+
2. **Enable sessions** before using session-based login. Set `authentication.enableSessions: true` in `harperdb-config.yaml`:
|
|
77
39
|
|
|
78
|
-
|
|
40
|
+
```yaml
|
|
41
|
+
authentication:
|
|
42
|
+
enableSessions: true
|
|
43
|
+
```
|
|
79
44
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
```
|
|
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
|
+
}
|
|
87
63
|
|
|
88
|
-
|
|
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
|
+
```
|
|
89
73
|
|
|
90
|
-
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
}
|
|
94
102
|
|
|
95
|
-
|
|
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
|
+
```
|
|
96
117
|
|
|
97
|
-
|
|
98
|
-
- 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.
|
|
99
119
|
|
|
100
|
-
|
|
120
|
+
6. **Configure JWT token expiry** in `harperdb-config.yaml` under the `authentication` section:
|
|
101
121
|
|
|
102
|
-
|
|
122
|
+
```yaml
|
|
123
|
+
authentication:
|
|
124
|
+
operationTokenTimeout: 1d
|
|
125
|
+
refreshTokenTimeout: 30d
|
|
126
|
+
```
|
|
103
127
|
|
|
104
|
-
|
|
105
|
-
- **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`).
|
|
106
129
|
|
|
107
|
-
|
|
130
|
+
## Examples
|
|
108
131
|
|
|
109
|
-
|
|
132
|
+
**Protecting a resource endpoint and returning user info:**
|
|
110
133
|
|
|
111
|
-
|
|
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
|
+
```
|
|
112
141
|
|
|
113
|
-
-
|
|
114
|
-
- from an explicit `{ username, password }` payload (useful for direct “login” from a CLI/mobile client).
|
|
142
|
+
**Full session-based sign-in/sign-out flow:**
|
|
115
143
|
|
|
116
144
|
```javascript
|
|
117
|
-
export class
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
this.getContext(),
|
|
125
|
-
);
|
|
126
|
-
return { refreshToken, jwt };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async post(target, data) {
|
|
130
|
-
if (!data.username || !data.password) {
|
|
131
|
-
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 });
|
|
132
152
|
}
|
|
153
|
+
return new Response('Logged in', { status: 200 });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
133
156
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
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 });
|
|
141
163
|
}
|
|
142
164
|
}
|
|
143
165
|
```
|
|
144
166
|
|
|
145
|
-
**
|
|
146
|
-
|
|
147
|
-
- `GET` variant: intended for “I already have an Authorization token, give me new tokens”.
|
|
148
|
-
- `POST` variant: intended for “I have credentials, give me tokens”.
|
|
149
|
-
- Response shape:
|
|
150
|
-
- `refreshToken`: store securely (long-lived).
|
|
151
|
-
- `jwt`: attach to requests (short-lived).
|
|
152
|
-
|
|
153
|
-
### Refreshing a JWT: `RefreshJWT`
|
|
154
|
-
|
|
155
|
-
**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:**
|
|
156
168
|
|
|
157
169
|
```javascript
|
|
158
170
|
export class RefreshJWT extends Resource {
|
|
159
|
-
static
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
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 });
|
|
164
175
|
}
|
|
165
|
-
|
|
166
|
-
const { operation_token: jwt } = await databases.system.hdb_user.operation({
|
|
176
|
+
const { operation_token } = await server.operation({
|
|
167
177
|
operation: 'refresh_operation_token',
|
|
168
|
-
refresh_token
|
|
178
|
+
refresh_token,
|
|
169
179
|
});
|
|
170
|
-
return {
|
|
180
|
+
return { operation_token };
|
|
171
181
|
}
|
|
172
182
|
}
|
|
173
183
|
```
|
|
174
184
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
- Requires `refreshToken` in the request body.
|
|
178
|
-
- Returns a new `{ jwt }`.
|
|
179
|
-
- If refresh fails (expired/revoked), client must re-authenticate (e.g., call `IssueTokens.post` again).
|
|
180
|
-
|
|
181
|
-
### Suggested client flow (high-level)
|
|
182
|
-
|
|
183
|
-
1. **Sign in (token flow)**
|
|
184
|
-
- POST /IssueTokens/ with a body of `{ "username": "your username", "password": "your password" }` or GET /IssueTokens/ with an existing Authorization token.
|
|
185
|
-
- Receive `{ jwt, refreshToken }` in the response
|
|
186
|
-
2. **Call protected APIs**
|
|
187
|
-
- Send the JWT with each request in the Authorization header (as your auth mechanism expects)
|
|
188
|
-
3. **JWT expires**
|
|
189
|
-
- POST /RefreshJWT/ with a body of `{ "refreshToken": "your refresh token" }`.
|
|
190
|
-
- Receive `{ jwt }` in the response and continue
|
|
191
|
-
|
|
192
|
-
## Quick checklist
|
|
185
|
+
## Notes
|
|
193
186
|
|
|
194
|
-
-
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
-
-
|
|
198
|
-
- [ ] `authentication.authorizeLocal` is `false` and `enableSessions` is `true` in Harper config.
|
|
199
|
-
- [ ] 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.
|
|
@@ -1,102 +1,122 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: deploying-to-harper-fabric
|
|
3
3
|
description: How to deploy a Harper application to the Harper Fabric cloud.
|
|
4
|
+
metadata:
|
|
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
|
|
4
13
|
---
|
|
5
14
|
|
|
6
15
|
# Deploying to Harper Fabric
|
|
7
16
|
|
|
8
|
-
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.
|
|
9
18
|
|
|
10
19
|
## When to Use
|
|
11
20
|
|
|
12
|
-
|
|
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.
|
|
13
22
|
|
|
14
23
|
## How It Works
|
|
15
24
|
|
|
16
|
-
1. **
|
|
17
|
-
|
|
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
|
+
|
|
18
62
|
```bash
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
22
71
|
```
|
|
23
|
-
3. **Deploy From Local Environment**: Run `npm run deploy`.
|
|
24
|
-
4. **Set up CI/CD**: Configure `.github/workflows/deploy.yaml` and set repository secrets for automated deployments.
|
|
25
72
|
|
|
26
|
-
|
|
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
|
|
27
76
|
|
|
28
|
-
|
|
77
|
+
**Interactive login then deploy (recommended):**
|
|
29
78
|
|
|
30
|
-
|
|
79
|
+
```bash
|
|
80
|
+
# Log in once
|
|
81
|
+
harper login <remote>
|
|
82
|
+
# Provide your username and password when prompted
|
|
31
83
|
|
|
32
|
-
|
|
84
|
+
# Subsequently deploy without credentials
|
|
85
|
+
harper deploy \
|
|
86
|
+
project=<name> \
|
|
87
|
+
package=<package> \
|
|
88
|
+
target=<remote> \
|
|
89
|
+
restart=true \
|
|
90
|
+
replicated=true
|
|
91
|
+
```
|
|
33
92
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
45
104
|
```
|
|
46
105
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
### 2. Configure GitHub Actions
|
|
57
|
-
|
|
58
|
-
Create a `.github/workflows/deploy.yaml` file with the following content:
|
|
59
|
-
|
|
60
|
-
```yaml
|
|
61
|
-
name: Deploy to Harper Fabric
|
|
62
|
-
on:
|
|
63
|
-
workflow_dispatch:
|
|
64
|
-
# push:
|
|
65
|
-
# branches:
|
|
66
|
-
# - main
|
|
67
|
-
concurrency:
|
|
68
|
-
group: main
|
|
69
|
-
cancel-in-progress: false
|
|
70
|
-
jobs:
|
|
71
|
-
deploy:
|
|
72
|
-
runs-on: ubuntu-latest
|
|
73
|
-
steps:
|
|
74
|
-
- name: Checkout code
|
|
75
|
-
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
76
|
-
with:
|
|
77
|
-
fetch-depth: 0
|
|
78
|
-
fetch-tags: true
|
|
79
|
-
- name: Set up Node.js
|
|
80
|
-
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
|
81
|
-
with:
|
|
82
|
-
cache: 'npm'
|
|
83
|
-
node-version-file: '.nvmrc'
|
|
84
|
-
- name: Install dependencies
|
|
85
|
-
run: npm ci
|
|
86
|
-
- name: Run unit tests
|
|
87
|
-
run: npm test
|
|
88
|
-
- name: Run lint
|
|
89
|
-
run: npm run lint
|
|
90
|
-
- name: Deploy
|
|
91
|
-
run: npm run deploy
|
|
92
|
-
env:
|
|
93
|
-
CLI_TARGET: ${{ secrets.CLI_TARGET }}
|
|
94
|
-
CLI_TARGET_USERNAME: ${{ secrets.CLI_TARGET_USERNAME }}
|
|
95
|
-
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
|
|
96
115
|
```
|
|
97
116
|
|
|
98
|
-
|
|
117
|
+
## Notes
|
|
99
118
|
|
|
100
|
-
- `
|
|
101
|
-
- `
|
|
102
|
-
- `
|
|
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.
|