@lepsto/app-dev 0.2.0 → 0.3.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/README.md +6 -6
- package/dist/index.d.ts +19 -9
- package/dist/index.js +93 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @lepsto/app-dev
|
|
2
2
|
|
|
3
|
-
Vite plugin for local development of Lepsto Apps. It proxies `/lessly-api/*` to the
|
|
3
|
+
Vite plugin for local development of Lepsto Apps. It proxies `/lepsto-api/*` (and, for apps scaffolded before the rename, `/lessly-api/*`) to the
|
|
4
4
|
Lepsto REST API with an automatically-injected bearer token, exposes the dev
|
|
5
5
|
`import.meta.env` values your App needs, and serves the Module-Federation manifest
|
|
6
6
|
at the origin root.
|
|
@@ -10,10 +10,10 @@ at the origin root.
|
|
|
10
10
|
```ts
|
|
11
11
|
// vite.config.ts
|
|
12
12
|
import { defineConfig } from 'vite';
|
|
13
|
-
import {
|
|
13
|
+
import { lepstoAppDev } from '@lepsto/app-dev';
|
|
14
14
|
|
|
15
15
|
export default defineConfig({
|
|
16
|
-
plugins: [
|
|
16
|
+
plugins: [lepstoAppDev()],
|
|
17
17
|
});
|
|
18
18
|
```
|
|
19
19
|
|
|
@@ -41,7 +41,7 @@ localStorage.setItem('lessly:dev-remote', '<app-slug>=http://localhost:5174')
|
|
|
41
41
|
The contract has two halves that are easy to confuse:
|
|
42
42
|
|
|
43
43
|
- **key** — the App's Module Federation remote name, i.e. `id` in
|
|
44
|
-
`lessly.app.yaml` (= `APP_NAME` in `config/app-name.mjs`). Not the product
|
|
44
|
+
`lepsto.app.yaml` / `lessly.app.yaml` (= `APP_NAME` in `config/app-name.mjs`). Not the product
|
|
45
45
|
slug.
|
|
46
46
|
- **value** — the App's **own** dev origin, a bare origin with no base path. The
|
|
47
47
|
shell fetches `<origin>/mf-manifest.json` at the origin root; the plugin's
|
|
@@ -56,14 +56,14 @@ shell from loading an `http://localhost` remote.
|
|
|
56
56
|
|
|
57
57
|
## Authentication
|
|
58
58
|
|
|
59
|
-
The plugin injects `Authorization: Bearer <token>` on every `/lessly-api/*` request
|
|
59
|
+
The plugin injects `Authorization: Bearer <token>` on every `/lepsto-api/*` (or legacy `/lessly-api/*`) request
|
|
60
60
|
using, in order: a valid cached token → a stored token in `~/.lessly/credentials.json`
|
|
61
61
|
→ a refreshed token → `DEV_API_KEY` exchange → interactive device login. Token
|
|
62
62
|
refresh is coordinated across processes with a file lock beside the credentials file.
|
|
63
63
|
|
|
64
64
|
## Scope: REST-only (v1)
|
|
65
65
|
|
|
66
|
-
**This plugin proxies HTTP REST calls only. WebSocket upgrades on `/
|
|
66
|
+
**This plugin proxies HTTP REST calls only. WebSocket upgrades on `/lepsto-api/*`
|
|
67
67
|
are intentionally not proxied in v1** — Lepsto Apps use the REST API for local
|
|
68
68
|
development, and adding WS proxying (and its auth/reconnect semantics) is deferred
|
|
69
69
|
until a concrete need exists. If you require realtime transport locally, connect to
|
package/dist/index.d.ts
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
|
|
3
|
-
type
|
|
4
|
-
|
|
3
|
+
type LepstoEnvName = 'staging' | 'prod';
|
|
4
|
+
/** @deprecated Use {@link LepstoEnvName}. */
|
|
5
|
+
type LesslyEnvName = LepstoEnvName;
|
|
6
|
+
interface LepstoAppDevOptions {
|
|
5
7
|
/** Override env; defaults to LEPSTO_ENV (deprecated fallback: LESSLY_ENV) or 'staging'. */
|
|
6
|
-
env?:
|
|
8
|
+
env?: LepstoEnvName;
|
|
7
9
|
/** Override REST API base; defaults to VITE_API_URL or the env default. */
|
|
8
10
|
apiUrl?: string;
|
|
9
11
|
/** Vite dev-server proxy path. Fixed contract C1. */
|
|
10
|
-
proxyPath?: '/lessly-api';
|
|
12
|
+
proxyPath?: '/lepsto-api' | '/lessly-api';
|
|
11
13
|
}
|
|
14
|
+
/** @deprecated Use {@link LepstoAppDevOptions}. */
|
|
15
|
+
type LesslyAppDevOptions = LepstoAppDevOptions;
|
|
12
16
|
interface ResolvedEnv {
|
|
13
|
-
name:
|
|
17
|
+
name: LepstoEnvName;
|
|
14
18
|
apiUrl: string;
|
|
15
19
|
authServer: string;
|
|
16
|
-
|
|
20
|
+
/** Deployed Workspace shell for this environment — where a developer loads their App. */
|
|
21
|
+
shellOrigin: string;
|
|
22
|
+
profile: LepstoEnvName;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
declare function resolveShellDevOrigin(processEnv?: Record<string, string | undefined>): string;
|
|
20
26
|
|
|
21
|
-
interface
|
|
27
|
+
interface LepstoAppDevDeps {
|
|
22
28
|
/** Override the home dir used to locate ~/.lessly (tests). */
|
|
23
29
|
homedir?: () => string;
|
|
24
30
|
/** Override token-provider construction (tests / advanced embedding). */
|
|
@@ -29,6 +35,10 @@ interface LesslyAppDevDeps {
|
|
|
29
35
|
get(): Promise<string>;
|
|
30
36
|
};
|
|
31
37
|
}
|
|
32
|
-
declare function
|
|
38
|
+
declare function lepstoAppDev(options?: LepstoAppDevOptions, deps?: LepstoAppDevDeps): Plugin;
|
|
39
|
+
/** @deprecated Use {@link lepstoAppDev}. Kept as an alias for existing configs. */
|
|
40
|
+
declare const lesslyAppDev: typeof lepstoAppDev;
|
|
41
|
+
/** @deprecated Use {@link LepstoAppDevDeps}. */
|
|
42
|
+
type LesslyAppDevDeps = LepstoAppDevDeps;
|
|
33
43
|
|
|
34
|
-
export { type LesslyAppDevDeps, type LesslyAppDevOptions, type LesslyEnvName, type ResolvedEnv, lesslyAppDev, resolveShellDevOrigin };
|
|
44
|
+
export { type LepstoAppDevDeps, type LepstoAppDevOptions, type LepstoEnvName, type LesslyAppDevDeps, type LesslyAppDevOptions, type LesslyEnvName, type ResolvedEnv, lepstoAppDev, lesslyAppDev, resolveShellDevOrigin };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { loadEnv } from "vite";
|
|
3
3
|
|
|
4
|
-
// src/
|
|
5
|
-
var
|
|
6
|
-
staging:
|
|
7
|
-
prod:
|
|
4
|
+
// src/platform-hosts.ts
|
|
5
|
+
var PLATFORM_BASE_DOMAIN = {
|
|
6
|
+
staging: "lepsto.dev",
|
|
7
|
+
prod: "lessly.com"
|
|
8
|
+
};
|
|
9
|
+
var SUBDOMAIN = {
|
|
10
|
+
staging: { api: "api", auth: "auth", shell: "console" },
|
|
11
|
+
prod: { api: "api", auth: "auth", shell: "app" }
|
|
12
|
+
};
|
|
13
|
+
function platformOrigin(subdomain, env) {
|
|
14
|
+
return `https://${subdomain}.${PLATFORM_BASE_DOMAIN[env]}`;
|
|
15
|
+
}
|
|
16
|
+
var PLATFORM_HOSTS = {
|
|
17
|
+
staging: {
|
|
18
|
+
apiUrl: platformOrigin(SUBDOMAIN.staging.api, "staging"),
|
|
19
|
+
authServer: platformOrigin(SUBDOMAIN.staging.auth, "staging"),
|
|
20
|
+
shellOrigin: platformOrigin(SUBDOMAIN.staging.shell, "staging")
|
|
21
|
+
},
|
|
22
|
+
prod: {
|
|
23
|
+
apiUrl: platformOrigin(SUBDOMAIN.prod.api, "prod"),
|
|
24
|
+
authServer: platformOrigin(SUBDOMAIN.prod.auth, "prod"),
|
|
25
|
+
shellOrigin: platformOrigin(SUBDOMAIN.prod.shell, "prod")
|
|
26
|
+
}
|
|
8
27
|
};
|
|
28
|
+
|
|
29
|
+
// src/env.ts
|
|
9
30
|
function resolveEnv(input = {}) {
|
|
10
31
|
const pe = input.processEnv ?? process.env;
|
|
11
32
|
let envVar = pe.LEPSTO_ENV;
|
|
@@ -16,38 +37,42 @@ function resolveEnv(input = {}) {
|
|
|
16
37
|
}
|
|
17
38
|
}
|
|
18
39
|
const name = input.env ?? (envVar === "prod" ? "prod" : "staging");
|
|
19
|
-
const hosts =
|
|
40
|
+
const hosts = PLATFORM_HOSTS[name];
|
|
20
41
|
return {
|
|
21
42
|
name,
|
|
22
43
|
profile: name,
|
|
23
44
|
apiUrl: input.apiUrl ?? pe.VITE_API_URL ?? hosts.apiUrl,
|
|
24
|
-
authServer: hosts.authServer
|
|
45
|
+
authServer: hosts.authServer,
|
|
46
|
+
shellOrigin: hosts.shellOrigin
|
|
25
47
|
};
|
|
26
48
|
}
|
|
27
49
|
|
|
28
50
|
// src/proxy.ts
|
|
29
51
|
function buildProxyConfig(args) {
|
|
30
|
-
const
|
|
31
|
-
const
|
|
52
|
+
const out = {};
|
|
53
|
+
for (const prefix of [args.proxyPath, ...args.legacyPaths ?? []]) {
|
|
54
|
+
out[`^${prefix}(?:[/?]|$)`] = proxyEntry(args.apiUrl, prefix);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
function proxyEntry(apiUrl, prefix) {
|
|
32
59
|
return {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
});
|
|
50
|
-
}
|
|
60
|
+
target: apiUrl,
|
|
61
|
+
changeOrigin: true,
|
|
62
|
+
rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ""),
|
|
63
|
+
// Bearer injection happens in the awaited auth middleware (see auth-middleware.ts);
|
|
64
|
+
// Vite forwards incoming request headers to the upstream. Here we only surface a
|
|
65
|
+
// one-time hint when the upstream rejects our token.
|
|
66
|
+
configure: (proxy) => {
|
|
67
|
+
let warned = false;
|
|
68
|
+
proxy.on("proxyRes", (proxyRes) => {
|
|
69
|
+
if (proxyRes.statusCode === 401 && !warned) {
|
|
70
|
+
warned = true;
|
|
71
|
+
console.warn(
|
|
72
|
+
" [lessly-app-dev] upstream returned 401 Unauthorized. Your session may be revoked server-side. Delete ~/.lessly/credentials.json and re-run dev to re-login."
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
51
76
|
}
|
|
52
77
|
};
|
|
53
78
|
}
|
|
@@ -64,7 +89,12 @@ function productIdMissingWarning() {
|
|
|
64
89
|
}
|
|
65
90
|
function buildDefine(args) {
|
|
66
91
|
const define = {
|
|
67
|
-
"import.meta.env.
|
|
92
|
+
"import.meta.env.VITE_LEPSTO_BASE_URL": JSON.stringify(args.proxyPath),
|
|
93
|
+
// Unchanged for apps scaffolded before the rename: the legacy name still
|
|
94
|
+
// resolves to the legacy prefix, which stays proxied and authenticated.
|
|
95
|
+
"import.meta.env.VITE_LESSLY_BASE_URL": JSON.stringify(
|
|
96
|
+
args.legacyProxyPath ?? args.proxyPath
|
|
97
|
+
)
|
|
68
98
|
};
|
|
69
99
|
if (args.productId) define["import.meta.env.VITE_PRODUCT_ID"] = JSON.stringify(args.productId);
|
|
70
100
|
return define;
|
|
@@ -373,7 +403,7 @@ async function resolveApiKeyToken(args) {
|
|
|
373
403
|
function resolveShellDevOrigin(processEnv = process.env) {
|
|
374
404
|
return processEnv.SHELL_DEV_ORIGIN ?? "http://localhost:5173";
|
|
375
405
|
}
|
|
376
|
-
var DEFAULT_SHELL_ORIGIN =
|
|
406
|
+
var DEFAULT_SHELL_ORIGIN = PLATFORM_HOSTS.staging.shellOrigin;
|
|
377
407
|
function shellOverrideMessage(input) {
|
|
378
408
|
const { slug, appOrigin, base = "/", productSlug } = input;
|
|
379
409
|
const shellOrigin = input.shellOrigin ?? DEFAULT_SHELL_ORIGIN;
|
|
@@ -401,13 +431,17 @@ function shellOverrideMessage(input) {
|
|
|
401
431
|
// src/app-slug.ts
|
|
402
432
|
import { readFileSync as readFileSync3 } from "fs";
|
|
403
433
|
import { join as join3 } from "path";
|
|
434
|
+
var MANIFEST_FILES = ["lepsto.app.yaml", "lessly.app.yaml"];
|
|
404
435
|
function resolveAppSlug(root, readFile = (p) => readFileSync3(p, "utf8")) {
|
|
405
436
|
let raw;
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
437
|
+
for (const file of MANIFEST_FILES) {
|
|
438
|
+
try {
|
|
439
|
+
raw = readFile(join3(root, file));
|
|
440
|
+
break;
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
410
443
|
}
|
|
444
|
+
if (raw === void 0) return void 0;
|
|
411
445
|
for (const line of raw.split("\n")) {
|
|
412
446
|
const match = /^id:\s*(.*)$/.exec(line);
|
|
413
447
|
if (!match) continue;
|
|
@@ -436,10 +470,12 @@ function mfManifestMiddleware(getBase) {
|
|
|
436
470
|
|
|
437
471
|
// src/auth-middleware.ts
|
|
438
472
|
function createAuthMiddleware(args) {
|
|
439
|
-
const
|
|
473
|
+
const boundaries = [args.proxyPath, ...args.legacyPaths ?? []].map(
|
|
474
|
+
(prefix) => new RegExp(`^${prefix}(?:[/?]|$)`)
|
|
475
|
+
);
|
|
440
476
|
return (req, res, next) => {
|
|
441
477
|
const url = req.url ?? "";
|
|
442
|
-
if (!
|
|
478
|
+
if (!boundaries.some((b) => b.test(url))) return next();
|
|
443
479
|
if (req.headers["sec-fetch-site"] === "cross-site") {
|
|
444
480
|
res.statusCode = 403;
|
|
445
481
|
res.end("cross-site requests to the Lessly dev proxy are not allowed");
|
|
@@ -457,8 +493,9 @@ function createAuthMiddleware(args) {
|
|
|
457
493
|
|
|
458
494
|
// src/index.ts
|
|
459
495
|
import { homedir as homedir2 } from "os";
|
|
460
|
-
var PROXY_PATH = "/
|
|
461
|
-
|
|
496
|
+
var PROXY_PATH = "/lepsto-api";
|
|
497
|
+
var LEGACY_PROXY_PATHS = ["/lessly-api"];
|
|
498
|
+
function lepstoAppDev(options = {}, deps = {}) {
|
|
462
499
|
const homedirFn = deps.homedir ?? homedir2;
|
|
463
500
|
const devDeps = defaultDeviceDeps();
|
|
464
501
|
const post = devDeps.post;
|
|
@@ -491,12 +528,26 @@ function lesslyAppDev(options = {}, deps = {}) {
|
|
|
491
528
|
productSlug = fileEnv.VITE_PRODUCT_SLUG;
|
|
492
529
|
token = buildToken(resolvedEnv, fileEnv.DEV_API_KEY);
|
|
493
530
|
return {
|
|
494
|
-
server: {
|
|
495
|
-
|
|
531
|
+
server: {
|
|
532
|
+
proxy: buildProxyConfig({
|
|
533
|
+
apiUrl: resolvedEnv.apiUrl,
|
|
534
|
+
proxyPath: PROXY_PATH,
|
|
535
|
+
legacyPaths: LEGACY_PROXY_PATHS
|
|
536
|
+
})
|
|
537
|
+
},
|
|
538
|
+
define: buildDefine({
|
|
539
|
+
productId,
|
|
540
|
+
proxyPath: PROXY_PATH,
|
|
541
|
+
legacyProxyPath: LEGACY_PROXY_PATHS[0]
|
|
542
|
+
})
|
|
496
543
|
};
|
|
497
544
|
},
|
|
498
545
|
configureServer(server) {
|
|
499
|
-
if (token)
|
|
546
|
+
if (token) {
|
|
547
|
+
server.middlewares.use(
|
|
548
|
+
createAuthMiddleware({ proxyPath: PROXY_PATH, legacyPaths: LEGACY_PROXY_PATHS, token })
|
|
549
|
+
);
|
|
550
|
+
}
|
|
500
551
|
server.middlewares.use(mfManifestMiddleware(() => server.config.base));
|
|
501
552
|
server.httpServer?.once("listening", () => {
|
|
502
553
|
const env = resolvedEnv;
|
|
@@ -513,6 +564,7 @@ function lesslyAppDev(options = {}, deps = {}) {
|
|
|
513
564
|
slug: appSlug,
|
|
514
565
|
appOrigin,
|
|
515
566
|
base: server.config.base ?? "/",
|
|
567
|
+
shellOrigin: env.shellOrigin,
|
|
516
568
|
productSlug
|
|
517
569
|
})
|
|
518
570
|
);
|
|
@@ -520,7 +572,9 @@ function lesslyAppDev(options = {}, deps = {}) {
|
|
|
520
572
|
}
|
|
521
573
|
};
|
|
522
574
|
}
|
|
575
|
+
var lesslyAppDev = lepstoAppDev;
|
|
523
576
|
export {
|
|
577
|
+
lepstoAppDev,
|
|
524
578
|
lesslyAppDev,
|
|
525
579
|
resolveShellDevOrigin
|
|
526
580
|
};
|