@everysk/apps-devkit 0.2.0 → 0.3.0-beta-20261019175049
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/README.md +12 -2
- package/dist/eslint/index.d.ts +13 -4
- package/dist/eslint/index.d.ts.map +1 -1
- package/dist/eslint/index.js +53 -48
- package/dist/eslint/index.js.map +1 -1
- package/dist/vite/apiToken.d.ts +27 -0
- package/dist/vite/apiToken.d.ts.map +1 -0
- package/dist/vite/apiToken.js +86 -0
- package/dist/vite/apiToken.js.map +1 -0
- package/dist/vite/index.d.ts +4 -0
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +9 -1
- package/dist/vite/index.js.map +1 -1
- package/dist/vite/plugins/envVarsLocation.js.map +1 -1
- package/dist/vite/plugins/serveDevAppConfig.js.map +1 -1
- package/dist/vite/plugins/serveDevFiles.d.ts +41 -0
- package/dist/vite/plugins/serveDevFiles.d.ts.map +1 -0
- package/dist/vite/plugins/serveDevFiles.js +153 -0
- package/dist/vite/plugins/serveDevFiles.js.map +1 -0
- package/dist/vite/serverProxy.d.ts +7 -7
- package/dist/vite/serverProxy.d.ts.map +1 -1
- package/dist/vite/serverProxy.js +9 -56
- package/dist/vite/serverProxy.js.map +1 -1
- package/package.json +1 -3
- package/src/eslint/index.ts +23 -5
- package/src/vite/apiToken.ts +107 -0
- package/src/vite/index.ts +10 -1
- package/src/vite/plugins/envVarsLocation.ts +7 -7
- package/src/vite/plugins/serveDevAppConfig.ts +28 -28
- package/src/vite/plugins/serveDevFiles.ts +216 -0
- package/src/vite/serverProxy.ts +29 -77
- package/tsconfig/vitest.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serveDevFiles.js","sourceRoot":"","sources":["../../../src/vite/plugins/serveDevFiles.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { ProxyOptions } from "vite";
|
|
2
|
+
import { type ApiTokenSource } from "./apiToken.js";
|
|
2
3
|
/**
|
|
3
4
|
* createDevServerProxy — Vite dev proxy for `/api`, authenticated with OAuth2 client credentials.
|
|
4
5
|
*
|
|
5
6
|
* Forwards `/api/*` to `EVERYSK_API_URL` (stripping the `/api` prefix) and injects an
|
|
6
|
-
* `Authorization: Bearer <access_token>` header
|
|
7
|
-
* `${EVERYSK_API_URL}/oauth2/token` with the `client_credentials` grant, cached in memory, and
|
|
8
|
-
* refreshed in the background shortly before it expires. It is fetched eagerly at startup so a
|
|
9
|
-
* token is ready before the first API call.
|
|
7
|
+
* `Authorization: Bearer <access_token>` header, from `createApiTokenSource`.
|
|
10
8
|
*
|
|
9
|
+
* @param {ApiTokenSource} [tokens] - The token to send; a new source when omitted. The preset
|
|
10
|
+
* passes the one it shares with the `/file/` route.
|
|
11
11
|
* @returns {Record<string, string | ProxyOptions>} The Vite `server.proxy` config for `/api`.
|
|
12
|
-
* @throws {Error} If `EVERYSK_API_URL`, `EVERYSK_API_CLIENT_ID`, or
|
|
13
|
-
* is missing.
|
|
12
|
+
* @throws {Error} If `tokens` is omitted and `EVERYSK_API_URL`, `EVERYSK_API_CLIENT_ID`, or
|
|
13
|
+
* `EVERYSK_API_CLIENT_SECRET` is missing.
|
|
14
14
|
*/
|
|
15
|
-
export declare function createDevServerProxy(): Record<string, string | ProxyOptions>;
|
|
15
|
+
export declare function createDevServerProxy(tokens?: ApiTokenSource): Record<string, string | ProxyOptions>;
|
|
16
16
|
//# sourceMappingURL=serverProxy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverProxy.d.ts","sourceRoot":"","sources":["../../src/vite/serverProxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"serverProxy.d.ts","sourceRoot":"","sources":["../../src/vite/serverProxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEzC,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE1E;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAChC,MAAM,GAAE,cAAuC,GAChD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAAC,CAqBvC"}
|
package/dist/vite/serverProxy.js
CHANGED
|
@@ -1,68 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
// request never races a just-expired token.
|
|
3
|
-
const REFRESH_SKEW_MS = 60_000;
|
|
1
|
+
import { createApiTokenSource } from "./apiToken.js";
|
|
4
2
|
/**
|
|
5
3
|
* createDevServerProxy — Vite dev proxy for `/api`, authenticated with OAuth2 client credentials.
|
|
6
4
|
*
|
|
7
5
|
* Forwards `/api/*` to `EVERYSK_API_URL` (stripping the `/api` prefix) and injects an
|
|
8
|
-
* `Authorization: Bearer <access_token>` header
|
|
9
|
-
* `${EVERYSK_API_URL}/oauth2/token` with the `client_credentials` grant, cached in memory, and
|
|
10
|
-
* refreshed in the background shortly before it expires. It is fetched eagerly at startup so a
|
|
11
|
-
* token is ready before the first API call.
|
|
6
|
+
* `Authorization: Bearer <access_token>` header, from `createApiTokenSource`.
|
|
12
7
|
*
|
|
8
|
+
* @param {ApiTokenSource} [tokens] - The token to send; a new source when omitted. The preset
|
|
9
|
+
* passes the one it shares with the `/file/` route.
|
|
13
10
|
* @returns {Record<string, string | ProxyOptions>} The Vite `server.proxy` config for `/api`.
|
|
14
|
-
* @throws {Error} If `EVERYSK_API_URL`, `EVERYSK_API_CLIENT_ID`, or
|
|
15
|
-
* is missing.
|
|
11
|
+
* @throws {Error} If `tokens` is omitted and `EVERYSK_API_URL`, `EVERYSK_API_CLIENT_ID`, or
|
|
12
|
+
* `EVERYSK_API_CLIENT_SECRET` is missing.
|
|
16
13
|
*/
|
|
17
|
-
export function createDevServerProxy() {
|
|
18
|
-
const EVERYSK_API_URL = process.env.EVERYSK_API_URL;
|
|
19
|
-
if (!EVERYSK_API_URL)
|
|
20
|
-
throw new Error("EVERYSK_API_URL must be set.");
|
|
21
|
-
const clientId = process.env.EVERYSK_API_CLIENT_ID;
|
|
22
|
-
const clientSecret = process.env.EVERYSK_API_CLIENT_SECRET;
|
|
23
|
-
if (!clientId || !clientSecret) {
|
|
24
|
-
throw new Error("EVERYSK_API_CLIENT_ID and EVERYSK_API_CLIENT_SECRET must be set (OAuth2 client credentials).");
|
|
25
|
-
}
|
|
26
|
-
// Captured into an object so the narrowed `string` type survives inside the async closure below.
|
|
27
|
-
const credentials = { clientId, clientSecret };
|
|
28
|
-
const tokenUrl = `${EVERYSK_API_URL.replace(/\/$/, "")}/oauth2/token`;
|
|
29
|
-
let accessToken = null;
|
|
30
|
-
let refreshTimer = null;
|
|
31
|
-
/**
|
|
32
|
-
* refreshToken — fetch a fresh access token and schedule the next refresh before it expires.
|
|
33
|
-
*
|
|
34
|
-
* @returns {Promise<void>} Resolves once `accessToken` is updated.
|
|
35
|
-
* @throws {Error} If the token request fails or returns no `access_token`.
|
|
36
|
-
*/
|
|
37
|
-
async function refreshToken() {
|
|
38
|
-
const res = await fetch(tokenUrl, {
|
|
39
|
-
method: "POST",
|
|
40
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
41
|
-
body: new URLSearchParams({
|
|
42
|
-
grant_type: "client_credentials",
|
|
43
|
-
client_id: credentials.clientId,
|
|
44
|
-
client_secret: credentials.clientSecret,
|
|
45
|
-
}),
|
|
46
|
-
});
|
|
47
|
-
if (!res.ok)
|
|
48
|
-
throw new Error(`OAuth2 token request failed: ${res.status} ${res.statusText}`);
|
|
49
|
-
const data = (await res.json());
|
|
50
|
-
if (!data.access_token)
|
|
51
|
-
throw new Error("OAuth2 token response had no access_token.");
|
|
52
|
-
accessToken = data.access_token;
|
|
53
|
-
const ttlMs = (data.expires_in ?? 3600) * 1000;
|
|
54
|
-
if (refreshTimer)
|
|
55
|
-
clearTimeout(refreshTimer);
|
|
56
|
-
refreshTimer = setTimeout(() => {
|
|
57
|
-
void refreshToken().catch((error) => console.error("[dev proxy] OAuth2 token refresh failed:", error));
|
|
58
|
-
}, Math.max(ttlMs - REFRESH_SKEW_MS, 30_000));
|
|
59
|
-
refreshTimer.unref?.();
|
|
60
|
-
}
|
|
61
|
-
// Warm the token before the first request; a failure is logged, not fatal (the request will 401).
|
|
62
|
-
void refreshToken().catch((error) => console.error("[dev proxy] initial OAuth2 token fetch failed:", error));
|
|
14
|
+
export function createDevServerProxy(tokens = createApiTokenSource()) {
|
|
63
15
|
return {
|
|
64
16
|
"/api": {
|
|
65
|
-
target:
|
|
17
|
+
target: tokens.apiUrl,
|
|
66
18
|
changeOrigin: true,
|
|
67
19
|
xfwd: true,
|
|
68
20
|
rewrite: (p) => p.replace(/^\/api(?=\/|$)/, ""),
|
|
@@ -70,6 +22,7 @@ export function createDevServerProxy() {
|
|
|
70
22
|
proxy.on("proxyReq", (proxyReq) => {
|
|
71
23
|
proxyReq.removeHeader("origin");
|
|
72
24
|
proxyReq.removeHeader("referer");
|
|
25
|
+
const accessToken = tokens.token();
|
|
73
26
|
if (accessToken) {
|
|
74
27
|
proxyReq.setHeader("Authorization", `Bearer ${accessToken}`);
|
|
75
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverProxy.js","sourceRoot":"","sources":["../../src/vite/serverProxy.ts"],"names":[],"mappings":"AAEA,
|
|
1
|
+
{"version":3,"file":"serverProxy.js","sourceRoot":"","sources":["../../src/vite/serverProxy.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAuB,MAAM,eAAe,CAAC;AAE1E;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAChC,SAAyB,oBAAoB,EAAE;IAE/C,OAAO;QACH,MAAM,EAAE;YACJ,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,YAAY,EAAE,IAAI;YAClB,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;YAE/C,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;gBACjB,KAAK,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,EAAE;oBAC9B,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;oBAChC,QAAQ,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;oBAEjC,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;oBACnC,IAAI,WAAW,EAAE,CAAC;wBACd,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,WAAW,EAAE,CAAC,CAAC;oBACjE,CAAC;gBACL,CAAC,CAAC,CAAC;YACP,CAAC;SACJ;KACJ,CAAC;AACN,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everysk/apps-devkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-beta-20261019175049",
|
|
4
4
|
"description": "Build-time presets for Everysk client applications: Vite, ESLint, Vitest, TypeScript and Prettier.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"everysk",
|
|
@@ -62,8 +62,6 @@
|
|
|
62
62
|
"@vitest/coverage-v8": "^5.0.1",
|
|
63
63
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
64
64
|
"eslint": "^10.10.0",
|
|
65
|
-
"eslint-config-prettier": "^10.1.8",
|
|
66
|
-
"eslint-plugin-prettier": "^5.5.6",
|
|
67
65
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
68
66
|
"eslint-plugin-react-refresh": "^0.5.2",
|
|
69
67
|
"globals": "^17.6.0",
|
package/src/eslint/index.ts
CHANGED
|
@@ -22,6 +22,17 @@ const noBarrelPaths = [
|
|
|
22
22
|
const dataLayerMessage =
|
|
23
23
|
"Data access only via entity hooks (@everysk/apps-sdk/<entity> or @/hooks/...). Pages and components must not call axios or an api module directly.";
|
|
24
24
|
|
|
25
|
+
export type EveryskConfigOptions = {
|
|
26
|
+
/**
|
|
27
|
+
* The app's directory, `import.meta.dirname` in its `eslint.config.js`. An editor lints a
|
|
28
|
+
* repository of several apps in one ESLint process, where typescript-eslint refuses to pick
|
|
29
|
+
* among their tsconfigs; only the app's own config knows which directory is its root.
|
|
30
|
+
*/
|
|
31
|
+
tsconfigRootDir?: string;
|
|
32
|
+
/** Appended after the preset, so an app can add or override rules for its own files. */
|
|
33
|
+
extra?: ReturnType<typeof defineConfig>;
|
|
34
|
+
};
|
|
35
|
+
|
|
25
36
|
/**
|
|
26
37
|
* everyskConfig
|
|
27
38
|
*
|
|
@@ -29,18 +40,23 @@ const dataLayerMessage =
|
|
|
29
40
|
*
|
|
30
41
|
* ```js
|
|
31
42
|
* import { everyskConfig } from "@everysk/apps-devkit/eslint";
|
|
32
|
-
* export default everyskConfig();
|
|
43
|
+
* export default everyskConfig({ tsconfigRootDir: import.meta.dirname });
|
|
33
44
|
* ```
|
|
34
45
|
*
|
|
35
46
|
* It encodes the conventions that lint can actually check: no MUI barrel imports, and no
|
|
36
47
|
* direct data access from pages or components. The rules that lint cannot express — which
|
|
37
48
|
* subpath to import, which provider to wire — belong to the `everysk-apps` generator.
|
|
38
49
|
*
|
|
39
|
-
*
|
|
40
|
-
* add or override rules for its own files.
|
|
50
|
+
* Takes the options, or the app's own blocks alone, as it did before there were options.
|
|
41
51
|
*/
|
|
42
|
-
export const everyskConfig = (
|
|
43
|
-
defineConfig
|
|
52
|
+
export const everyskConfig = (
|
|
53
|
+
optionsOrExtra: EveryskConfigOptions | ReturnType<typeof defineConfig> = {},
|
|
54
|
+
) => {
|
|
55
|
+
const { tsconfigRootDir, extra = [] } = Array.isArray(optionsOrExtra)
|
|
56
|
+
? { extra: optionsOrExtra }
|
|
57
|
+
: optionsOrExtra;
|
|
58
|
+
|
|
59
|
+
return defineConfig([
|
|
44
60
|
globalIgnores(["dist", "coverage"]),
|
|
45
61
|
{
|
|
46
62
|
files: ["**/*.{ts,tsx}"],
|
|
@@ -53,6 +69,7 @@ export const everyskConfig = (extra: ReturnType<typeof defineConfig> = []) =>
|
|
|
53
69
|
languageOptions: {
|
|
54
70
|
ecmaVersion: 2020,
|
|
55
71
|
globals: globals.browser,
|
|
72
|
+
...(tsconfigRootDir ? { parserOptions: { tsconfigRootDir } } : {}),
|
|
56
73
|
},
|
|
57
74
|
rules: {
|
|
58
75
|
"@typescript-eslint/no-restricted-imports": ["error", { paths: noBarrelPaths }],
|
|
@@ -88,3 +105,4 @@ export const everyskConfig = (extra: ReturnType<typeof defineConfig> = []) =>
|
|
|
88
105
|
},
|
|
89
106
|
...extra,
|
|
90
107
|
]);
|
|
108
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** How long before a token expires it is renewed, so a request in flight never meets an expired one. */
|
|
2
|
+
const REFRESH_SKEW_MS = 60_000;
|
|
3
|
+
|
|
4
|
+
/** The first wait after a failed token request; it doubles on each failure, up to the maximum. */
|
|
5
|
+
const FIRST_RETRY_MS = 5_000;
|
|
6
|
+
const MAX_RETRY_MS = 60_000;
|
|
7
|
+
|
|
8
|
+
/** The API the dev server talks to, and the token it talks to it with. */
|
|
9
|
+
export type ApiTokenSource = {
|
|
10
|
+
/** `EVERYSK_API_URL`, without a trailing slash, so a path can be appended as is. */
|
|
11
|
+
apiUrl: string;
|
|
12
|
+
/** The current access token; `null` until one arrives, and once it has expired. */
|
|
13
|
+
token: () => string | null;
|
|
14
|
+
/** Why there is no token: the last failed request, until one succeeds. */
|
|
15
|
+
lastError: () => Error | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* createApiTokenSource
|
|
20
|
+
*
|
|
21
|
+
* An OAuth2 access token for `EVERYSK_API_URL`, from the `client_credentials` grant at
|
|
22
|
+
* `${EVERYSK_API_URL}/oauth2/token`, cached in memory and renewed in the background a minute
|
|
23
|
+
* before it expires. It is fetched at once, so a token is ready before the first request. A
|
|
24
|
+
* failed request is logged and tried again, waiting 5 seconds and doubling up to a minute, so a
|
|
25
|
+
* VPN or API that comes back later does not need a restart of the dev server; until then the
|
|
26
|
+
* `/api` proxy sends no token (the API answers 401) and the `/file/` route says why, from
|
|
27
|
+
* `lastError`. `defineAppConfig` creates one source and hands it to both, so the dev server asks
|
|
28
|
+
* for one token, not one per use.
|
|
29
|
+
*
|
|
30
|
+
* @returns {ApiTokenSource} The API URL, the current token, and the last failure.
|
|
31
|
+
* @throws {Error} If `EVERYSK_API_URL`, `EVERYSK_API_CLIENT_ID`, or `EVERYSK_API_CLIENT_SECRET`
|
|
32
|
+
* is missing.
|
|
33
|
+
*/
|
|
34
|
+
export function createApiTokenSource(): ApiTokenSource {
|
|
35
|
+
const apiUrl = process.env.EVERYSK_API_URL?.replace(/\/$/, "");
|
|
36
|
+
if (!apiUrl) throw new Error("EVERYSK_API_URL must be set.");
|
|
37
|
+
|
|
38
|
+
const clientId = process.env.EVERYSK_API_CLIENT_ID;
|
|
39
|
+
const clientSecret = process.env.EVERYSK_API_CLIENT_SECRET;
|
|
40
|
+
if (!clientId || !clientSecret) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"EVERYSK_API_CLIENT_ID and EVERYSK_API_CLIENT_SECRET must be set (OAuth2 client credentials).",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const credentials = { clientId, clientSecret };
|
|
46
|
+
|
|
47
|
+
const tokenUrl = `${apiUrl}/oauth2/token`;
|
|
48
|
+
let accessToken: string | null = null;
|
|
49
|
+
let expiresAt = 0;
|
|
50
|
+
let lastError: Error | null = null;
|
|
51
|
+
let retryMs = FIRST_RETRY_MS;
|
|
52
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
53
|
+
|
|
54
|
+
const schedule = (delayMs: number) => {
|
|
55
|
+
if (timer) clearTimeout(timer);
|
|
56
|
+
timer = setTimeout(() => void refreshToken(), delayMs);
|
|
57
|
+
timer.unref?.();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Asks for a token, then schedules the next request: a renewal on success, a retry on failure. */
|
|
61
|
+
async function requestToken(): Promise<number> {
|
|
62
|
+
const res = await fetch(tokenUrl, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
65
|
+
body: new URLSearchParams({
|
|
66
|
+
grant_type: "client_credentials",
|
|
67
|
+
client_id: credentials.clientId,
|
|
68
|
+
client_secret: credentials.clientSecret,
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
if (!res.ok)
|
|
72
|
+
throw new Error(`OAuth2 token request failed: ${res.status} ${res.statusText}`);
|
|
73
|
+
|
|
74
|
+
const data = (await res.json()) as { access_token?: string; expires_in?: number };
|
|
75
|
+
if (!data.access_token) throw new Error("OAuth2 token response had no access_token.");
|
|
76
|
+
|
|
77
|
+
const ttlMs = (data.expires_in ?? 3600) * 1000;
|
|
78
|
+
accessToken = data.access_token;
|
|
79
|
+
expiresAt = Date.now() + ttlMs;
|
|
80
|
+
return ttlMs;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function refreshToken(): Promise<void> {
|
|
84
|
+
try {
|
|
85
|
+
const ttlMs = await requestToken();
|
|
86
|
+
lastError = null;
|
|
87
|
+
retryMs = FIRST_RETRY_MS;
|
|
88
|
+
schedule(Math.max(ttlMs - REFRESH_SKEW_MS, 30_000));
|
|
89
|
+
} catch (error) {
|
|
90
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
91
|
+
console.error(
|
|
92
|
+
`[dev server] OAuth2 token request failed; trying again in ${retryMs / 1000}s:`,
|
|
93
|
+
lastError,
|
|
94
|
+
);
|
|
95
|
+
schedule(retryMs);
|
|
96
|
+
retryMs = Math.min(retryMs * 2, MAX_RETRY_MS);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
void refreshToken();
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
apiUrl,
|
|
104
|
+
token: () => (accessToken && Date.now() < expiresAt ? accessToken : null),
|
|
105
|
+
lastError: () => lastError,
|
|
106
|
+
};
|
|
107
|
+
}
|
package/src/vite/index.ts
CHANGED
|
@@ -6,13 +6,17 @@ import react, { reactCompilerPreset } from "@vitejs/plugin-react";
|
|
|
6
6
|
import babel from "@rolldown/plugin-babel";
|
|
7
7
|
import tailwindcss from "@tailwindcss/vite";
|
|
8
8
|
|
|
9
|
+
import { createApiTokenSource } from "./apiToken.js";
|
|
9
10
|
import { createDevServerProxy } from "./serverProxy.js";
|
|
10
11
|
import { envVarsLocationPlugin } from "./plugins/envVarsLocation.js";
|
|
11
12
|
import { serveDevAppConfigPlugin } from "./plugins/serveDevAppConfig.js";
|
|
13
|
+
import { serveDevFilesPlugin } from "./plugins/serveDevFiles.js";
|
|
12
14
|
|
|
15
|
+
export { createApiTokenSource, type ApiTokenSource } from "./apiToken.js";
|
|
13
16
|
export { createDevServerProxy } from "./serverProxy.js";
|
|
14
17
|
export { envVarsLocationPlugin } from "./plugins/envVarsLocation.js";
|
|
15
18
|
export { serveDevAppConfigPlugin } from "./plugins/serveDevAppConfig.js";
|
|
19
|
+
export { serveDevFilesPlugin } from "./plugins/serveDevFiles.js";
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* defineAppConfig
|
|
@@ -34,6 +38,8 @@ export { serveDevAppConfigPlugin } from "./plugins/serveDevAppConfig.js";
|
|
|
34
38
|
* authenticates against `EVERYSK_API_URL` using OAuth2 client credentials.
|
|
35
39
|
* - React with the compiler preset, Tailwind, and the two plugins that carry the app's
|
|
36
40
|
* `config.json` `env_vars` into the page — a meta tag in build, `/app-config.json` in dev.
|
|
41
|
+
* - The platform's `/file/<hash>` route, on the dev server and `vite preview`, so a file's own
|
|
42
|
+
* `url` shows it there too. It shares the `/api` proxy's OAuth2 token.
|
|
37
43
|
*
|
|
38
44
|
* @param {UserConfig} [overrides] - Merged over the preset with Vite's own `mergeConfig`, so
|
|
39
45
|
* arrays (plugins, aliases) are concatenated rather than replaced.
|
|
@@ -47,6 +53,8 @@ export const defineAppConfig = (overrides: UserConfig = {}) =>
|
|
|
47
53
|
const env = loadEnv(mode, root, "");
|
|
48
54
|
Object.assign(process.env, env);
|
|
49
55
|
|
|
56
|
+
const tokens = command === "serve" ? createApiTokenSource() : undefined;
|
|
57
|
+
|
|
50
58
|
const preset: UserConfig = {
|
|
51
59
|
base: "./",
|
|
52
60
|
envDir: root,
|
|
@@ -66,7 +74,7 @@ export const defineAppConfig = (overrides: UserConfig = {}) =>
|
|
|
66
74
|
host: true,
|
|
67
75
|
port: Number(process.env.PORT) || 5173,
|
|
68
76
|
strictPort: true,
|
|
69
|
-
proxy:
|
|
77
|
+
proxy: tokens ? createDevServerProxy(tokens) : undefined,
|
|
70
78
|
},
|
|
71
79
|
|
|
72
80
|
plugins: [
|
|
@@ -75,6 +83,7 @@ export const defineAppConfig = (overrides: UserConfig = {}) =>
|
|
|
75
83
|
tailwindcss(),
|
|
76
84
|
envVarsLocationPlugin(),
|
|
77
85
|
serveDevAppConfigPlugin(),
|
|
86
|
+
serveDevFilesPlugin(tokens),
|
|
78
87
|
],
|
|
79
88
|
};
|
|
80
89
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
2
|
|
|
3
3
|
export function envVarsLocationPlugin(): Plugin {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
return {
|
|
5
|
+
name: "env_vars_location",
|
|
6
|
+
apply: "build",
|
|
7
|
+
transformIndexHtml() {
|
|
8
|
+
return [{ tag: "meta", attrs: { name: "app-config" }, injectTo: "head" }];
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
11
|
}
|
|
@@ -16,36 +16,36 @@ import type { Plugin } from "vite";
|
|
|
16
16
|
* on change — a full reload re-runs the bootstrap fetch, picking up edits to `env_vars` live.
|
|
17
17
|
*/
|
|
18
18
|
export function serveDevAppConfigPlugin(): Plugin {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
return {
|
|
20
|
+
name: "serve-dev-app-config",
|
|
21
|
+
apply: "serve",
|
|
22
|
+
configureServer(server) {
|
|
23
|
+
// apps/<name>/config.json — one level up from the Vite root (the app's project/ dir).
|
|
24
|
+
const configPath = path.resolve(process.cwd(), "..", "config.json");
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
server.middlewares.use(async (req, res, next) => {
|
|
27
|
+
if (req.url !== "/app-config.json") return next();
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
try {
|
|
30
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
31
|
+
const envVars = (JSON.parse(content).env_vars ?? {}) as Record<string, unknown>;
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
34
|
+
res.statusCode = 200;
|
|
35
|
+
res.end(JSON.stringify(envVars));
|
|
36
|
+
} catch {
|
|
37
|
+
res.statusCode = 404;
|
|
38
|
+
res.end();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
// config.json is outside the Vite root, so watch it explicitly and full-reload on change.
|
|
43
|
+
server.watcher.add(configPath);
|
|
44
|
+
server.watcher.on("change", (file) => {
|
|
45
|
+
if (path.resolve(file) === configPath) {
|
|
46
|
+
server.hot.send({ type: "full-reload" });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
51
|
}
|