@antelopejs/dms-frontend 0.6.4 → 0.7.1
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/build-action.d.ts +2 -0
- package/dist/commands/build-action.js +37 -9
- package/dist/commands/build.js +9 -3
- package/dist/commands/snapshot-action.d.ts +6 -0
- package/dist/commands/snapshot-action.js +53 -0
- package/dist/commands/snapshot.d.ts +2 -0
- package/dist/commands/snapshot.js +55 -0
- package/dist/commands/start-action.d.ts +1 -0
- package/dist/commands/start-action.js +80 -5
- package/dist/commands/start.js +7 -2
- package/dist/config.d.ts +3 -1
- package/dist/config.js +14 -3
- package/dist/help.js +9 -4
- package/dist/index.js +2 -0
- package/dist/manifest.d.ts +7 -1
- package/dist/manifest.js +21 -17
- package/dist/materialize.js +11 -1
- package/dist/module-set.d.ts +11 -0
- package/dist/module-set.js +18 -0
- package/dist/private-values.d.ts +9 -0
- package/dist/private-values.js +77 -0
- package/dist/snapshot.d.ts +25 -0
- package/dist/snapshot.js +90 -0
- package/dist/workspace-setup.d.ts +7 -0
- package/dist/workspace-setup.js +55 -23
- package/dist/workspace.d.ts +1 -0
- package/dist/workspace.js +6 -0
- package/package.json +1 -1
- package/templates/vue/server/backend-response.mjs +2 -2
- package/templates/vue/server/backend-wake.mjs +26 -26
- package/templates/vue/server/module-server.mjs +67 -5
|
@@ -7,9 +7,29 @@ const node_path_1 = require("node:path");
|
|
|
7
7
|
const cli_1 = require("@antelopejs/core/cli");
|
|
8
8
|
const child_output_1 = require("../child-output");
|
|
9
9
|
const common_1 = require("../common");
|
|
10
|
+
const snapshot_1 = require("../snapshot");
|
|
10
11
|
const output_1 = require("../output");
|
|
11
12
|
const vite_output_1 = require("../vite-output");
|
|
12
13
|
const workspace_task_1 = require("./workspace-task");
|
|
14
|
+
function buildSource(options) {
|
|
15
|
+
if (!options.from) {
|
|
16
|
+
return { backendUrl: (0, common_1.requireBackendUrl)(options.backendUrl) };
|
|
17
|
+
}
|
|
18
|
+
if (options.offline) {
|
|
19
|
+
throw new cli_1.CliError({
|
|
20
|
+
title: "--from and --offline cannot be combined",
|
|
21
|
+
reason: "A snapshot build never asks the backend already.",
|
|
22
|
+
fixes: ["Drop --offline"],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const snapshot = (0, snapshot_1.readSnapshot)(options.from);
|
|
26
|
+
return {
|
|
27
|
+
backendUrl: options.backendUrl
|
|
28
|
+
? (0, common_1.parseBackendUrl)(options.backendUrl)
|
|
29
|
+
: snapshot.backendUrl,
|
|
30
|
+
snapshot: { dir: options.from, snapshot },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
13
33
|
exports.BUILD_STEPS = [
|
|
14
34
|
{
|
|
15
35
|
script: "build:client",
|
|
@@ -102,19 +122,25 @@ async function runBuild(options) {
|
|
|
102
122
|
}
|
|
103
123
|
}
|
|
104
124
|
async function buildProduction(options, startedAt) {
|
|
105
|
-
const backendUrl = (
|
|
106
|
-
const
|
|
107
|
-
|
|
125
|
+
const { backendUrl, snapshot } = buildSource(options);
|
|
126
|
+
const bootstrapSecret = snapshot
|
|
127
|
+
? undefined
|
|
128
|
+
: (0, common_1.normalizeBootstrapSecret)(options.bootstrapSecret);
|
|
108
129
|
const ui = (0, cli_1.getProcessUi)();
|
|
109
130
|
(0, output_1.writeHeader)("build", [backendUrl, "production"]);
|
|
110
|
-
const { workspaceDir, layers, manifestFromCache, manifestFetchedAt } = await (0, workspace_task_1.setUpWorkspace)({
|
|
131
|
+
const { workspaceDir, layers, manifestFromCache, manifestFetchedAt, buildOrigin, } = await (0, workspace_task_1.setUpWorkspace)({
|
|
111
132
|
backendUrl,
|
|
112
133
|
force: !!options.force,
|
|
113
134
|
mode: "build",
|
|
114
|
-
offline: (0, common_1.flagOrEnv)(options.offline, "DMS_OFFLINE"),
|
|
135
|
+
offline: snapshot ? false : (0, common_1.flagOrEnv)(options.offline, "DMS_OFFLINE"),
|
|
115
136
|
bootstrapSecret,
|
|
137
|
+
workspaceDir: options.workspace,
|
|
138
|
+
snapshot,
|
|
116
139
|
});
|
|
117
|
-
if (
|
|
140
|
+
if (snapshot) {
|
|
141
|
+
ui.message("info", `Building from the snapshot of ${snapshot.snapshot.backendUrl}${(0, output_1.cachedAge)(snapshot.snapshot.takenAt)}`, { detail: `Modules digest ${snapshot.snapshot.digest}` });
|
|
142
|
+
}
|
|
143
|
+
else if (manifestFromCache) {
|
|
118
144
|
ui.message("warn", `Building from the manifest and layers archive${(0, output_1.cachedAge)(manifestFetchedAt)}`, { detail: "The output may not match the current backend." });
|
|
119
145
|
}
|
|
120
146
|
ui.message("info", "Building for production", {
|
|
@@ -129,7 +155,6 @@ async function buildProduction(options, startedAt) {
|
|
|
129
155
|
nameFile: (0, child_output_1.createFileNamer)(layers),
|
|
130
156
|
env: {
|
|
131
157
|
...process.env,
|
|
132
|
-
DMS_SESSION_SECRET: sessionSecret,
|
|
133
158
|
NODE_OPTIONS: "--max-old-space-size=4096",
|
|
134
159
|
NODE_PATH: (0, node_path_1.join)(workspaceDir, "node_modules"),
|
|
135
160
|
[vite_output_1.VITE_LOG_LEVEL_VARIABLE]: (0, vite_output_1.viteLogLevel)(isVerbose),
|
|
@@ -137,6 +162,7 @@ async function buildProduction(options, startedAt) {
|
|
|
137
162
|
};
|
|
138
163
|
for (const step of exports.BUILD_STEPS)
|
|
139
164
|
await runBuildStep(step, context);
|
|
165
|
+
(0, snapshot_1.writeBuildOrigin)(workspaceDir, buildOrigin);
|
|
140
166
|
(0, vite_output_1.reportViteWarnings)(context.warnings, isVerbose, ui);
|
|
141
167
|
(0, output_1.writeBlankLine)();
|
|
142
168
|
ui.summary({
|
|
@@ -145,8 +171,10 @@ async function buildProduction(options, startedAt) {
|
|
|
145
171
|
durationMs: Date.now() - startedAt,
|
|
146
172
|
nextSteps: [
|
|
147
173
|
{
|
|
148
|
-
command:
|
|
149
|
-
|
|
174
|
+
command: options.workspace
|
|
175
|
+
? `ajs dms start -b ${backendUrl} --workspace ${options.workspace}`
|
|
176
|
+
: `ajs dms start -b ${backendUrl}`,
|
|
177
|
+
description: "with DMS_SESSION_SECRET and the server's secrets in its environment",
|
|
150
178
|
},
|
|
151
179
|
],
|
|
152
180
|
});
|
package/dist/commands/build.js
CHANGED
|
@@ -39,9 +39,13 @@ const config_1 = require("../config");
|
|
|
39
39
|
const cli_1 = require("@antelopejs/core/cli");
|
|
40
40
|
const BUILD_EXAMPLES = [
|
|
41
41
|
{
|
|
42
|
-
description: "With
|
|
42
|
+
description: "With DMS_BOOTSTRAP_SECRET set in the environment or ./.env",
|
|
43
43
|
command: "ajs dms build -b https://dms.example.com",
|
|
44
44
|
},
|
|
45
|
+
{
|
|
46
|
+
description: "From a snapshot, without the backend or any secret, into the project (a container image)",
|
|
47
|
+
command: "ajs dms build --from dms-snapshot --workspace .dms-frontend",
|
|
48
|
+
},
|
|
45
49
|
{
|
|
46
50
|
description: "Rebuild from the cache while the backend is down",
|
|
47
51
|
command: "ajs dms build -b https://dms.example.com --offline",
|
|
@@ -50,8 +54,10 @@ const BUILD_EXAMPLES = [
|
|
|
50
54
|
function cmdBuild() {
|
|
51
55
|
const command = new commander_1.Command("build")
|
|
52
56
|
.summary("Build the production frontend")
|
|
53
|
-
.description("Build the production frontend from the manifest and layers archive the backend serves, into the workspace's dist directory.
|
|
54
|
-
.addOption(config_1.Options.backendUrl("Backend URL to build for; required, as -b or the variable"))
|
|
57
|
+
.description("Build the production frontend from the manifest and layers archive the backend serves, or from a snapshot of them, into the workspace's dist directory. The build holds no secret: the server reads its own from its environment when it starts.")
|
|
58
|
+
.addOption(config_1.Options.backendUrl("Backend URL to build for; required, as -b or the variable, unless --from names a snapshot"))
|
|
59
|
+
.addOption(new commander_1.Option("--from <dir>", "Build from the snapshot in this directory (ajs dms snapshot), without the backend"))
|
|
60
|
+
.addOption(config_1.Options.workspace("Build into this directory instead of the workspace under ~/.antelopejs, to ship it with the project"))
|
|
55
61
|
.addOption(config_1.Options.force("Extract the layers archive from scratch and reinstall the workspace dependencies"))
|
|
56
62
|
.addOption(config_1.Options.offline("Build from the last cached manifest and layers archive, without the backend"))
|
|
57
63
|
.addOption(config_1.Options.bootstrapSecret())
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSnapshot = runSnapshot;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_os_1 = require("node:os");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const cli_1 = require("@antelopejs/core/cli");
|
|
8
|
+
const common_1 = require("../common");
|
|
9
|
+
const output_1 = require("../output");
|
|
10
|
+
const snapshot_1 = require("../snapshot");
|
|
11
|
+
async function runSnapshot(options) {
|
|
12
|
+
const startedAt = Date.now();
|
|
13
|
+
const backendUrl = (0, common_1.requireBackendUrl)(options.backendUrl);
|
|
14
|
+
const bootstrapSecret = (0, common_1.normalizeBootstrapSecret)(options.bootstrapSecret);
|
|
15
|
+
if (!bootstrapSecret) {
|
|
16
|
+
throw new common_1.UsageError({
|
|
17
|
+
title: "A snapshot needs the bootstrap credential",
|
|
18
|
+
reason: "Without it the backend withholds the modules' backend proxies, and a build from the snapshot would not route them.",
|
|
19
|
+
fixes: [
|
|
20
|
+
"Set DMS_BOOTSTRAP_SECRET to the backend's frontend.bootstrapSecret",
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
(0, output_1.writeHeader)("snapshot", [backendUrl]);
|
|
25
|
+
const task = (0, cli_1.getProcessTasks)().start("Fetching the manifest and layers");
|
|
26
|
+
const scratch = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "dms-snapshot-"));
|
|
27
|
+
try {
|
|
28
|
+
const manifest = await (0, common_1.fetchManifest)(backendUrl, undefined, bootstrapSecret);
|
|
29
|
+
const layers = (0, node_path_1.join)(scratch, snapshot_1.SNAPSHOT_LAYERS_DIR);
|
|
30
|
+
await (0, common_1.downloadAndExtractLayers)(backendUrl, manifest.pack, layers, true, bootstrapSecret);
|
|
31
|
+
const snapshot = (0, snapshot_1.writeSnapshot)(options.output, backendUrl, manifest, layers);
|
|
32
|
+
task.succeed(`Fetched ${manifest.modules.length} frontend module${manifest.modules.length === 1 ? "" : "s"}`);
|
|
33
|
+
(0, output_1.writeBlankLine)();
|
|
34
|
+
(0, cli_1.getProcessUi)().summary({
|
|
35
|
+
headline: `Took a snapshot of modules ${snapshot.digest}, without private values`,
|
|
36
|
+
artifact: options.output,
|
|
37
|
+
durationMs: Date.now() - startedAt,
|
|
38
|
+
nextSteps: [
|
|
39
|
+
{
|
|
40
|
+
command: `ajs dms build --from ${options.output} --workspace .dms-frontend`,
|
|
41
|
+
description: "without the backend or any secret",
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
(0, output_1.endInterruptedTask)(task, error, "Snapshot stopped");
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
(0, node_fs_1.rmSync)(scratch, { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
}
|
|
@@ -10,7 +10,34 @@ const ports_1 = require("../ports");
|
|
|
10
10
|
const server_process_1 = require("../server-process");
|
|
11
11
|
const update_check_1 = require("../update-check");
|
|
12
12
|
const module_set_1 = require("../module-set");
|
|
13
|
+
const snapshot_1 = require("../snapshot");
|
|
14
|
+
const config_1 = require("../config");
|
|
15
|
+
const manifest_secrets_1 = require("../manifest-secrets");
|
|
16
|
+
const private_values_1 = require("../private-values");
|
|
13
17
|
const BUILD_TIME_MANIFEST = "the build-time manifest";
|
|
18
|
+
function builtPrivateOptions(workspaceDir) {
|
|
19
|
+
try {
|
|
20
|
+
const { modules } = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(workspaceDir, config_1.SERVER_OPTIONS_FILE), "utf8"));
|
|
21
|
+
return modules ?? {};
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function reportUnfilledPrivateValues(workspaceDir) {
|
|
28
|
+
const unfilled = (0, private_values_1.unfilledPrivateValues)(builtPrivateOptions(workspaceDir), (0, private_values_1.parsePrivateOptions)(process.env[private_values_1.PRIVATE_OPTIONS_VARIABLE]), Object.values(manifest_secrets_1.MANIFEST_SECRET_ENV));
|
|
29
|
+
const ui = (0, cli_1.getProcessUi)();
|
|
30
|
+
unfilled.forEach(({ module, path }, index) => {
|
|
31
|
+
const isLast = index === unfilled.length - 1;
|
|
32
|
+
ui.message("warn", `${module}: private option ${path.join(".")} is not set`, {
|
|
33
|
+
details: isLast
|
|
34
|
+
? [
|
|
35
|
+
`${ui.symbols.levels.hint} Set ${unfilled.length === 1 ? "it" : "them"} in ${private_values_1.PRIVATE_OPTIONS_VARIABLE}, a JSON object of options by module id, with the values the backend uses`,
|
|
36
|
+
]
|
|
37
|
+
: [],
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
14
41
|
const PRODUCTION = "production";
|
|
15
42
|
function serverNodeEnv() {
|
|
16
43
|
const nodeEnv = process.env.NODE_ENV || PRODUCTION;
|
|
@@ -24,22 +51,68 @@ function serverNodeEnv() {
|
|
|
24
51
|
}
|
|
25
52
|
return nodeEnv;
|
|
26
53
|
}
|
|
54
|
+
async function reportDrift(backendUrl, origin) {
|
|
55
|
+
const ui = (0, cli_1.getProcessUi)();
|
|
56
|
+
const bootstrapSecret = process.env.DMS_BOOTSTRAP_SECRET?.trim();
|
|
57
|
+
if (!origin)
|
|
58
|
+
return;
|
|
59
|
+
if (origin.authenticated === false) {
|
|
60
|
+
ui.message("info", `Built from modules ${origin.digest}; not compared with the backend`, {
|
|
61
|
+
detail: "The build read the manifest without DMS_BOOTSTRAP_SECRET, which leaves out the backend proxies.",
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!bootstrapSecret) {
|
|
66
|
+
ui.message("info", `Built from modules ${origin.digest}; not compared with the backend`, { detail: "Set DMS_BOOTSTRAP_SECRET to have start compare them." });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const drift = await (0, module_set_1.compareWithBackend)(backendUrl, origin.digest, snapshot_1.manifestDigest, bootstrapSecret);
|
|
70
|
+
if (drift.status === "matches") {
|
|
71
|
+
ui.message("info", `The backend serves the modules ${origin.digest} this build was made from`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (drift.status === "unknown") {
|
|
75
|
+
ui.message("info", `Built from modules ${origin.digest}; not compared with the backend: ${drift.reason}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const refresh = origin.source === "snapshot"
|
|
79
|
+
? `Refresh the snapshot and rebuild: ajs dms snapshot -b ${backendUrl} -o <dir>, then ajs dms build --from <dir>`
|
|
80
|
+
: `Rebuild: ajs dms build -b ${backendUrl}`;
|
|
81
|
+
ui.message("warn", `The backend serves modules ${drift.served}; this build was made from ${origin.digest} (${origin.source}${(0, output_1.cachedAge)(origin.takenAt)})`, {
|
|
82
|
+
details: [
|
|
83
|
+
"Pages of modules added or removed since, or whose routes changed, are missing or stale.",
|
|
84
|
+
`${ui.symbols.levels.hint} ${refresh}`,
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
}
|
|
27
88
|
async function runStart(options) {
|
|
28
89
|
const startedAt = Date.now();
|
|
29
|
-
const
|
|
90
|
+
const explicitWorkspace = options.workspace
|
|
91
|
+
? (0, node_path_1.resolve)(options.workspace)
|
|
92
|
+
: undefined;
|
|
93
|
+
const origin = explicitWorkspace
|
|
94
|
+
? (0, snapshot_1.readBuildOrigin)(explicitWorkspace)
|
|
95
|
+
: undefined;
|
|
96
|
+
const backendUrl = (0, common_1.requireBackendUrl)(options.backendUrl ?? origin?.backendUrl);
|
|
30
97
|
const requestedPort = (0, common_1.parsePort)(options.port);
|
|
31
98
|
const sessionSecret = (0, common_1.resolveSessionSecret)("start");
|
|
32
|
-
const workspaceDir = (0, common_1.getWorkspaceDir)(backendUrl);
|
|
99
|
+
const workspaceDir = explicitWorkspace ?? (0, common_1.getWorkspaceDir)(backendUrl);
|
|
33
100
|
const serverPath = (0, node_path_1.join)(workspaceDir, "server.mjs");
|
|
34
101
|
const clientPath = (0, node_path_1.join)(workspaceDir, "dist", "client", "index.html");
|
|
35
102
|
if (!(0, node_fs_1.existsSync)(serverPath) || !(0, node_fs_1.existsSync)(clientPath)) {
|
|
36
103
|
throw new cli_1.CliError({
|
|
37
104
|
title: `No production build for ${backendUrl}`,
|
|
38
105
|
reason: `${(0, output_1.showWorkspace)(workspaceDir)} has no built server or client.`,
|
|
39
|
-
fixes: [
|
|
106
|
+
fixes: [
|
|
107
|
+
explicitWorkspace
|
|
108
|
+
? `Build it first: ajs dms build --from <snapshot> --workspace ${options.workspace}`
|
|
109
|
+
: `Build it first: ajs dms build -b ${backendUrl}`,
|
|
110
|
+
],
|
|
40
111
|
});
|
|
41
112
|
}
|
|
42
|
-
(
|
|
113
|
+
if (!explicitWorkspace) {
|
|
114
|
+
(0, common_1.writeWorkspaceMeta)(workspaceDir, backendUrl, (0, common_1.canonicalizeBackendUrl)(backendUrl));
|
|
115
|
+
}
|
|
43
116
|
const builtAt = (0, node_fs_1.statSync)(clientPath).mtime.toISOString();
|
|
44
117
|
(0, output_1.writeHeader)("start", [
|
|
45
118
|
backendUrl,
|
|
@@ -57,7 +130,8 @@ async function runStart(options) {
|
|
|
57
130
|
}
|
|
58
131
|
const port = reserved.port;
|
|
59
132
|
const secrets = (0, common_1.resolveManifestSecrets)((0, common_1.collectManifestSecrets)((0, common_1.readCachedManifest)(workspaceDir)?.manifest.modules ?? []));
|
|
60
|
-
(0, common_1.reportManifestSecrets)(secrets, (count) => `Set ${count === 1 ? "it" : "them"}
|
|
133
|
+
(0, common_1.reportManifestSecrets)(secrets, (count) => `Set ${count === 1 ? "it" : "them"} in the server's environment, with the value${count === 1 ? "" : "s"} the backend uses`);
|
|
134
|
+
reportUnfilledPrivateValues(workspaceDir);
|
|
61
135
|
const secretSources = (0, common_1.describeSecretSources)(secrets.sources, BUILD_TIME_MANIFEST);
|
|
62
136
|
const nodeEnv = serverNodeEnv();
|
|
63
137
|
await reserved.release();
|
|
@@ -88,6 +162,7 @@ async function runStart(options) {
|
|
|
88
162
|
});
|
|
89
163
|
void (0, module_set_1.reportFrontendReady)(backendUrl, ((0, common_1.readCachedManifest)(workspaceDir)?.manifest.modules ?? []).map((module) => module.name), process.env.DMS_BOOTSTRAP_SECRET);
|
|
90
164
|
void (0, update_check_1.reportAvailableUpdate)({ isFollowed: true });
|
|
165
|
+
void reportDrift(backendUrl, origin ?? (0, snapshot_1.readBuildOrigin)(workspaceDir));
|
|
91
166
|
},
|
|
92
167
|
});
|
|
93
168
|
process.exitCode = code;
|
package/dist/commands/start.js
CHANGED
|
@@ -42,13 +42,18 @@ const START_EXAMPLES = [
|
|
|
42
42
|
description: "After ajs dms build -b https://dms.example.com",
|
|
43
43
|
command: "ajs dms start -b https://dms.example.com -p 3001",
|
|
44
44
|
},
|
|
45
|
+
{
|
|
46
|
+
description: "From a build shipped with the project, in a read-only container",
|
|
47
|
+
command: "ajs dms start --workspace .dms-frontend -p 3000",
|
|
48
|
+
},
|
|
45
49
|
];
|
|
46
50
|
function cmdStart() {
|
|
47
51
|
const command = new commander_1.Command("start")
|
|
48
52
|
.summary("Serve the production build")
|
|
49
|
-
.description("Serve the production frontend ajs dms build made for the same backend URL
|
|
50
|
-
.addOption(config_1.Options.backendUrl("Backend URL
|
|
53
|
+
.description("Serve the production frontend ajs dms build made for the same backend URL, or the one in --workspace. Reads DMS_SESSION_SECRET and the server's secrets (DMS_HTML_RENDER_SECRET, DMS_OAUTH_RELAY_SECRET) from its environment, and says once ready whether the backend still serves the modules the build was made from.")
|
|
54
|
+
.addOption(config_1.Options.backendUrl("Backend URL to serve; required, as -b or the variable, unless --workspace holds a build that records it"))
|
|
51
55
|
.addOption(config_1.Options.port)
|
|
56
|
+
.addOption(config_1.Options.workspace("Serve the build in this directory (ajs dms build --workspace); start writes nothing into it"))
|
|
52
57
|
.action(async (options) => (await Promise.resolve().then(() => __importStar(require("./start-action")))).runStart(options));
|
|
53
58
|
return (0, cli_1.withExamples)(command, START_EXAMPLES);
|
|
54
59
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare const DMS_FRONTEND_HOME: string;
|
|
|
6
6
|
export declare const DEPS_HASH_FILE = ".deps-hash";
|
|
7
7
|
export declare const WORKSPACE_DIR_MODE = 448;
|
|
8
8
|
export declare function writeSecretBearingFile(file: string, content: string): void;
|
|
9
|
+
export declare function writeShareableFile(file: string, content: string): void;
|
|
9
10
|
export declare const TEMPLATE_FILES: ReadonlyArray<readonly [string, string]>;
|
|
10
11
|
export declare const LAYERS_SUBDIR = "frontend-modules";
|
|
11
12
|
export declare const FRONTEND_MODULE_ENTRY = "dms.frontend.ts";
|
|
@@ -31,13 +32,14 @@ export declare const Options: {
|
|
|
31
32
|
force(description: string): Option;
|
|
32
33
|
offline(description: string): Option;
|
|
33
34
|
strict: Option;
|
|
35
|
+
workspace(description: string): Option;
|
|
34
36
|
bootstrapSecret(discovery?: string): Option;
|
|
35
37
|
};
|
|
36
38
|
export interface BootstrapDiscoveryOptions extends DiscoveryOptions {
|
|
37
39
|
cwd?: string;
|
|
38
40
|
}
|
|
39
41
|
export declare function normalizeBootstrapSecret(value: string | undefined, source?: string): string | undefined;
|
|
40
|
-
export type SessionSecretMode = "dev" | "
|
|
42
|
+
export type SessionSecretMode = "dev" | "start";
|
|
41
43
|
export declare function resolveSessionSecret(mode: SessionSecretMode, value?: string | undefined): string;
|
|
42
44
|
export declare function resolveBootstrapSecret(explicit: string | undefined, backendUrl: string, options?: BootstrapDiscoveryOptions): string | undefined;
|
|
43
45
|
export declare function sha256Hex(input: string): string;
|
package/dist/config.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.Options = exports.UsageError = exports.TAILWIND_SOURCE_GLOB = exports.PNPM_LIFECYCLE_SCRIPTS = exports.layerCopyIgnore = exports.LAYER_COPY_BLOCKLIST = exports.PUBLIC_ENDPOINTS_FILE = exports.AUTH_ESTABLISH_FILE = exports.SERVER_OPTIONS_FILE = exports.SERVER_MODULE_ENTRY = exports.FRONTEND_BUILD_ENTRY = exports.FRONTEND_MODULE_ENTRY = exports.LAYERS_SUBDIR = exports.TEMPLATE_FILES = exports.WORKSPACE_DIR_MODE = exports.DEPS_HASH_FILE = exports.DMS_FRONTEND_HOME = void 0;
|
|
7
7
|
exports.writeSecretBearingFile = writeSecretBearingFile;
|
|
8
|
+
exports.writeShareableFile = writeShareableFile;
|
|
8
9
|
exports.parseBackendUrl = parseBackendUrl;
|
|
9
10
|
exports.requireBackendUrl = requireBackendUrl;
|
|
10
11
|
exports.parsePort = parsePort;
|
|
@@ -30,9 +31,16 @@ const SECRET_BEARING_FILE_MODE = 0o600;
|
|
|
30
31
|
const TEMP_FILE_SUFFIX = ".tmp";
|
|
31
32
|
exports.WORKSPACE_DIR_MODE = 0o700;
|
|
32
33
|
function writeSecretBearingFile(file, content) {
|
|
34
|
+
writeFileAtomically(file, content, SECRET_BEARING_FILE_MODE);
|
|
35
|
+
}
|
|
36
|
+
const SHAREABLE_FILE_MODE = 0o644;
|
|
37
|
+
function writeShareableFile(file, content) {
|
|
38
|
+
writeFileAtomically(file, content, SHAREABLE_FILE_MODE);
|
|
39
|
+
}
|
|
40
|
+
function writeFileAtomically(file, content, mode) {
|
|
33
41
|
const temp = `${file}.${process.pid}${TEMP_FILE_SUFFIX}`;
|
|
34
|
-
(0, node_fs_1.writeFileSync)(temp, content, { mode
|
|
35
|
-
(0, node_fs_1.chmodSync)(temp,
|
|
42
|
+
(0, node_fs_1.writeFileSync)(temp, content, { mode });
|
|
43
|
+
(0, node_fs_1.chmodSync)(temp, mode);
|
|
36
44
|
(0, node_fs_1.renameSync)(temp, file);
|
|
37
45
|
}
|
|
38
46
|
exports.TEMPLATE_FILES = [
|
|
@@ -174,6 +182,9 @@ exports.Options = {
|
|
|
174
182
|
return new commander_1.Option("--offline", `${description} (env: DMS_OFFLINE)`);
|
|
175
183
|
},
|
|
176
184
|
strict: new commander_1.Option("--strict", "Exit 1 when the workspace cannot be prepared, and 2 when the backend URL is missing or invalid, instead of warning and exiting 0 (env: DMS_PREPARE_STRICT)"),
|
|
185
|
+
workspace(description) {
|
|
186
|
+
return new commander_1.Option("--workspace <dir>", description).env("DMS_WORKSPACE_DIR");
|
|
187
|
+
},
|
|
177
188
|
bootstrapSecret(discovery) {
|
|
178
189
|
return new commander_1.Option("--bootstrap-secret <secret>", discovery
|
|
179
190
|
? `${BOOTSTRAP_SECRET_DESCRIPTION}. ${discovery}`
|
|
@@ -226,7 +237,7 @@ function resolveSessionSecret(mode, value = process.env.DMS_SESSION_SECRET) {
|
|
|
226
237
|
title: `DMS_SESSION_SECRET ${describeSessionSecret(value)}`,
|
|
227
238
|
reason: mode === "dev"
|
|
228
239
|
? "dev generates an ephemeral secret only when the variable is not set at all."
|
|
229
|
-
: "
|
|
240
|
+
: "start signs sessions with it, so it must stay the same across restarts.",
|
|
230
241
|
fixes: [
|
|
231
242
|
"Create one: openssl rand -hex 32, then set it in the environment or ./.env",
|
|
232
243
|
],
|
package/dist/help.js
CHANGED
|
@@ -11,9 +11,10 @@ exports.ENVIRONMENT_VARIABLES = [
|
|
|
11
11
|
["DMS_BOOTSTRAP_SECRET", "Same as --bootstrap-secret"],
|
|
12
12
|
[
|
|
13
13
|
"DMS_SESSION_SECRET",
|
|
14
|
-
"Session key, 32 characters or more;
|
|
14
|
+
"Session key, 32 characters or more; start needs one, dev makes one when unset",
|
|
15
15
|
],
|
|
16
16
|
["DMS_OFFLINE", "Same as --offline, unless 0, false, no or off"],
|
|
17
|
+
["DMS_WORKSPACE_DIR", "Same as build and start --workspace"],
|
|
17
18
|
["DMS_PREPARE_STRICT", "Same as prepare --strict, read like DMS_OFFLINE"],
|
|
18
19
|
["PORT", "Same as -p"],
|
|
19
20
|
[
|
|
@@ -38,11 +39,15 @@ exports.ENVIRONMENT_VARIABLES = [
|
|
|
38
39
|
],
|
|
39
40
|
[
|
|
40
41
|
"DMS_HTML_RENDER_SECRET",
|
|
41
|
-
"E-mail render secret,
|
|
42
|
+
"E-mail render secret, the backend's htmlRender.serviceSecret; start needs it, a build keeps none",
|
|
42
43
|
],
|
|
43
44
|
[
|
|
44
45
|
"DMS_OAUTH_RELAY_SECRET",
|
|
45
|
-
"OAuth relay secret
|
|
46
|
+
"OAuth relay secret the backend expects; start needs it, a build keeps none",
|
|
47
|
+
],
|
|
48
|
+
[
|
|
49
|
+
"DMS_PRIVATE_OPTIONS",
|
|
50
|
+
"Any module's private options for start, as JSON by module id; a build keeps none",
|
|
46
51
|
],
|
|
47
52
|
[
|
|
48
53
|
"DMS_AUTH_ESTABLISH_ENDPOINTS",
|
|
@@ -58,7 +63,7 @@ exports.ENVIRONMENT_VARIABLES = [
|
|
|
58
63
|
],
|
|
59
64
|
[
|
|
60
65
|
"DMS_BACKEND_WAKE_BUDGET_MS",
|
|
61
|
-
"How long a GET or HEAD waits out a backend's
|
|
66
|
+
"How long a GET or HEAD waits out a waking or starting backend's 503, in ms (default: 30000; 0: never)",
|
|
62
67
|
],
|
|
63
68
|
["DMS_CLIENT_BASE_URL", "Public frontend URL used in links and e-mails"],
|
|
64
69
|
["NO_COLOR", "Same as --no-color"],
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const build_1 = require("./commands/build");
|
|
|
8
8
|
const clean_1 = require("./commands/clean");
|
|
9
9
|
const dev_1 = require("./commands/dev");
|
|
10
10
|
const prepare_1 = require("./commands/prepare");
|
|
11
|
+
const snapshot_1 = require("./commands/snapshot");
|
|
11
12
|
const start_1 = require("./commands/start");
|
|
12
13
|
const verify_source_1 = require("./commands/verify-source");
|
|
13
14
|
const workspaces_1 = require("./commands/workspaces");
|
|
@@ -116,6 +117,7 @@ const runCLI = async () => {
|
|
|
116
117
|
program.addCommand((0, dev_1.cmdDev)());
|
|
117
118
|
program.addCommand((0, build_1.cmdBuild)());
|
|
118
119
|
program.addCommand((0, start_1.cmdStart)());
|
|
120
|
+
program.addCommand((0, snapshot_1.cmdSnapshot)());
|
|
119
121
|
program.addCommand((0, prepare_1.cmdPrepare)());
|
|
120
122
|
program.addCommand((0, workspaces_1.cmdWorkspaces)());
|
|
121
123
|
program.addCommand((0, clean_1.cmdClean)());
|
package/dist/manifest.d.ts
CHANGED
|
@@ -29,13 +29,19 @@ export interface ManifestCacheEntry {
|
|
|
29
29
|
fetchedAt: string;
|
|
30
30
|
}
|
|
31
31
|
export declare function readCachedManifest(workspaceDir: string): ManifestCacheEntry | undefined;
|
|
32
|
+
export declare function writeCachedManifest(workspaceDir: string, manifest: Manifest, privateValues: PrivateValues, fetchedAt?: string): void;
|
|
33
|
+
export type PrivateValues = "keep" | "drop";
|
|
32
34
|
export type ManifestCacheUse = "fallback" | "offline" | "never";
|
|
33
35
|
export interface ResolvedManifest {
|
|
34
36
|
manifest: Manifest;
|
|
35
37
|
fromCache: boolean;
|
|
36
38
|
fetchedAt?: string;
|
|
37
39
|
}
|
|
38
|
-
export declare function resolveManifest(workspaceDir: string, backendUrl: string, cacheUse: ManifestCacheUse, clientUrl
|
|
40
|
+
export declare function resolveManifest(workspaceDir: string, backendUrl: string, cacheUse: ManifestCacheUse, { clientUrl, bootstrapSecret, privateValues, }?: {
|
|
41
|
+
clientUrl?: string;
|
|
42
|
+
bootstrapSecret?: string;
|
|
43
|
+
privateValues?: PrivateValues;
|
|
44
|
+
}): Promise<ResolvedManifest>;
|
|
39
45
|
export declare function assertCachedLayerPathsExist(modules: ServedWithPath[], fetchedAt?: string): void;
|
|
40
46
|
export type ServedWithPath = ManifestModule & {
|
|
41
47
|
path: string;
|
package/dist/manifest.js
CHANGED
|
@@ -7,12 +7,14 @@ exports.fetchFromBackend = fetchFromBackend;
|
|
|
7
7
|
exports.describeManifestVersion = describeManifestVersion;
|
|
8
8
|
exports.fetchManifest = fetchManifest;
|
|
9
9
|
exports.readCachedManifest = readCachedManifest;
|
|
10
|
+
exports.writeCachedManifest = writeCachedManifest;
|
|
10
11
|
exports.resolveManifest = resolveManifest;
|
|
11
12
|
exports.assertCachedLayerPathsExist = assertCachedLayerPathsExist;
|
|
12
13
|
const node_fs_1 = require("node:fs");
|
|
13
14
|
const node_path_1 = require("node:path");
|
|
14
15
|
const cli_1 = require("@antelopejs/core/cli");
|
|
15
16
|
const config_1 = require("./config");
|
|
17
|
+
const private_values_1 = require("./private-values");
|
|
16
18
|
const output_1 = require("./output");
|
|
17
19
|
const workspace_1 = require("./workspace");
|
|
18
20
|
class ManifestUnauthorizedError extends cli_1.CliError {
|
|
@@ -215,18 +217,19 @@ function readCachedManifest(workspaceDir) {
|
|
|
215
217
|
return undefined;
|
|
216
218
|
}
|
|
217
219
|
}
|
|
218
|
-
function writeCachedManifest(workspaceDir, manifest) {
|
|
219
|
-
const entry = {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
};
|
|
223
|
-
(0, config_1.writeSecretBearingFile)((0, node_path_1.join)(workspaceDir, MANIFEST_CACHE_FILE), `${JSON.stringify(entry, null, 2)}\n`);
|
|
220
|
+
function writeCachedManifest(workspaceDir, manifest, privateValues, fetchedAt = new Date().toISOString()) {
|
|
221
|
+
const entry = { manifest, fetchedAt };
|
|
222
|
+
const write = privateValues === "keep" ? config_1.writeSecretBearingFile : config_1.writeShareableFile;
|
|
223
|
+
write((0, node_path_1.join)(workspaceDir, MANIFEST_CACHE_FILE), `${JSON.stringify(entry, null, 2)}\n`);
|
|
224
224
|
}
|
|
225
|
-
async function resolveManifest(workspaceDir, backendUrl, cacheUse, clientUrl, bootstrapSecret) {
|
|
225
|
+
async function resolveManifest(workspaceDir, backendUrl, cacheUse, { clientUrl, bootstrapSecret, privateValues = "keep", } = {}) {
|
|
226
226
|
if (cacheUse !== "offline") {
|
|
227
227
|
try {
|
|
228
|
-
const
|
|
229
|
-
|
|
228
|
+
const served = await fetchManifest(backendUrl, clientUrl, bootstrapSecret);
|
|
229
|
+
const manifest = privateValues === "keep"
|
|
230
|
+
? served
|
|
231
|
+
: { ...served, modules: (0, private_values_1.withoutPrivateValues)(served.modules) };
|
|
232
|
+
writeCachedManifest(workspaceDir, manifest, privateValues);
|
|
230
233
|
return { manifest, fromCache: false };
|
|
231
234
|
}
|
|
232
235
|
catch (err) {
|
|
@@ -239,13 +242,13 @@ async function resolveManifest(workspaceDir, backendUrl, cacheUse, clientUrl, bo
|
|
|
239
242
|
throw err;
|
|
240
243
|
if (cacheUse === "never")
|
|
241
244
|
throw cacheNotUsed(err, backendUrl, cached);
|
|
242
|
-
return fromCachedEntry(cached);
|
|
245
|
+
return fromCachedEntry(workspaceDir, cached, privateValues);
|
|
243
246
|
}
|
|
244
247
|
}
|
|
245
248
|
const cached = readCachedManifest(workspaceDir);
|
|
246
249
|
if (!cached)
|
|
247
250
|
throw new NoCachedManifestError();
|
|
248
|
-
return fromCachedEntry(cached);
|
|
251
|
+
return fromCachedEntry(workspaceDir, cached, privateValues);
|
|
249
252
|
}
|
|
250
253
|
function cacheNotUsed(failure, backendUrl, cached) {
|
|
251
254
|
const problem = failure instanceof cli_1.CliError
|
|
@@ -261,12 +264,13 @@ function cacheNotUsed(failure, backendUrl, cached) {
|
|
|
261
264
|
],
|
|
262
265
|
}, { cause: failure });
|
|
263
266
|
}
|
|
264
|
-
function fromCachedEntry(cached) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
267
|
+
function fromCachedEntry(workspaceDir, cached, privateValues) {
|
|
268
|
+
let { manifest } = cached;
|
|
269
|
+
if (privateValues === "drop") {
|
|
270
|
+
manifest = { ...manifest, modules: (0, private_values_1.withoutPrivateValues)(manifest.modules) };
|
|
271
|
+
writeCachedManifest(workspaceDir, manifest, "drop", cached.fetchedAt);
|
|
272
|
+
}
|
|
273
|
+
return { manifest, fromCache: true, fetchedAt: cached.fetchedAt };
|
|
270
274
|
}
|
|
271
275
|
function assertCachedLayerPathsExist(modules, fetchedAt) {
|
|
272
276
|
const missing = modules.filter((mod) => !(0, node_fs_1.existsSync)(mod.path));
|