@insureco/cli 0.1.9 → 0.1.11
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/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +6 -52
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/git.d.ts.map +1 -1
- package/dist/commands/git.js +22 -8
- package/dist/commands/git.js.map +1 -1
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +94 -4
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +10 -1
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/rollback.d.ts.map +1 -1
- package/dist/commands/rollback.js +10 -35
- package/dist/commands/rollback.js.map +1 -1
- package/dist/commands/sample.d.ts +2 -0
- package/dist/commands/sample.d.ts.map +1 -1
- package/dist/commands/sample.js +140 -12
- package/dist/commands/sample.js.map +1 -1
- package/dist/commands/versions.d.ts.map +1 -1
- package/dist/commands/versions.js +10 -56
- package/dist/commands/versions.js.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/builder.d.ts.map +1 -1
- package/dist/lib/builder.js +46 -3
- package/dist/lib/builder.js.map +1 -1
- package/dist/lib/forgejo.d.ts +19 -2
- package/dist/lib/forgejo.d.ts.map +1 -1
- package/dist/lib/forgejo.js +143 -8
- package/dist/lib/forgejo.js.map +1 -1
- package/dist/lib/watch.d.ts +26 -0
- package/dist/lib/watch.d.ts.map +1 -0
- package/dist/lib/watch.js +136 -0
- package/dist/lib/watch.js.map +1 -0
- package/dist/templates/nextjs-oauth/.env.example +14 -0
- package/dist/templates/nextjs-oauth/Dockerfile +51 -0
- package/dist/templates/nextjs-oauth/README.md +128 -0
- package/dist/templates/nextjs-oauth/catalog-info.yaml +17 -0
- package/dist/templates/nextjs-oauth/helm/{{name}}/Chart.yaml +9 -0
- package/dist/templates/nextjs-oauth/helm/{{name}}/templates/deployment.yaml +68 -0
- package/dist/templates/nextjs-oauth/helm/{{name}}/templates/service.yaml +17 -0
- package/dist/templates/nextjs-oauth/helm/{{name}}/values.yaml +51 -0
- package/dist/templates/nextjs-oauth/next.config.js +23 -0
- package/dist/templates/nextjs-oauth/package.json +30 -0
- package/dist/templates/nextjs-oauth/public/.gitkeep +0 -0
- package/dist/templates/nextjs-oauth/src/app/api/auth/callback/route.ts +64 -0
- package/dist/templates/nextjs-oauth/src/app/api/auth/login/route.ts +16 -0
- package/dist/templates/nextjs-oauth/src/app/api/auth/logout/route.ts +9 -0
- package/dist/templates/nextjs-oauth/src/app/api/auth/session/route.ts +40 -0
- package/dist/templates/nextjs-oauth/src/app/api/example/route.ts +63 -0
- package/dist/templates/nextjs-oauth/src/app/api/health/route.ts +10 -0
- package/dist/templates/nextjs-oauth/src/app/dashboard/page.tsx +92 -0
- package/dist/templates/nextjs-oauth/src/app/layout.tsx +18 -0
- package/dist/templates/nextjs-oauth/src/app/page.tsx +110 -0
- package/dist/templates/nextjs-oauth/src/lib/auth.ts +274 -0
- package/dist/templates/nextjs-oauth/src/middleware.ts +70 -0
- package/dist/templates/nextjs-oauth/tsconfig.json +26 -0
- package/package.json +1 -1
package/dist/lib/forgejo.js
CHANGED
|
@@ -1,10 +1,64 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';
|
|
4
|
+
import { randomBytes, createHash } from 'node:crypto';
|
|
4
5
|
const FORGEJO_URL = 'https://git.insureco.io';
|
|
5
6
|
const FORGEJO_ORG = 'insureco';
|
|
7
|
+
const FORGEJO_OAUTH_CLIENT_ID = '01b2211c-35f9-46a1-b71e-4890bf821055';
|
|
6
8
|
const CONFIG_DIR = join(homedir(), '.iec');
|
|
7
9
|
const CREDENTIALS_FILE = join(CONFIG_DIR, 'forgejo-credentials.json');
|
|
10
|
+
// PKCE utilities for OAuth2 public clients
|
|
11
|
+
export function generateCodeVerifier() {
|
|
12
|
+
return randomBytes(32).toString('base64url');
|
|
13
|
+
}
|
|
14
|
+
export function generateCodeChallenge(verifier) {
|
|
15
|
+
return createHash('sha256').update(verifier).digest('base64url');
|
|
16
|
+
}
|
|
17
|
+
export function generateState() {
|
|
18
|
+
return randomBytes(16).toString('hex');
|
|
19
|
+
}
|
|
20
|
+
export function buildForgejoAuthUrl(codeChallenge, redirectUri, state) {
|
|
21
|
+
const params = new URLSearchParams({
|
|
22
|
+
client_id: FORGEJO_OAUTH_CLIENT_ID,
|
|
23
|
+
redirect_uri: redirectUri,
|
|
24
|
+
response_type: 'code',
|
|
25
|
+
code_challenge: codeChallenge,
|
|
26
|
+
code_challenge_method: 'S256',
|
|
27
|
+
state,
|
|
28
|
+
});
|
|
29
|
+
return `${FORGEJO_URL}/login/oauth/authorize?${params.toString()}`;
|
|
30
|
+
}
|
|
31
|
+
export async function exchangeCodeForToken(code, codeVerifier, redirectUri) {
|
|
32
|
+
const response = await fetch(`${FORGEJO_URL}/login/oauth/access_token`, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
36
|
+
'Accept': 'application/json',
|
|
37
|
+
},
|
|
38
|
+
body: new URLSearchParams({
|
|
39
|
+
grant_type: 'authorization_code',
|
|
40
|
+
client_id: FORGEJO_OAUTH_CLIENT_ID,
|
|
41
|
+
code,
|
|
42
|
+
redirect_uri: redirectUri,
|
|
43
|
+
code_verifier: codeVerifier,
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
const text = await response.text();
|
|
48
|
+
throw new Error(`Token exchange failed: ${text}`);
|
|
49
|
+
}
|
|
50
|
+
const data = (await response.json());
|
|
51
|
+
if (!data.access_token) {
|
|
52
|
+
throw new Error('No access token in response');
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
url: FORGEJO_URL,
|
|
56
|
+
token: data.access_token,
|
|
57
|
+
tokenType: 'oauth2',
|
|
58
|
+
refreshToken: data.refresh_token,
|
|
59
|
+
expiresAt: new Date(Date.now() + data.expires_in * 1000).toISOString(),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
8
62
|
function loadCredentials() {
|
|
9
63
|
if (!existsSync(CREDENTIALS_FILE)) {
|
|
10
64
|
return null;
|
|
@@ -26,33 +80,93 @@ function saveCredentials(credentials) {
|
|
|
26
80
|
export class ForgejoClient {
|
|
27
81
|
baseUrl;
|
|
28
82
|
token;
|
|
29
|
-
|
|
83
|
+
tokenType;
|
|
84
|
+
refreshToken;
|
|
85
|
+
expiresAt;
|
|
86
|
+
constructor(url, token, tokenType = 'pat', refreshToken, expiresAt) {
|
|
30
87
|
this.baseUrl = url.replace(/\/$/, '');
|
|
31
88
|
this.token = token;
|
|
89
|
+
this.tokenType = tokenType;
|
|
90
|
+
this.refreshToken = refreshToken;
|
|
91
|
+
this.expiresAt = expiresAt;
|
|
92
|
+
}
|
|
93
|
+
isExpired() {
|
|
94
|
+
if (!this.expiresAt)
|
|
95
|
+
return false;
|
|
96
|
+
return new Date(this.expiresAt).getTime() < Date.now() - 60_000;
|
|
97
|
+
}
|
|
98
|
+
async tryRefresh() {
|
|
99
|
+
if (this.tokenType !== 'oauth2' || !this.refreshToken) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const response = await fetch(`${this.baseUrl}/login/oauth/access_token`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: {
|
|
106
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
107
|
+
'Accept': 'application/json',
|
|
108
|
+
},
|
|
109
|
+
body: new URLSearchParams({
|
|
110
|
+
grant_type: 'refresh_token',
|
|
111
|
+
client_id: FORGEJO_OAUTH_CLIENT_ID,
|
|
112
|
+
refresh_token: this.refreshToken,
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
if (!response.ok)
|
|
116
|
+
return false;
|
|
117
|
+
const data = (await response.json());
|
|
118
|
+
if (!data.access_token)
|
|
119
|
+
return false;
|
|
120
|
+
const newToken = data.access_token;
|
|
121
|
+
const newRefreshToken = data.refresh_token;
|
|
122
|
+
const newExpiresAt = new Date(Date.now() + data.expires_in * 1000).toISOString();
|
|
123
|
+
// Persist to disk first — if this throws, in-memory state stays unchanged
|
|
124
|
+
saveCredentials({
|
|
125
|
+
url: this.baseUrl,
|
|
126
|
+
token: newToken,
|
|
127
|
+
tokenType: 'oauth2',
|
|
128
|
+
refreshToken: newRefreshToken,
|
|
129
|
+
expiresAt: newExpiresAt,
|
|
130
|
+
});
|
|
131
|
+
this.token = newToken;
|
|
132
|
+
this.refreshToken = newRefreshToken;
|
|
133
|
+
this.expiresAt = newExpiresAt;
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
32
139
|
}
|
|
33
140
|
static async create() {
|
|
34
141
|
const creds = loadCredentials();
|
|
35
142
|
if (!creds) {
|
|
36
|
-
throw new Error('Not authenticated with
|
|
143
|
+
throw new Error('Not authenticated with git.insureco.io. Run: iec login');
|
|
37
144
|
}
|
|
38
|
-
return new ForgejoClient(creds.url, creds.token);
|
|
145
|
+
return new ForgejoClient(creds.url, creds.token, creds.tokenType || 'pat', creds.refreshToken, creds.expiresAt);
|
|
39
146
|
}
|
|
40
|
-
static saveCredentials(url, token) {
|
|
41
|
-
saveCredentials({ url, token });
|
|
147
|
+
static saveCredentials(url, token, tokenType = 'pat', refreshToken, expiresAt) {
|
|
148
|
+
saveCredentials({ url, token, tokenType, refreshToken, expiresAt });
|
|
42
149
|
}
|
|
43
150
|
static hasCredentials() {
|
|
44
151
|
return loadCredentials() !== null;
|
|
45
152
|
}
|
|
46
153
|
static clearCredentials() {
|
|
47
154
|
if (existsSync(CREDENTIALS_FILE)) {
|
|
48
|
-
const { unlinkSync } = require('node:fs');
|
|
49
155
|
unlinkSync(CREDENTIALS_FILE);
|
|
50
156
|
}
|
|
51
157
|
}
|
|
52
158
|
async request(method, path, body) {
|
|
159
|
+
// Auto-refresh expired OAuth2 tokens before making the request
|
|
160
|
+
let didRefresh = false;
|
|
161
|
+
if (this.tokenType === 'oauth2' && this.isExpired()) {
|
|
162
|
+
didRefresh = await this.tryRefresh();
|
|
163
|
+
}
|
|
164
|
+
const authHeader = this.tokenType === 'oauth2'
|
|
165
|
+
? `Bearer ${this.token}`
|
|
166
|
+
: `token ${this.token}`;
|
|
53
167
|
const url = `${this.baseUrl}/api/v1${path}`;
|
|
54
168
|
const headers = {
|
|
55
|
-
'Authorization':
|
|
169
|
+
'Authorization': authHeader,
|
|
56
170
|
'Accept': 'application/json',
|
|
57
171
|
};
|
|
58
172
|
if (body) {
|
|
@@ -64,6 +178,27 @@ export class ForgejoClient {
|
|
|
64
178
|
headers,
|
|
65
179
|
body: body ? JSON.stringify(body) : undefined,
|
|
66
180
|
});
|
|
181
|
+
// If 401 with OAuth2 and we haven't already refreshed, try once
|
|
182
|
+
if (response.status === 401 && this.tokenType === 'oauth2' && !didRefresh) {
|
|
183
|
+
const refreshed = await this.tryRefresh();
|
|
184
|
+
if (refreshed) {
|
|
185
|
+
const retryHeaders = {
|
|
186
|
+
...headers,
|
|
187
|
+
'Authorization': `Bearer ${this.token}`,
|
|
188
|
+
};
|
|
189
|
+
const retryResponse = await fetch(url, {
|
|
190
|
+
method,
|
|
191
|
+
headers: retryHeaders,
|
|
192
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
193
|
+
});
|
|
194
|
+
if (retryResponse.ok) {
|
|
195
|
+
if (retryResponse.status === 204)
|
|
196
|
+
return { success: true };
|
|
197
|
+
const data = (await retryResponse.json());
|
|
198
|
+
return { success: true, data };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
67
202
|
if (!response.ok) {
|
|
68
203
|
const text = await response.text();
|
|
69
204
|
let errorMessage = `HTTP ${response.status}`;
|
package/dist/lib/forgejo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forgejo.js","sourceRoot":"","sources":["../../src/lib/forgejo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"forgejo.js","sourceRoot":"","sources":["../../src/lib/forgejo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BuilderClient } from './builder.js';
|
|
2
|
+
interface WatchOptions {
|
|
3
|
+
/** Max consecutive transient errors before giving up (default: 10) */
|
|
4
|
+
maxRetries?: number;
|
|
5
|
+
/** Base poll interval in ms (default: 3000) */
|
|
6
|
+
pollInterval?: number;
|
|
7
|
+
/** Max poll interval in ms during backoff (default: 15000) */
|
|
8
|
+
maxPollInterval?: number;
|
|
9
|
+
/** Max total watch duration in ms (default: 30 minutes) */
|
|
10
|
+
timeout?: number;
|
|
11
|
+
/** Stream build logs incrementally (default: true) */
|
|
12
|
+
streamLogs?: boolean;
|
|
13
|
+
/** Label for terminal output — e.g. "Build", "Deployment", "Rollback" */
|
|
14
|
+
label?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare class WatchError extends Error {
|
|
17
|
+
readonly buildId: string;
|
|
18
|
+
constructor(message: string, buildId: string);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Watch a build's progress with retry logic, backoff, and incremental log streaming.
|
|
22
|
+
* Throws WatchError on timeout, max retries, or build failure.
|
|
23
|
+
*/
|
|
24
|
+
export declare function watchBuild(builder: BuilderClient, buildId: string, options?: WatchOptions): Promise<void>;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/lib/watch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAmBjD,UAAU,YAAY;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sDAAsD;IACtD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,qBAAa,UAAW,SAAQ,KAAK;aACU,OAAO,EAAE,MAAM;gBAAhD,OAAO,EAAE,MAAM,EAAkB,OAAO,EAAE,MAAM;CAI7D;AAMD;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAgGf"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { log } from './output.js';
|
|
2
|
+
const TERMINAL_STATES = ['completed', 'failed', 'cancelled'];
|
|
3
|
+
const STATUS_ICONS = {
|
|
4
|
+
queued: '\u23f3', // hourglass
|
|
5
|
+
cloning: '\ud83d\udce5', // inbox
|
|
6
|
+
building: '\ud83d\udd28', // hammer
|
|
7
|
+
pushing: '\ud83d\udce4', // outbox
|
|
8
|
+
deploying: '\ud83d\ude80', // rocket
|
|
9
|
+
completed: '\u2705', // check
|
|
10
|
+
failed: '\u274c', // x
|
|
11
|
+
cancelled: '\u26d4', // no entry
|
|
12
|
+
};
|
|
13
|
+
export class WatchError extends Error {
|
|
14
|
+
buildId;
|
|
15
|
+
constructor(message, buildId) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.buildId = buildId;
|
|
18
|
+
this.name = 'WatchError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function isTransientError(code) {
|
|
22
|
+
return code === 'NON_JSON_RESPONSE' || code === 'HTTP_ERROR';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Watch a build's progress with retry logic, backoff, and incremental log streaming.
|
|
26
|
+
* Throws WatchError on timeout, max retries, or build failure.
|
|
27
|
+
*/
|
|
28
|
+
export async function watchBuild(builder, buildId, options = {}) {
|
|
29
|
+
const { maxRetries = 10, pollInterval = 3000, maxPollInterval = 15000, timeout = 30 * 60 * 1000, streamLogs = true, label = 'Build', } = options;
|
|
30
|
+
let lastStatus = '';
|
|
31
|
+
let consecutiveErrors = 0;
|
|
32
|
+
let currentInterval = pollInterval;
|
|
33
|
+
let logLinesSeen = 0;
|
|
34
|
+
const startTime = Date.now();
|
|
35
|
+
while (true) {
|
|
36
|
+
// Timeout guard
|
|
37
|
+
if (Date.now() - startTime > timeout) {
|
|
38
|
+
log.error(`${label} watch timed out after ${Math.round(timeout / 60000)} minutes`);
|
|
39
|
+
log.dim(` The build may still be running. Check status with:`);
|
|
40
|
+
log.dim(` iec status --build ${buildId}`);
|
|
41
|
+
throw new WatchError('Watch timed out', buildId);
|
|
42
|
+
}
|
|
43
|
+
const result = await builder.getBuild(buildId);
|
|
44
|
+
if (!result.success || !result.data) {
|
|
45
|
+
consecutiveErrors++;
|
|
46
|
+
const errMsg = result.error?.message || 'Unknown error';
|
|
47
|
+
if (consecutiveErrors >= maxRetries) {
|
|
48
|
+
log.error(`${label} watch failed after ${maxRetries} consecutive errors`);
|
|
49
|
+
log.dim(` Last error: ${errMsg}`);
|
|
50
|
+
log.dim(` The build may still be running. Check status with:`);
|
|
51
|
+
log.dim(` iec status --build ${buildId}`);
|
|
52
|
+
throw new WatchError(`Failed after ${maxRetries} consecutive errors: ${errMsg}`, buildId);
|
|
53
|
+
}
|
|
54
|
+
if (isTransientError(result.error?.code)) {
|
|
55
|
+
// Backoff on transient errors (Cloudflare HTML pages, 502s, etc.)
|
|
56
|
+
currentInterval = Math.min(currentInterval * 1.5, maxPollInterval);
|
|
57
|
+
log.warning(`${label} status unavailable (${consecutiveErrors}/${maxRetries}), retrying...`);
|
|
58
|
+
log.dim(` ${errMsg}`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
throw new WatchError(`Failed to get build status: ${errMsg}`, buildId);
|
|
62
|
+
}
|
|
63
|
+
await delay(currentInterval);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Successful response — reset error state and interval
|
|
67
|
+
consecutiveErrors = 0;
|
|
68
|
+
currentInterval = pollInterval;
|
|
69
|
+
const build = result.data;
|
|
70
|
+
// Print status transition
|
|
71
|
+
if (build.status !== lastStatus) {
|
|
72
|
+
lastStatus = build.status;
|
|
73
|
+
const icon = STATUS_ICONS[build.status] || '\u2022';
|
|
74
|
+
log.info(`${icon} ${build.status}`);
|
|
75
|
+
}
|
|
76
|
+
// Stream incremental logs
|
|
77
|
+
if (streamLogs) {
|
|
78
|
+
logLinesSeen = await printNewLogs(builder, buildId, logLinesSeen);
|
|
79
|
+
}
|
|
80
|
+
// Check for terminal state
|
|
81
|
+
if (TERMINAL_STATES.includes(build.status)) {
|
|
82
|
+
log.info('');
|
|
83
|
+
if (build.status === 'completed') {
|
|
84
|
+
log.success(`${label} completed successfully!`);
|
|
85
|
+
if (build.imageTag) {
|
|
86
|
+
log.dim(` Image: ${build.imageTag}`);
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
else if (build.status === 'cancelled') {
|
|
91
|
+
log.warning(`${label} was cancelled`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
log.error(`${label} failed`);
|
|
96
|
+
if (build.error) {
|
|
97
|
+
log.dim(` Error: ${build.error}`);
|
|
98
|
+
}
|
|
99
|
+
log.info('');
|
|
100
|
+
log.info('View full logs:');
|
|
101
|
+
log.dim(` iec logs --build ${buildId}`);
|
|
102
|
+
throw new WatchError(`${label} failed: ${build.error || 'Unknown error'}`, buildId);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
await delay(currentInterval);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function printNewLogs(builder, buildId, linesSeen) {
|
|
109
|
+
try {
|
|
110
|
+
const result = await builder.getBuildLogs(buildId);
|
|
111
|
+
if (!result.success || !result.data) {
|
|
112
|
+
return linesSeen;
|
|
113
|
+
}
|
|
114
|
+
const logs = Array.isArray(result.data.logs)
|
|
115
|
+
? result.data.logs
|
|
116
|
+
: typeof result.data.logs === 'string'
|
|
117
|
+
? result.data.logs.split('\n')
|
|
118
|
+
: [];
|
|
119
|
+
if (logs.length > linesSeen) {
|
|
120
|
+
const newLines = logs.slice(linesSeen);
|
|
121
|
+
for (const line of newLines) {
|
|
122
|
+
log.dim(` ${line}`);
|
|
123
|
+
}
|
|
124
|
+
return logs.length;
|
|
125
|
+
}
|
|
126
|
+
return linesSeen;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Log fetching is best-effort, don't interrupt the watch
|
|
130
|
+
return linesSeen;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function delay(ms) {
|
|
134
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=watch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/lib/watch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Next.js Configuration
|
|
2
|
+
NEXT_PUBLIC_APP_NAME={{name}}
|
|
3
|
+
|
|
4
|
+
# Bio-id OAuth2 Configuration
|
|
5
|
+
# Register an OAuth2 app at https://bio-id.tawa.insureco.io/admin/oauth
|
|
6
|
+
# Set redirect URI to: http://localhost:3000/api/auth/callback (dev)
|
|
7
|
+
BIO_ID_URL=https://bio-id.tawa.insureco.io
|
|
8
|
+
APP_URL=http://localhost:3000
|
|
9
|
+
OAUTH_CLIENT_ID=your-client-id
|
|
10
|
+
OAUTH_CLIENT_SECRET=your-client-secret
|
|
11
|
+
JWT_SECRET=your-jwt-secret
|
|
12
|
+
|
|
13
|
+
# API Configuration
|
|
14
|
+
# NEXT_PUBLIC_API_URL=https://api.example.com
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
FROM node:22-alpine AS base
|
|
2
|
+
|
|
3
|
+
# Install dependencies only when needed
|
|
4
|
+
FROM base AS deps
|
|
5
|
+
RUN apk add --no-cache libc6-compat
|
|
6
|
+
WORKDIR /app
|
|
7
|
+
|
|
8
|
+
COPY package*.json ./
|
|
9
|
+
RUN npm ci
|
|
10
|
+
|
|
11
|
+
# Build the application
|
|
12
|
+
FROM base AS builder
|
|
13
|
+
WORKDIR /app
|
|
14
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
15
|
+
COPY . .
|
|
16
|
+
|
|
17
|
+
ENV NEXT_TELEMETRY_DISABLED 1
|
|
18
|
+
|
|
19
|
+
RUN npm run build
|
|
20
|
+
|
|
21
|
+
# Production image
|
|
22
|
+
FROM base AS runner
|
|
23
|
+
WORKDIR /app
|
|
24
|
+
|
|
25
|
+
ENV NODE_ENV production
|
|
26
|
+
ENV NEXT_TELEMETRY_DISABLED 1
|
|
27
|
+
|
|
28
|
+
RUN addgroup --system --gid 1001 nodejs
|
|
29
|
+
RUN adduser --system --uid 1001 nextjs
|
|
30
|
+
|
|
31
|
+
COPY --from=builder /app/public ./public
|
|
32
|
+
|
|
33
|
+
# Set correct permissions for prerender cache
|
|
34
|
+
RUN mkdir .next
|
|
35
|
+
RUN chown nextjs:nodejs .next
|
|
36
|
+
|
|
37
|
+
# Automatically leverage output traces to reduce image size
|
|
38
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
39
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
40
|
+
|
|
41
|
+
USER nextjs
|
|
42
|
+
|
|
43
|
+
EXPOSE 3000
|
|
44
|
+
|
|
45
|
+
ENV PORT 3000
|
|
46
|
+
ENV HOSTNAME "0.0.0.0"
|
|
47
|
+
|
|
48
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
49
|
+
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
|
|
50
|
+
|
|
51
|
+
CMD ["node", "server.js"]
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
{{description}}
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
### Prerequisites
|
|
8
|
+
|
|
9
|
+
- Node.js 20+
|
|
10
|
+
- npm or pnpm
|
|
11
|
+
- A Bio-id OAuth2 application (see Setup below)
|
|
12
|
+
|
|
13
|
+
### OAuth Setup
|
|
14
|
+
|
|
15
|
+
1. Register an OAuth2 app at `https://bio-id.tawa.insureco.io/admin/oauth`
|
|
16
|
+
- Name: `{{name}}`
|
|
17
|
+
- Redirect URI: `http://localhost:3000/api/auth/callback`
|
|
18
|
+
- Copy the **Client ID** and **Client Secret**
|
|
19
|
+
|
|
20
|
+
2. Create `.env` from the example:
|
|
21
|
+
```bash
|
|
22
|
+
cp .env.example .env
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
3. Fill in the OAuth values in `.env`:
|
|
26
|
+
```
|
|
27
|
+
OAUTH_CLIENT_ID=your-client-id
|
|
28
|
+
OAUTH_CLIENT_SECRET=your-client-secret
|
|
29
|
+
JWT_SECRET=your-jwt-secret
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Development
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm run dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
45
|
+
|
|
46
|
+
### Production Build
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm run build
|
|
50
|
+
npm start
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Authentication Flow
|
|
54
|
+
|
|
55
|
+
This app uses Bio-id OAuth2 with PKCE for authentication:
|
|
56
|
+
|
|
57
|
+
1. User clicks **Sign In** on the home page
|
|
58
|
+
2. Redirected to Bio-id authorization page
|
|
59
|
+
3. After consent, redirected back to `/api/auth/callback`
|
|
60
|
+
4. Tokens stored in httpOnly cookies
|
|
61
|
+
5. Protected routes check cookies via middleware + server components
|
|
62
|
+
|
|
63
|
+
### Auth Routes
|
|
64
|
+
|
|
65
|
+
| Route | Method | Description |
|
|
66
|
+
|-------|--------|-------------|
|
|
67
|
+
| `/api/auth/login` | GET | Initiates OAuth flow (redirects to Bio-id) |
|
|
68
|
+
| `/api/auth/callback` | GET | Handles OAuth callback, sets cookies |
|
|
69
|
+
| `/api/auth/logout` | GET/POST | Clears auth cookies |
|
|
70
|
+
| `/api/auth/session` | GET | Returns current user as JSON |
|
|
71
|
+
|
|
72
|
+
### Route Protection
|
|
73
|
+
|
|
74
|
+
- **Middleware** (`src/middleware.ts`): Redirects unauthenticated users to login for `/dashboard` routes
|
|
75
|
+
- **Server components**: Use `getCurrentUser()` from `@/lib/auth` for server-side auth checks
|
|
76
|
+
|
|
77
|
+
## Project Structure
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
{{name}}/
|
|
81
|
+
├── src/
|
|
82
|
+
│ ├── lib/
|
|
83
|
+
│ │ └── auth.ts # OAuth2 library (PKCE, tokens, cookies)
|
|
84
|
+
│ ├── middleware.ts # Route protection middleware
|
|
85
|
+
│ └── app/
|
|
86
|
+
│ ├── layout.tsx # Root layout
|
|
87
|
+
│ ├── page.tsx # Home page (login/logout UI)
|
|
88
|
+
│ ├── dashboard/
|
|
89
|
+
│ │ └── page.tsx # Protected dashboard
|
|
90
|
+
│ └── api/
|
|
91
|
+
│ ├── health/ # Health check endpoint
|
|
92
|
+
│ │ └── route.ts
|
|
93
|
+
│ ├── example/ # Example API route
|
|
94
|
+
│ │ └── route.ts
|
|
95
|
+
│ └── auth/ # OAuth2 routes
|
|
96
|
+
│ ├── login/route.ts
|
|
97
|
+
│ ├── callback/route.ts
|
|
98
|
+
│ ├── logout/route.ts
|
|
99
|
+
│ └── session/route.ts
|
|
100
|
+
├── public/
|
|
101
|
+
├── helm/
|
|
102
|
+
│ └── {{name}}/ # Kubernetes Helm chart
|
|
103
|
+
├── next.config.js
|
|
104
|
+
├── Dockerfile
|
|
105
|
+
└── catalog-info.yaml # Backstage service catalog
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Deployment
|
|
109
|
+
|
|
110
|
+
### Using iec-cli
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Link repository for auto-deploy
|
|
114
|
+
iec link
|
|
115
|
+
|
|
116
|
+
# Manual deploy to sandbox
|
|
117
|
+
iec deploy
|
|
118
|
+
|
|
119
|
+
# Deploy to production
|
|
120
|
+
iec deploy --prod
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
When deploying, set the OAuth environment variables in your Helm values or via `iec env set`.
|
|
124
|
+
|
|
125
|
+
## Learn More
|
|
126
|
+
|
|
127
|
+
- [Next.js Documentation](https://nextjs.org/docs)
|
|
128
|
+
- [Tawa Platform Docs](https://docs.tawa.insureco.io)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
apiVersion: backstage.io/v1alpha1
|
|
2
|
+
kind: Component
|
|
3
|
+
metadata:
|
|
4
|
+
name: {{name}}
|
|
5
|
+
description: {{description}}
|
|
6
|
+
tags:
|
|
7
|
+
- tawa
|
|
8
|
+
- frontend
|
|
9
|
+
- nextjs
|
|
10
|
+
annotations:
|
|
11
|
+
insureco.io/framework: nextjs
|
|
12
|
+
insureco.io/language: typescript
|
|
13
|
+
insureco.io/auth: bio-id
|
|
14
|
+
spec:
|
|
15
|
+
type: service
|
|
16
|
+
lifecycle: experimental
|
|
17
|
+
owner: team-platform
|