@antelopejs/dms-frontend 0.7.0 → 0.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/README.md +4 -4
- package/dist/commands/build-action.d.ts +8 -5
- package/dist/commands/build-action.js +13 -41
- package/dist/commands/build.js +9 -15
- package/dist/commands/dev-action.js +5 -1
- package/dist/commands/start-action.d.ts +2 -0
- package/dist/commands/start-action.js +36 -49
- package/dist/commands/start.js +8 -6
- package/dist/config.d.ts +2 -0
- package/dist/config.js +6 -0
- package/dist/derived-outputs.js +2 -2
- package/dist/fs-sync.d.ts +1 -0
- package/dist/fs-sync.js +6 -0
- package/dist/help.js +8 -4
- package/dist/index.js +4 -6
- package/dist/layer-watch.js +4 -0
- package/dist/layers.d.ts +1 -3
- package/dist/layers.js +1 -95
- package/dist/manifest.d.ts +8 -8
- package/dist/manifest.js +15 -39
- package/dist/materialize.js +6 -0
- package/dist/module-set.d.ts +14 -12
- package/dist/module-set.js +39 -31
- package/dist/private-values.d.ts +2 -2
- package/dist/private-values.js +1 -6
- package/dist/project-modules.d.ts +46 -0
- package/dist/project-modules.js +233 -0
- package/dist/workspace-setup.d.ts +3 -7
- package/dist/workspace-setup.js +41 -62
- package/dist/workspace.d.ts +2 -4
- package/dist/workspace.js +1 -1
- package/package.json +5 -7
- package/skills/dms-frontend/SKILL.md +2 -2
- package/templates/vue/client-chunking.mjs +61 -0
- package/templates/vue/email-renderer.ts +6 -1
- package/templates/vue/frontend-build-loader.ts +1 -1
- package/templates/vue/frontend-module.ts +24 -0
- package/templates/vue/main.ts +11 -2
- package/templates/vue/package.json +2 -2
- package/templates/vue/prepare-types.mjs +2 -7
- package/templates/vue/public-options.ts +19 -0
- package/templates/vue/server/auth/backend.mjs +20 -3
- package/templates/vue/server/auth/client-ip.mjs +64 -22
- package/templates/vue/server/auth/session.mjs +4 -11
- package/templates/vue/server/backend-proxies.mjs +13 -2
- package/templates/vue/server/backend-response.mjs +2 -13
- package/templates/vue/server/cli-report.mjs +42 -18
- package/templates/vue/server/client-manifest.mjs +22 -58
- package/templates/vue/server/dev-private-files.mjs +1 -1
- package/templates/vue/server/dev-styles.mjs +16 -5
- package/templates/vue/server/homepage.mjs +4 -1
- package/templates/vue/server/module-event.mjs +23 -8
- package/templates/vue/server/module-server.mjs +4 -4
- package/templates/vue/server/module-testing.mjs +5 -3
- package/templates/vue/server/proxy-compression.mjs +15 -8
- package/templates/vue/server/proxy-headers.mjs +12 -1
- package/templates/vue/server/proxy-redirects.mjs +66 -0
- package/templates/vue/server/proxy-transfers.mjs +84 -0
- package/templates/vue/server/public-endpoints.mjs +2 -2
- package/templates/vue/server/public-options.mjs +49 -0
- package/templates/vue/server/request-routing.mjs +20 -0
- package/templates/vue/server/web-body.mjs +24 -0
- package/templates/vue/server-module.ts +19 -7
- package/templates/vue/server.mjs +59 -49
- package/templates/vue/ssr-renderer.ts +19 -1
- package/templates/vue/vite.config.ts +72 -46
- package/templates/vue/vite.email.config.ts +6 -3
- package/templates/vue/vite.server.config.ts +5 -2
- package/dist/commands/snapshot-action.d.ts +0 -6
- package/dist/commands/snapshot-action.js +0 -53
- package/dist/commands/snapshot.d.ts +0 -2
- package/dist/commands/snapshot.js +0 -55
- package/dist/snapshot.d.ts +0 -25
- package/dist/snapshot.js +0 -90
- package/templates/vue/server/backend-wake.mjs +0 -129
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.cmdSnapshot = cmdSnapshot;
|
|
37
|
-
const commander_1 = require("commander");
|
|
38
|
-
const config_1 = require("../config");
|
|
39
|
-
const cli_1 = require("@antelopejs/core/cli");
|
|
40
|
-
const SNAPSHOT_EXAMPLES = [
|
|
41
|
-
{
|
|
42
|
-
description: "With DMS_BOOTSTRAP_SECRET set, after a backend release",
|
|
43
|
-
command: "ajs dms snapshot -b https://dms.example.com -o dms-snapshot",
|
|
44
|
-
},
|
|
45
|
-
];
|
|
46
|
-
function cmdSnapshot() {
|
|
47
|
-
const command = new commander_1.Command("snapshot")
|
|
48
|
-
.summary("Keep what a build needs from the backend, without its secrets")
|
|
49
|
-
.description("Fetch the manifest and the layers archive the backend serves, and write them into a directory with every private value emptied, so that ajs dms build --from builds without the backend or any credential. Take it again when the backend's frontend modules change: start says when they no longer match.")
|
|
50
|
-
.addOption(config_1.Options.backendUrl("Backend URL to take the snapshot from; required, as -b or the variable"))
|
|
51
|
-
.addOption(new commander_1.Option("-o, --output <dir>", "Directory to write the snapshot to").default("dms-snapshot"))
|
|
52
|
-
.addOption(config_1.Options.bootstrapSecret())
|
|
53
|
-
.action(async (options) => (await Promise.resolve().then(() => __importStar(require("./snapshot-action")))).runSnapshot(options));
|
|
54
|
-
return (0, cli_1.withExamples)(command, SNAPSHOT_EXAMPLES);
|
|
55
|
-
}
|
package/dist/snapshot.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { Manifest, ManifestModule } from "./workspace";
|
|
2
|
-
export declare const SNAPSHOT_FILE = "snapshot.json";
|
|
3
|
-
export declare const SNAPSHOT_LAYERS_DIR = "layers";
|
|
4
|
-
export declare const BUILD_ORIGIN_FILE = ".build-origin.json";
|
|
5
|
-
declare const SNAPSHOT_FORMAT = 1;
|
|
6
|
-
export interface FrontendSnapshot {
|
|
7
|
-
format: typeof SNAPSHOT_FORMAT;
|
|
8
|
-
backendUrl: string;
|
|
9
|
-
takenAt: string;
|
|
10
|
-
digest: string;
|
|
11
|
-
manifest: Manifest;
|
|
12
|
-
}
|
|
13
|
-
export interface BuildOrigin {
|
|
14
|
-
source: "snapshot" | "backend";
|
|
15
|
-
backendUrl: string;
|
|
16
|
-
takenAt: string;
|
|
17
|
-
digest: string;
|
|
18
|
-
authenticated: boolean;
|
|
19
|
-
}
|
|
20
|
-
export declare function manifestDigest(modules: readonly ManifestModule[]): string;
|
|
21
|
-
export declare function writeSnapshot(dir: string, backendUrl: string, manifest: Manifest, layersSource: string): FrontendSnapshot;
|
|
22
|
-
export declare function readSnapshot(dir: string): FrontendSnapshot;
|
|
23
|
-
export declare function writeBuildOrigin(workspaceDir: string, origin: BuildOrigin): void;
|
|
24
|
-
export declare function readBuildOrigin(workspaceDir: string): BuildOrigin | undefined;
|
|
25
|
-
export {};
|
package/dist/snapshot.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BUILD_ORIGIN_FILE = exports.SNAPSHOT_LAYERS_DIR = exports.SNAPSHOT_FILE = void 0;
|
|
4
|
-
exports.manifestDigest = manifestDigest;
|
|
5
|
-
exports.writeSnapshot = writeSnapshot;
|
|
6
|
-
exports.readSnapshot = readSnapshot;
|
|
7
|
-
exports.writeBuildOrigin = writeBuildOrigin;
|
|
8
|
-
exports.readBuildOrigin = readBuildOrigin;
|
|
9
|
-
const node_crypto_1 = require("node:crypto");
|
|
10
|
-
const node_fs_1 = require("node:fs");
|
|
11
|
-
const node_path_1 = require("node:path");
|
|
12
|
-
const cli_1 = require("@antelopejs/core/cli");
|
|
13
|
-
const config_1 = require("./config");
|
|
14
|
-
const module_set_1 = require("./module-set");
|
|
15
|
-
const private_values_1 = require("./private-values");
|
|
16
|
-
exports.SNAPSHOT_FILE = "snapshot.json";
|
|
17
|
-
exports.SNAPSHOT_LAYERS_DIR = "layers";
|
|
18
|
-
exports.BUILD_ORIGIN_FILE = ".build-origin.json";
|
|
19
|
-
const SNAPSHOT_FORMAT = 1;
|
|
20
|
-
const DIGEST_LENGTH = 16;
|
|
21
|
-
function manifestDigest(modules) {
|
|
22
|
-
return (0, node_crypto_1.createHash)("sha256")
|
|
23
|
-
.update((0, module_set_1.moduleSetFingerprint)(modules.map(({ path: _path, ...module }) => module)))
|
|
24
|
-
.digest("hex")
|
|
25
|
-
.slice(0, DIGEST_LENGTH);
|
|
26
|
-
}
|
|
27
|
-
function writeSnapshot(dir, backendUrl, manifest, layersSource) {
|
|
28
|
-
const modules = (0, private_values_1.withoutPrivateValues)(manifest.modules).map(({ path: _path, ...module }) => module);
|
|
29
|
-
const snapshot = {
|
|
30
|
-
format: SNAPSHOT_FORMAT,
|
|
31
|
-
backendUrl: (0, config_1.canonicalizeBackendUrl)(backendUrl),
|
|
32
|
-
takenAt: new Date().toISOString(),
|
|
33
|
-
digest: manifestDigest(modules),
|
|
34
|
-
manifest: { pack: manifest.pack, modules },
|
|
35
|
-
};
|
|
36
|
-
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
37
|
-
const layers = (0, node_path_1.join)(dir, exports.SNAPSHOT_LAYERS_DIR);
|
|
38
|
-
(0, node_fs_1.rmSync)(layers, { recursive: true, force: true });
|
|
39
|
-
(0, node_fs_1.cpSync)(layersSource, layers, { recursive: true });
|
|
40
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, exports.SNAPSHOT_FILE), `${JSON.stringify(snapshot, null, 2)}\n`);
|
|
41
|
-
return snapshot;
|
|
42
|
-
}
|
|
43
|
-
function readSnapshot(dir) {
|
|
44
|
-
const file = (0, node_path_1.join)(dir, exports.SNAPSHOT_FILE);
|
|
45
|
-
const refresh = "Take one: ajs dms snapshot -b <backend url> -o <dir>";
|
|
46
|
-
if (!(0, node_fs_1.existsSync)(file)) {
|
|
47
|
-
throw new cli_1.CliError({
|
|
48
|
-
title: `No snapshot in ${dir}`,
|
|
49
|
-
reason: `${exports.SNAPSHOT_FILE} is missing.`,
|
|
50
|
-
fixes: [refresh],
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
let snapshot;
|
|
54
|
-
try {
|
|
55
|
-
snapshot = JSON.parse((0, node_fs_1.readFileSync)(file, "utf8"));
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
throw new cli_1.CliError({ title: `${file} is not valid JSON`, fixes: [refresh] }, { cause: error });
|
|
59
|
-
}
|
|
60
|
-
if (snapshot.format !== SNAPSHOT_FORMAT ||
|
|
61
|
-
!Array.isArray(snapshot.manifest?.modules) ||
|
|
62
|
-
typeof snapshot.backendUrl !== "string") {
|
|
63
|
-
throw new cli_1.CliError({
|
|
64
|
-
title: `${file} is not a snapshot this loader can build from`,
|
|
65
|
-
reason: `It needs format ${SNAPSHOT_FORMAT}, a backend URL and a manifest.`,
|
|
66
|
-
fixes: [refresh],
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(dir, exports.SNAPSHOT_LAYERS_DIR))) {
|
|
70
|
-
throw new cli_1.CliError({
|
|
71
|
-
title: `The snapshot in ${dir} has no ${exports.SNAPSHOT_LAYERS_DIR} directory`,
|
|
72
|
-
fixes: [refresh],
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
return snapshot;
|
|
76
|
-
}
|
|
77
|
-
function writeBuildOrigin(workspaceDir, origin) {
|
|
78
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, exports.BUILD_ORIGIN_FILE), `${JSON.stringify(origin, null, 2)}\n`);
|
|
79
|
-
}
|
|
80
|
-
function readBuildOrigin(workspaceDir) {
|
|
81
|
-
try {
|
|
82
|
-
const origin = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(workspaceDir, exports.BUILD_ORIGIN_FILE), "utf8"));
|
|
83
|
-
return typeof origin.digest === "string"
|
|
84
|
-
? origin
|
|
85
|
-
: undefined;
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
// Waiting out a backend the platform is waking from sleep.
|
|
2
|
-
//
|
|
3
|
-
// A platform that scales the backend to zero answers the first requests with
|
|
4
|
-
// a 503 while it starts a replica, marked with the cold-start header and a
|
|
5
|
-
// Retry-After in whole seconds: all three make the signal, and a 503 missing
|
|
6
|
-
// one of them goes out as it came. The header is a reserved resume
|
|
7
|
-
// convention, not a proof of where the answer came from: a backend may set it
|
|
8
|
-
// too, and then gets the same bounded wait. Only a GET or a HEAD is repeated;
|
|
9
|
-
// a POST, PATCH or DELETE is sent once.
|
|
10
|
-
//
|
|
11
|
-
// The wait starts with the first cold-start answer and lasts at most the
|
|
12
|
-
// budget, the repeated requests included: one still waiting for its answer
|
|
13
|
-
// when the budget runs out is aborted, and no request starts after it. It
|
|
14
|
-
// ends at once when the client leaves.
|
|
15
|
-
//
|
|
16
|
-
// Lives apart from server.mjs so the file stays under the size the linter
|
|
17
|
-
// allows.
|
|
18
|
-
|
|
19
|
-
import { setTimeout as sleep } from "node:timers/promises";
|
|
20
|
-
|
|
21
|
-
/** Header marking a 503 as a cold start to wait out. */
|
|
22
|
-
export const COLD_START_HEADER = "x-antelope-cold-start";
|
|
23
|
-
const COLD_START_VALUE = "1";
|
|
24
|
-
const UNAVAILABLE = 503;
|
|
25
|
-
const RETRYABLE_METHODS = new Set(["GET", "HEAD"]);
|
|
26
|
-
const DEFAULT_BUDGET_MS = 30_000;
|
|
27
|
-
const MIN_DELAY_MS = 1_000;
|
|
28
|
-
// Repeats after the first request, so a GET is sent 16 times at most. They
|
|
29
|
-
// are capped as well as the time, so a marked answer with `Retry-After: 0`
|
|
30
|
-
// cannot multiply a GET.
|
|
31
|
-
const MAX_REPEATS = 15;
|
|
32
|
-
const MAX_DELAY_MS = 5_000;
|
|
33
|
-
const MS_PER_SECOND = 1_000;
|
|
34
|
-
const WHOLE_SECONDS = /^\d+$/;
|
|
35
|
-
const WHOLE_MILLISECONDS = /^\d+$/;
|
|
36
|
-
|
|
37
|
-
/** Whether `response` is a 503 marked as a cold start, with its Retry-After. */
|
|
38
|
-
export function isColdStart(response) {
|
|
39
|
-
return (
|
|
40
|
-
response.status === UNAVAILABLE &&
|
|
41
|
-
response.headers.get(COLD_START_HEADER) === COLD_START_VALUE &&
|
|
42
|
-
WHOLE_SECONDS.test(retryAfter(response))
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function retryAfter(response) {
|
|
47
|
-
return (response.headers.get("retry-after") ?? "").trim();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* How long a request may wait for a waking backend, from
|
|
52
|
-
* DMS_BACKEND_WAKE_BUDGET_MS. 0 turns the wait off; anything but a whole
|
|
53
|
-
* number of milliseconds keeps the default.
|
|
54
|
-
*/
|
|
55
|
-
export function wakeBudgetMs(env = process.env) {
|
|
56
|
-
const raw = (env.DMS_BACKEND_WAKE_BUDGET_MS ?? "").trim();
|
|
57
|
-
return WHOLE_MILLISECONDS.test(raw) ? Number(raw) : DEFAULT_BUDGET_MS;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** The pause a cold-start answer's Retry-After asks for, kept between bounds. */
|
|
61
|
-
function retryDelayMs(response) {
|
|
62
|
-
return Math.min(
|
|
63
|
-
MAX_DELAY_MS,
|
|
64
|
-
Math.max(MIN_DELAY_MS, Number(retryAfter(response)) * MS_PER_SECOND),
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* What the client gets once the budget is spent: the last cold-start answer's
|
|
70
|
-
* status and headers, without the body its pause already discarded.
|
|
71
|
-
*/
|
|
72
|
-
function expired(cold) {
|
|
73
|
-
return new Response(null, {
|
|
74
|
-
status: UNAVAILABLE,
|
|
75
|
-
headers: {
|
|
76
|
-
[COLD_START_HEADER]: COLD_START_VALUE,
|
|
77
|
-
"retry-after": retryAfter(cold),
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Sends one repeat, aborted if no answer has come by `deadline`. Once the
|
|
84
|
-
* answer has come the deadline no longer applies, so a ready backend's body
|
|
85
|
-
* streams for as long as it needs; only the client leaving ends it.
|
|
86
|
-
* Resolves to undefined when the deadline aborted the request.
|
|
87
|
-
*/
|
|
88
|
-
async function repeat(url, init, deadline, now) {
|
|
89
|
-
const late = new AbortController();
|
|
90
|
-
const timer = setTimeout(() => late.abort(), Math.max(0, deadline - now()));
|
|
91
|
-
const signal = init.signal
|
|
92
|
-
? AbortSignal.any([init.signal, late.signal])
|
|
93
|
-
: late.signal;
|
|
94
|
-
try {
|
|
95
|
-
return await fetch(url, { ...init, signal });
|
|
96
|
-
} catch (error) {
|
|
97
|
-
if (late.signal.aborted && !init.signal?.aborted) return undefined;
|
|
98
|
-
throw error;
|
|
99
|
-
} finally {
|
|
100
|
-
clearTimeout(timer);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* `fetch`, waiting out cold-start answers to a GET or a HEAD until `budgetMs`
|
|
106
|
-
* from the first of them is spent or MAX_REPEATS repeats are sent. Then the
|
|
107
|
-
* caller gets a cold-start 503 with its `Retry-After` to relay. The client
|
|
108
|
-
* leaving, through `init.signal`, ends the wait with its AbortError.
|
|
109
|
-
*/
|
|
110
|
-
export async function fetchWaking(
|
|
111
|
-
url,
|
|
112
|
-
init = {},
|
|
113
|
-
{ budgetMs = wakeBudgetMs(), now = Date.now } = {},
|
|
114
|
-
) {
|
|
115
|
-
const method = (init.method ?? "GET").toUpperCase();
|
|
116
|
-
let response = await fetch(url, init);
|
|
117
|
-
if (!RETRYABLE_METHODS.has(method)) return response;
|
|
118
|
-
const deadline = now() + budgetMs;
|
|
119
|
-
for (let repeats = 0; isColdStart(response); repeats += 1) {
|
|
120
|
-
const cold = response;
|
|
121
|
-
const delay = retryDelayMs(cold);
|
|
122
|
-
if (repeats >= MAX_REPEATS || now() + delay > deadline) return cold;
|
|
123
|
-
cold.body?.cancel().catch(() => {});
|
|
124
|
-
await sleep(delay, undefined, { signal: init.signal });
|
|
125
|
-
if (now() >= deadline) return expired(cold);
|
|
126
|
-
response = (await repeat(url, init, deadline, now)) ?? expired(cold);
|
|
127
|
-
}
|
|
128
|
-
return response;
|
|
129
|
-
}
|