@antelopejs/dms-frontend 0.7.1 → 0.9.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 +16 -41
- package/dist/commands/build.js +9 -15
- package/dist/commands/dev-action.d.ts +2 -0
- package/dist/commands/dev-action.js +14 -3
- package/dist/commands/dev.js +3 -1
- package/dist/commands/start-action.d.ts +2 -0
- package/dist/commands/start-action.js +65 -47
- package/dist/commands/start.js +8 -6
- package/dist/common.d.ts +1 -0
- package/dist/common.js +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +9 -0
- package/dist/derived-outputs.d.ts +1 -5
- package/dist/derived-outputs.js +48 -35
- package/dist/dev-modules.d.ts +5 -0
- package/dist/dev-modules.js +88 -0
- package/dist/entry-weight.d.ts +24 -0
- package/dist/entry-weight.js +86 -0
- package/dist/fs-sync.d.ts +1 -0
- package/dist/fs-sync.js +6 -0
- package/dist/help.js +12 -4
- package/dist/index.js +4 -6
- package/dist/layer-watch.js +7 -2
- package/dist/layers.d.ts +2 -3
- package/dist/layers.js +1 -95
- package/dist/locale-files.d.ts +6 -0
- package/dist/locale-files.js +75 -0
- package/dist/manifest.d.ts +8 -8
- package/dist/manifest.js +17 -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 +3 -2
- package/dist/private-values.js +16 -5
- package/dist/project-modules-child.d.ts +13 -0
- package/dist/project-modules-child.js +26 -0
- package/dist/project-modules.d.ts +47 -0
- package/dist/project-modules.js +268 -0
- package/dist/workspace-setup.d.ts +3 -7
- package/dist/workspace-setup.js +79 -62
- package/dist/workspace.d.ts +8 -4
- package/dist/workspace.js +1 -1
- package/package.json +13 -8
- package/skills/dms-frontend/SKILL.md +2 -2
- package/templates/vue/app-runtime.ts +8 -1
- package/templates/vue/client-chunking.mjs +61 -0
- package/templates/vue/email-renderer.ts +6 -1
- package/templates/vue/entry-weight.mjs +132 -0
- package/templates/vue/frontend-build-loader.ts +1 -1
- package/templates/vue/frontend-module.ts +168 -1
- package/templates/vue/gitignore +1 -0
- package/templates/vue/main.ts +32 -2
- package/templates/vue/network-error.ts +3 -1
- package/templates/vue/package.json +3 -3
- package/templates/vue/patches/@vueuse__core@14.4.0.patch +24 -0
- package/templates/vue/pnpm-workspace.yaml +5 -0
- 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 +34 -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/in-place-render.mjs +110 -0
- 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/page-props.mjs +10 -0
- 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 +110 -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 +98 -78
- package/templates/vue/sfc-global-types.mjs +119 -0
- package/templates/vue/ssr-renderer.ts +27 -1
- package/templates/vue/ui-kbd.mjs +45 -0
- package/templates/vue/vite.config.ts +104 -47
- 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
|
@@ -9,16 +9,25 @@ import {
|
|
|
9
9
|
normalizePath,
|
|
10
10
|
type Plugin,
|
|
11
11
|
} from "vite";
|
|
12
|
+
import { clientCodeSplitting, onBuildLog } from "./client-chunking.mjs";
|
|
13
|
+
import { recordEntryWeight } from "./entry-weight.mjs";
|
|
12
14
|
import { rejectServerSourcesInClient } from "./client-server-guard.mjs";
|
|
13
15
|
import { uiInertiaPage } from "./ui-inertia-page.mjs";
|
|
16
|
+
import { uiKbd } from "./ui-kbd.mjs";
|
|
14
17
|
import { uiRequestState } from "./ui-request-state.mjs";
|
|
15
|
-
import { autoImportDirectories } from "./frontend-build-loader";
|
|
16
|
-
import { frontendBuilds } from "./frontend-builds.generated";
|
|
18
|
+
import { autoImportDirectories } from "./frontend-build-loader.ts";
|
|
19
|
+
import { frontendBuilds } from "./frontend-builds.generated.ts";
|
|
17
20
|
import { orderHeadForFirstPaint } from "./head-order.mjs";
|
|
18
21
|
import { iconScanGlobs } from "./icon-scan.mjs";
|
|
22
|
+
import {
|
|
23
|
+
sfcGlobalTypesWatcher,
|
|
24
|
+
writeSfcGlobalTypes,
|
|
25
|
+
} from "./sfc-global-types.mjs";
|
|
19
26
|
|
|
20
27
|
interface FrontendModuleRegistryEntry {
|
|
21
28
|
id: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
packageName?: string;
|
|
22
31
|
root: string;
|
|
23
32
|
priority: number;
|
|
24
33
|
}
|
|
@@ -27,7 +36,10 @@ interface FrontendModuleRegistry {
|
|
|
27
36
|
modules: FrontendModuleRegistryEntry[];
|
|
28
37
|
}
|
|
29
38
|
|
|
30
|
-
const registryPath = resolve(
|
|
39
|
+
const registryPath = resolve(
|
|
40
|
+
import.meta.dirname,
|
|
41
|
+
"generated-frontend-modules.json",
|
|
42
|
+
);
|
|
31
43
|
const registry = JSON.parse(
|
|
32
44
|
readFileSync(registryPath, "utf8"),
|
|
33
45
|
) as FrontendModuleRegistry;
|
|
@@ -65,6 +77,19 @@ const importDirectories = autoImportDirectories(
|
|
|
65
77
|
frontendBuilds,
|
|
66
78
|
(message) => logger.warn(message),
|
|
67
79
|
);
|
|
80
|
+
/**
|
|
81
|
+
* The auto-imported types, as declarations Vue's SFC compiler reads: without
|
|
82
|
+
* them a prop typed with one compiles to the wrong runtime type.
|
|
83
|
+
*/
|
|
84
|
+
const sfcGlobalTypesFile = resolve(
|
|
85
|
+
import.meta.dirname,
|
|
86
|
+
"sfc-global-types.generated.d.ts",
|
|
87
|
+
);
|
|
88
|
+
await writeSfcGlobalTypes(
|
|
89
|
+
importDirectories,
|
|
90
|
+
import.meta.dirname,
|
|
91
|
+
sfcGlobalTypesFile,
|
|
92
|
+
);
|
|
68
93
|
/**
|
|
69
94
|
* Dependencies pre-bundled by name. A module's own dependencies need no entry:
|
|
70
95
|
* the startup crawl below (`optimizerEntries`) finds them in its sources. A
|
|
@@ -109,7 +134,7 @@ const optimizedDependencies = [
|
|
|
109
134
|
* `normalizePath` is Vite's own answer to this, the identity on POSIX.
|
|
110
135
|
*/
|
|
111
136
|
const optimizerEntries = [
|
|
112
|
-
resolve(
|
|
137
|
+
resolve(import.meta.dirname, "main.ts"),
|
|
113
138
|
...moduleRoots.map((root) => resolve(root, "dms.frontend.ts")),
|
|
114
139
|
...frontendSourceRoots.flatMap((root) => [
|
|
115
140
|
resolve(root, "app/**/*.vue"),
|
|
@@ -128,7 +153,7 @@ function paintBeforeHydrate(): Plugin {
|
|
|
128
153
|
|
|
129
154
|
const uiLinkImport = "@nuxt/ui/components/Link.vue";
|
|
130
155
|
const uiInertiaLinkImport = resolve(
|
|
131
|
-
|
|
156
|
+
import.meta.dirname,
|
|
132
157
|
"node_modules/@nuxt/ui/dist/runtime/vue/overrides/inertia/Link.vue",
|
|
133
158
|
);
|
|
134
159
|
/**
|
|
@@ -149,6 +174,8 @@ export default defineConfig({
|
|
|
149
174
|
// Fails a client build that imports a dms.server.ts or a file under a
|
|
150
175
|
// module's server/ directory, however it is imported.
|
|
151
176
|
rejectServerSourcesInClient(moduleRoots),
|
|
177
|
+
// What each module adds to the client entry, which `ajs dms build` reports.
|
|
178
|
+
recordEntryWeight(registry.modules),
|
|
152
179
|
{
|
|
153
180
|
name: "dms-ui-inertia-link",
|
|
154
181
|
enforce: "pre",
|
|
@@ -156,14 +183,25 @@ export default defineConfig({
|
|
|
156
183
|
return id === uiLinkImport ? uiInertiaLinkImport : null;
|
|
157
184
|
},
|
|
158
185
|
},
|
|
186
|
+
uiKbd(isNuxtUiFile),
|
|
159
187
|
uiInertiaPage(isNuxtUiFile),
|
|
160
188
|
uiRequestState(isNuxtUiFile),
|
|
161
|
-
vue(),
|
|
189
|
+
vue({ script: { globalTypeFiles: [sfcGlobalTypesFile] } }),
|
|
190
|
+
sfcGlobalTypesWatcher(
|
|
191
|
+
importDirectories,
|
|
192
|
+
import.meta.dirname,
|
|
193
|
+
sfcGlobalTypesFile,
|
|
194
|
+
),
|
|
162
195
|
ui({
|
|
163
196
|
router: "inertia",
|
|
164
197
|
icon: {
|
|
165
198
|
clientBundle: {
|
|
166
|
-
scan: {
|
|
199
|
+
scan: {
|
|
200
|
+
globInclude: iconScanGlobs(
|
|
201
|
+
import.meta.dirname,
|
|
202
|
+
frontendSourceRoots,
|
|
203
|
+
),
|
|
204
|
+
},
|
|
167
205
|
},
|
|
168
206
|
},
|
|
169
207
|
components: { dirs: [] },
|
|
@@ -179,42 +217,45 @@ export default defineConfig({
|
|
|
179
217
|
// backslashes intact, where `\U`, `\M` and `\f` are read as escape
|
|
180
218
|
// sequences: the specifier the bundler then resolves is a mangled
|
|
181
219
|
// path that cannot exist. Keep it POSIX.
|
|
182
|
-
[normalizePath(resolve(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
220
|
+
[normalizePath(resolve(import.meta.dirname, "frontend-module.ts"))]:
|
|
221
|
+
[
|
|
222
|
+
"$fetch",
|
|
223
|
+
"abortDmsNavigation",
|
|
224
|
+
"clearDmsError",
|
|
225
|
+
"createDmsError",
|
|
226
|
+
"dmsRequestKey",
|
|
227
|
+
"defineDmsAppConfig",
|
|
228
|
+
"defineDmsPlugin",
|
|
229
|
+
"defineDmsMiddleware",
|
|
230
|
+
"defineDmsPageMeta",
|
|
231
|
+
"navigateDms",
|
|
232
|
+
"preloadDmsComponents",
|
|
233
|
+
"refreshDmsData",
|
|
234
|
+
"refreshDmsPageData",
|
|
235
|
+
"setDmsAsyncDataScope",
|
|
236
|
+
"showDmsError",
|
|
237
|
+
"useDmsAsyncData",
|
|
238
|
+
"useDmsData",
|
|
239
|
+
"useDmsCookie",
|
|
240
|
+
"useDmsFetch",
|
|
241
|
+
"useHead",
|
|
242
|
+
"useI18n",
|
|
243
|
+
"useDmsError",
|
|
244
|
+
"useDmsPagedData",
|
|
245
|
+
"useDmsPageLoading",
|
|
246
|
+
"isDmsHydrating",
|
|
247
|
+
"useDmsApp",
|
|
248
|
+
"useDmsRoute",
|
|
249
|
+
"useDmsRouter",
|
|
250
|
+
"useDmsRuntimeConfig",
|
|
251
|
+
"useSeoMeta",
|
|
252
|
+
"useDmsState",
|
|
253
|
+
"useDmsAppConfig",
|
|
254
|
+
"DmsClientOnly",
|
|
255
|
+
"DmsLink",
|
|
256
|
+
"useDmsUserSession",
|
|
257
|
+
"provideDmsKbdPlatform",
|
|
258
|
+
],
|
|
218
259
|
},
|
|
219
260
|
],
|
|
220
261
|
dts: "auto-imports.d.ts",
|
|
@@ -228,13 +269,16 @@ export default defineConfig({
|
|
|
228
269
|
// Nuxt UI's `Icon` must share one copy for the same-origin provider to apply.
|
|
229
270
|
dedupe: ["vue", "reka-ui", "@nuxt/ui", "@vueuse/core", "@iconify/vue"],
|
|
230
271
|
alias: {
|
|
231
|
-
"#dms/frontend-module": resolve(
|
|
272
|
+
"#dms/frontend-module": resolve(
|
|
273
|
+
import.meta.dirname,
|
|
274
|
+
"frontend-module.ts",
|
|
275
|
+
),
|
|
232
276
|
// For the server's development loader only: no browser module imports
|
|
233
277
|
// a `dms.server.ts`.
|
|
234
|
-
"#dms/server": resolve(
|
|
278
|
+
"#dms/server": resolve(import.meta.dirname, "server-module.ts"),
|
|
235
279
|
"#build/nuxt-icon-client-bundle": "virtual:nuxt-ui-icons",
|
|
236
280
|
"#shortcuts-aggregated": resolve(
|
|
237
|
-
|
|
281
|
+
import.meta.dirname,
|
|
238
282
|
"shortcuts-aggregated.generated.ts",
|
|
239
283
|
),
|
|
240
284
|
...stableModuleAliases,
|
|
@@ -252,5 +296,18 @@ export default defineConfig({
|
|
|
252
296
|
},
|
|
253
297
|
ssr: { noExternal: ["@nuxt/icon", "@nuxt/ui"] },
|
|
254
298
|
server: { strictPort: true, allowedHosts: [".onamp.dev"] },
|
|
255
|
-
build: {
|
|
299
|
+
build: {
|
|
300
|
+
manifest: true,
|
|
301
|
+
outDir: "dist/client",
|
|
302
|
+
rolldownOptions: { onLog: onBuildLog },
|
|
303
|
+
},
|
|
304
|
+
// The browser bundle only: the server bundles are single Node programs, for
|
|
305
|
+
// which a file count means nothing.
|
|
306
|
+
environments: {
|
|
307
|
+
client: {
|
|
308
|
+
build: {
|
|
309
|
+
rolldownOptions: { output: { codeSplitting: clientCodeSplitting } },
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
256
313
|
});
|
|
@@ -14,7 +14,10 @@ interface FrontendModuleRegistry {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const registry = JSON.parse(
|
|
17
|
-
readFileSync(
|
|
17
|
+
readFileSync(
|
|
18
|
+
resolve(import.meta.dirname, "generated-frontend-modules.json"),
|
|
19
|
+
"utf8",
|
|
20
|
+
),
|
|
18
21
|
) as FrontendModuleRegistry;
|
|
19
22
|
const moduleAliases = Object.fromEntries(
|
|
20
23
|
registry.modules.map((module) => [`@frontend/${module.id}`, module.root]),
|
|
@@ -42,7 +45,7 @@ export default defineConfig({
|
|
|
42
45
|
{
|
|
43
46
|
name: "dms-email-locale-assets",
|
|
44
47
|
buildStart() {
|
|
45
|
-
const directory = resolve(
|
|
48
|
+
const directory = resolve(import.meta.dirname, "locales.generated");
|
|
46
49
|
for (const file of readdirSync(directory).filter((name) =>
|
|
47
50
|
name.endsWith(".json"),
|
|
48
51
|
)) {
|
|
@@ -66,7 +69,7 @@ export default defineConfig({
|
|
|
66
69
|
// Inlined verbatim as a module specifier in the generated import, so
|
|
67
70
|
// it has to be POSIX: a native Windows path would reach the parser
|
|
68
71
|
// with its backslashes read as string escapes. See vite.config.ts.
|
|
69
|
-
[normalizePath(resolve(
|
|
72
|
+
[normalizePath(resolve(import.meta.dirname, "email-runtime.ts"))]: [
|
|
70
73
|
"useDmsAppConfig",
|
|
71
74
|
"useDmsRuntimeConfig",
|
|
72
75
|
"defineDmsAppConfig",
|
|
@@ -12,7 +12,10 @@ interface FrontendModuleRegistry {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const registry = JSON.parse(
|
|
15
|
-
readFileSync(
|
|
15
|
+
readFileSync(
|
|
16
|
+
resolve(import.meta.dirname, "generated-frontend-modules.json"),
|
|
17
|
+
"utf8",
|
|
18
|
+
),
|
|
16
19
|
) as FrontendModuleRegistry;
|
|
17
20
|
const moduleAliases = Object.fromEntries(
|
|
18
21
|
registry.modules.map((module) => [`@frontend/${module.id}`, module.root]),
|
|
@@ -44,7 +47,7 @@ export default defineConfig({
|
|
|
44
47
|
logLevel,
|
|
45
48
|
resolve: {
|
|
46
49
|
alias: {
|
|
47
|
-
"#dms/server": resolve(
|
|
50
|
+
"#dms/server": resolve(import.meta.dirname, "server-module.ts"),
|
|
48
51
|
...stableModuleAliases,
|
|
49
52
|
...moduleAliases,
|
|
50
53
|
},
|
|
@@ -1,53 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -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 that is waking from sleep or still starting.
|
|
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; a DMS backend that listens before it is ready
|
|
6
|
-
// answers 503 with a Retry-After too, without the header. A 503 with a
|
|
7
|
-
// Retry-After in whole seconds is the signal, with or without the header; a
|
|
8
|
-
// 503 without one goes out as it came. Only a GET or a HEAD is repeated; a
|
|
9
|
-
// POST, PATCH or DELETE is sent once.
|
|
10
|
-
//
|
|
11
|
-
// The wait starts with the first waitable 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 a platform marks the 503 of a backend it is waking with. */
|
|
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
|
-
/**
|
|
38
|
-
* Whether `response` is a 503 asking to come back after a Retry-After in
|
|
39
|
-
* whole seconds: a platform waking the backend (with the cold-start header)
|
|
40
|
-
* or a backend still starting.
|
|
41
|
-
*/
|
|
42
|
-
export function isWaitable(response) {
|
|
43
|
-
return (
|
|
44
|
-
response.status === UNAVAILABLE && WHOLE_SECONDS.test(retryAfter(response))
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function retryAfter(response) {
|
|
49
|
-
return (response.headers.get("retry-after") ?? "").trim();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* How long a request may wait for a waking backend, from
|
|
54
|
-
* DMS_BACKEND_WAKE_BUDGET_MS. 0 turns the wait off; anything but a whole
|
|
55
|
-
* number of milliseconds keeps the default.
|
|
56
|
-
*/
|
|
57
|
-
export function wakeBudgetMs(env = process.env) {
|
|
58
|
-
const raw = (env.DMS_BACKEND_WAKE_BUDGET_MS ?? "").trim();
|
|
59
|
-
return WHOLE_MILLISECONDS.test(raw) ? Number(raw) : DEFAULT_BUDGET_MS;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** The pause a waitable answer's Retry-After asks for, kept between bounds. */
|
|
63
|
-
function retryDelayMs(response) {
|
|
64
|
-
return Math.min(
|
|
65
|
-
MAX_DELAY_MS,
|
|
66
|
-
Math.max(MIN_DELAY_MS, Number(retryAfter(response)) * MS_PER_SECOND),
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* What the client gets once the budget is spent: the last waitable answer's
|
|
72
|
-
* status, Retry-After and cold-start header if it had one, without the body
|
|
73
|
-
* its pause already discarded.
|
|
74
|
-
*/
|
|
75
|
-
function expired(cold) {
|
|
76
|
-
const headers = { "retry-after": retryAfter(cold) };
|
|
77
|
-
if (cold.headers.get(COLD_START_HEADER) === COLD_START_VALUE)
|
|
78
|
-
headers[COLD_START_HEADER] = COLD_START_VALUE;
|
|
79
|
-
return new Response(null, { status: UNAVAILABLE, headers });
|
|
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 waitable 503s 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 the 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; isWaitable(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
|
-
}
|