@antelopejs/dms-frontend 0.0.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/LICENSE +190 -0
- package/README.md +131 -0
- package/dist/commands/build.js +66 -0
- package/dist/commands/clean.js +42 -0
- package/dist/commands/dev.js +116 -0
- package/dist/commands/prepare.js +60 -0
- package/dist/commands/start.js +49 -0
- package/dist/commands/verify-source.js +39 -0
- package/dist/common.js +24 -0
- package/dist/config.js +142 -0
- package/dist/discovery.js +123 -0
- package/dist/fs-sync.js +142 -0
- package/dist/index.js +44 -0
- package/dist/layer-watch.js +154 -0
- package/dist/layers.js +120 -0
- package/dist/manifest.js +109 -0
- package/dist/materialize.js +249 -0
- package/dist/ports.js +30 -0
- package/dist/update-check.js +173 -0
- package/dist/utils/cli-ui.js +178 -0
- package/dist/verify-source-runner.js +228 -0
- package/dist/workspace-setup.js +109 -0
- package/dist/workspace.js +76 -0
- package/package.json +97 -0
- package/templates/vue/DmsDynamicPage.vue +89 -0
- package/templates/vue/app-config-stub.mjs +1 -0
- package/templates/vue/app-runtime.ts +240 -0
- package/templates/vue/compress-assets.mjs +48 -0
- package/templates/vue/email-locales.ts +32 -0
- package/templates/vue/email-renderer.ts +159 -0
- package/templates/vue/email-runtime.ts +23 -0
- package/templates/vue/frontend-module.ts +1418 -0
- package/templates/vue/globals.d.ts +1 -0
- package/templates/vue/index.html +24 -0
- package/templates/vue/main.ts +33 -0
- package/templates/vue/npmrc +2 -0
- package/templates/vue/package.json +35 -0
- package/templates/vue/pnpm-workspace.yaml +4 -0
- package/templates/vue/server/auth/backend.mjs +83 -0
- package/templates/vue/server/auth/client-ip.mjs +52 -0
- package/templates/vue/server/auth/oauth.mjs +213 -0
- package/templates/vue/server/auth/routes.mjs +254 -0
- package/templates/vue/server/auth/session.mjs +180 -0
- package/templates/vue/server/client-manifest.mjs +116 -0
- package/templates/vue/server/email.mjs +36 -0
- package/templates/vue/server/inertia.mjs +79 -0
- package/templates/vue/server/render-token.mjs +81 -0
- package/templates/vue/server/tester.mjs +228 -0
- package/templates/vue/server.mjs +526 -0
- package/templates/vue/ssr-renderer.ts +146 -0
- package/templates/vue/tsconfig.json +31 -0
- package/templates/vue/typecheck-loader.mjs +13 -0
- package/templates/vue/vite.config.ts +161 -0
- package/templates/vue/vite.email.config.ts +77 -0
package/dist/layers.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertLayerPathsServed = assertLayerPathsServed;
|
|
4
|
+
exports.assertCachedArchivesExist = assertCachedArchivesExist;
|
|
5
|
+
exports.buildLayersFromPaths = buildLayersFromPaths;
|
|
6
|
+
exports.downloadAndExtractLayers = downloadAndExtractLayers;
|
|
7
|
+
exports.buildLayersFromCache = buildLayersFromCache;
|
|
8
|
+
exports.getLayerSafeName = getLayerSafeName;
|
|
9
|
+
exports.getLayerWorkspacePath = getLayerWorkspacePath;
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const promises_1 = require("node:fs/promises");
|
|
12
|
+
const node_path_1 = require("node:path");
|
|
13
|
+
const node_stream_1 = require("node:stream");
|
|
14
|
+
const consumers_1 = require("node:stream/consumers");
|
|
15
|
+
const unzipper_1 = require("unzipper");
|
|
16
|
+
const manifest_1 = require("./manifest");
|
|
17
|
+
const workspace_1 = require("./workspace");
|
|
18
|
+
const fs_sync_1 = require("./fs-sync");
|
|
19
|
+
const config_1 = require("./config");
|
|
20
|
+
function assertLayerPathsServed(modules) {
|
|
21
|
+
const pathless = modules.filter((mod) => !mod.path);
|
|
22
|
+
if (pathless.length === 0)
|
|
23
|
+
return;
|
|
24
|
+
throw new Error("The backend served a manifest without layer source paths:\n" +
|
|
25
|
+
pathless.map((mod) => ` - ${mod.name}`).join("\n") +
|
|
26
|
+
"\n`ajs-dms dev` needs a development backend running on this machine (started with " +
|
|
27
|
+
"`ajs project dev`); use `ajs-dms build` against a remote or production one.\n" +
|
|
28
|
+
"If the backend is local and in development mode, it did not recognize the bootstrap " +
|
|
29
|
+
"credential — see DMS_BOOTSTRAP_SECRET.");
|
|
30
|
+
}
|
|
31
|
+
function assertCachedArchivesExist(modules, cacheDir, fetchedAt) {
|
|
32
|
+
const present = new Set((0, node_fs_1.readdirSync)(cacheDir));
|
|
33
|
+
const missing = modules.filter((mod) => !present.has(mod.archiveName));
|
|
34
|
+
if (missing.length === 0)
|
|
35
|
+
return;
|
|
36
|
+
throw new Error(`Cached layers archive is missing entries listed in the manifest${fetchedAt ? ` (fetched ${fetchedAt})` : ""}:\n` +
|
|
37
|
+
missing.map((mod) => ` - ${mod.name}: ${mod.archiveName}`).join("\n") +
|
|
38
|
+
"\nRun once with the backend reachable to re-download the layers.");
|
|
39
|
+
}
|
|
40
|
+
function sortModulesByPriority(modules) {
|
|
41
|
+
return [...modules].sort((a, b) => b.priority - a.priority);
|
|
42
|
+
}
|
|
43
|
+
function readLayerPackageName(layerPath) {
|
|
44
|
+
const pkgPath = (0, node_path_1.join)(layerPath, "package.json");
|
|
45
|
+
if (!(0, node_fs_1.existsSync)(pkgPath))
|
|
46
|
+
return undefined;
|
|
47
|
+
try {
|
|
48
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)(pkgPath, "utf-8"));
|
|
49
|
+
return typeof pkg.name === "string" ? pkg.name : undefined;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function resolveLayer(layerPath, mod) {
|
|
56
|
+
return {
|
|
57
|
+
path: layerPath,
|
|
58
|
+
packageName: readLayerPackageName(layerPath),
|
|
59
|
+
priority: mod.priority,
|
|
60
|
+
configKey: mod.configKey,
|
|
61
|
+
options: mod.options,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function buildLayersFromPaths(modules) {
|
|
65
|
+
assertLayerPathsServed(modules);
|
|
66
|
+
return sortModulesByPriority(modules).map((mod) => resolveLayer(mod.path, mod));
|
|
67
|
+
}
|
|
68
|
+
async function downloadAndExtractLayers(backendUrl, packUrl, cacheDir, force, bootstrapSecret) {
|
|
69
|
+
const url = `${backendUrl}${packUrl}`;
|
|
70
|
+
const response = await fetch(url, {
|
|
71
|
+
headers: (0, workspace_1.bootstrapHeaders)(bootstrapSecret),
|
|
72
|
+
});
|
|
73
|
+
if ((0, workspace_1.isUnauthorized)(response.status)) {
|
|
74
|
+
throw new manifest_1.ManifestUnauthorizedError(url, response.status);
|
|
75
|
+
}
|
|
76
|
+
if (!response.ok || !response.body) {
|
|
77
|
+
throw new Error("Failed to download layers archive");
|
|
78
|
+
}
|
|
79
|
+
const modulesBlob = await (0, consumers_1.buffer)(node_stream_1.Readable.fromWeb(response.body));
|
|
80
|
+
const cacheExists = (0, node_fs_1.existsSync)(cacheDir);
|
|
81
|
+
if (force || !cacheExists) {
|
|
82
|
+
if (cacheExists) {
|
|
83
|
+
(0, node_fs_1.rmSync)(cacheDir, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
await (await unzipper_1.Open.buffer(modulesBlob)).extract({ path: cacheDir });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const tempDir = `${cacheDir}_temp`;
|
|
89
|
+
if ((0, node_fs_1.existsSync)(tempDir)) {
|
|
90
|
+
(0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
await (await unzipper_1.Open.buffer(modulesBlob)).extract({ path: tempDir });
|
|
93
|
+
const tempEntries = (0, node_fs_1.readdirSync)(tempDir);
|
|
94
|
+
for (const entry of tempEntries) {
|
|
95
|
+
(0, fs_sync_1.syncDirectories)((0, node_path_1.join)(tempDir, entry), (0, node_path_1.join)(cacheDir, entry));
|
|
96
|
+
}
|
|
97
|
+
(0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
|
|
98
|
+
}
|
|
99
|
+
async function buildLayersFromCache(modules, cacheDir) {
|
|
100
|
+
if (!(0, node_fs_1.existsSync)(cacheDir))
|
|
101
|
+
return [];
|
|
102
|
+
const layerFiles = await (0, promises_1.readdir)(cacheDir);
|
|
103
|
+
const layers = [];
|
|
104
|
+
for (const mod of sortModulesByPriority(modules)) {
|
|
105
|
+
if (!layerFiles.includes(mod.archiveName))
|
|
106
|
+
continue;
|
|
107
|
+
const layerPath = (0, node_path_1.join)(cacheDir, mod.archiveName);
|
|
108
|
+
layers.push(resolveLayer(layerPath, mod));
|
|
109
|
+
}
|
|
110
|
+
return layers;
|
|
111
|
+
}
|
|
112
|
+
function getLayerSafeName(layer) {
|
|
113
|
+
if (layer.packageName) {
|
|
114
|
+
return layer.packageName.replace(/^@/, "").replace(/\//g, "__");
|
|
115
|
+
}
|
|
116
|
+
return (0, node_path_1.basename)(layer.path);
|
|
117
|
+
}
|
|
118
|
+
function getLayerWorkspacePath(workspaceDir, layer) {
|
|
119
|
+
return (0, node_path_1.join)(workspaceDir, config_1.LAYERS_SUBDIR, getLayerSafeName(layer));
|
|
120
|
+
}
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ManifestUnauthorizedError = void 0;
|
|
4
|
+
exports.fetchManifest = fetchManifest;
|
|
5
|
+
exports.readCachedManifest = readCachedManifest;
|
|
6
|
+
exports.resolveManifest = resolveManifest;
|
|
7
|
+
exports.assertCachedLayerPathsExist = assertCachedLayerPathsExist;
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
const workspace_1 = require("./workspace");
|
|
12
|
+
class ManifestUnauthorizedError extends Error {
|
|
13
|
+
constructor(url, status) {
|
|
14
|
+
super(`Backend refused the layers request (${status}) at ${url}.\n` +
|
|
15
|
+
" This DMS backend requires a bootstrap credential for its layer endpoints.\n" +
|
|
16
|
+
" • Production/CI: set DMS_BOOTSTRAP_SECRET to the backend's frontend.bootstrapSecret.\n" +
|
|
17
|
+
" • Local dev: run inside the antelope project started with `ajs project dev` —\n" +
|
|
18
|
+
" the credential is read from .antelope/dms-dev.json automatically.");
|
|
19
|
+
this.url = url;
|
|
20
|
+
this.status = status;
|
|
21
|
+
this.name = "ManifestUnauthorizedError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.ManifestUnauthorizedError = ManifestUnauthorizedError;
|
|
25
|
+
async function fetchManifest(backendUrl, clientUrl, bootstrapSecret) {
|
|
26
|
+
const frontendUrl = new URL(`${backendUrl}/dms/frontend`);
|
|
27
|
+
frontendUrl.searchParams.set("renderer", "vue");
|
|
28
|
+
frontendUrl.searchParams.set("rendererVersion", "3");
|
|
29
|
+
if (clientUrl) {
|
|
30
|
+
frontendUrl.searchParams.set("clientUrl", clientUrl);
|
|
31
|
+
}
|
|
32
|
+
const response = await fetch(frontendUrl, {
|
|
33
|
+
headers: (0, workspace_1.bootstrapHeaders)(bootstrapSecret),
|
|
34
|
+
});
|
|
35
|
+
if ((0, workspace_1.isUnauthorized)(response.status)) {
|
|
36
|
+
throw new ManifestUnauthorizedError(frontendUrl.href, response.status);
|
|
37
|
+
}
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`Failed to fetch manifest from ${frontendUrl.href} (${response.status})`);
|
|
40
|
+
}
|
|
41
|
+
const manifest = (await response.json());
|
|
42
|
+
if (manifest.version !== workspace_1.FRONTEND_MANIFEST_VERSION) {
|
|
43
|
+
throw new Error(`Unsupported frontend manifest version: ${String(manifest.version)}`);
|
|
44
|
+
}
|
|
45
|
+
const incompatible = manifest.modules.filter((module) => module.renderer?.name !== "vue" || module.renderer?.version !== "3");
|
|
46
|
+
if (incompatible.length) {
|
|
47
|
+
throw new Error(`Frontend manifest contains incompatible renderer modules: ${incompatible.map((module) => module.name).join(", ")}`);
|
|
48
|
+
}
|
|
49
|
+
return { pack: manifest.archive, modules: manifest.modules };
|
|
50
|
+
}
|
|
51
|
+
const MANIFEST_CACHE_FILE = ".manifest-cache.json";
|
|
52
|
+
function readCachedManifest(workspaceDir) {
|
|
53
|
+
const file = (0, node_path_1.join)(workspaceDir, MANIFEST_CACHE_FILE);
|
|
54
|
+
if (!(0, node_fs_1.existsSync)(file))
|
|
55
|
+
return undefined;
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(file, "utf-8"));
|
|
58
|
+
if (!parsed?.manifest)
|
|
59
|
+
return undefined;
|
|
60
|
+
return parsed;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function writeCachedManifest(workspaceDir, manifest) {
|
|
67
|
+
const entry = {
|
|
68
|
+
manifest,
|
|
69
|
+
fetchedAt: new Date().toISOString(),
|
|
70
|
+
};
|
|
71
|
+
(0, config_1.writeSecretBearingFile)((0, node_path_1.join)(workspaceDir, MANIFEST_CACHE_FILE), `${JSON.stringify(entry, null, 2)}\n`);
|
|
72
|
+
}
|
|
73
|
+
async function resolveManifest(workspaceDir, backendUrl, offline, clientUrl, bootstrapSecret) {
|
|
74
|
+
if (!offline) {
|
|
75
|
+
try {
|
|
76
|
+
const manifest = await fetchManifest(backendUrl, clientUrl, bootstrapSecret);
|
|
77
|
+
writeCachedManifest(workspaceDir, manifest);
|
|
78
|
+
return { manifest, fromCache: false };
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
if (err instanceof ManifestUnauthorizedError)
|
|
82
|
+
throw err;
|
|
83
|
+
const cached = readCachedManifest(workspaceDir);
|
|
84
|
+
if (!cached)
|
|
85
|
+
throw err;
|
|
86
|
+
return fromCachedEntry(cached);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const cached = readCachedManifest(workspaceDir);
|
|
90
|
+
if (!cached) {
|
|
91
|
+
throw new Error("No cached manifest for this workspace — run once with the backend reachable before using --offline.");
|
|
92
|
+
}
|
|
93
|
+
return fromCachedEntry(cached);
|
|
94
|
+
}
|
|
95
|
+
function fromCachedEntry(cached) {
|
|
96
|
+
return {
|
|
97
|
+
manifest: cached.manifest,
|
|
98
|
+
fromCache: true,
|
|
99
|
+
fetchedAt: cached.fetchedAt,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function assertCachedLayerPathsExist(modules, fetchedAt) {
|
|
103
|
+
const missing = modules.filter((mod) => !(0, node_fs_1.existsSync)(mod.path));
|
|
104
|
+
if (missing.length === 0)
|
|
105
|
+
return;
|
|
106
|
+
throw new Error(`Cached manifest${fetchedAt ? ` (fetched ${fetchedAt})` : ""} references layer paths that no longer exist:\n` +
|
|
107
|
+
missing.map((mod) => ` - ${mod.name}: ${mod.path}`).join("\n") +
|
|
108
|
+
"\nStart the backend and re-run to refresh the cache.");
|
|
109
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPackageRoot = getPackageRoot;
|
|
4
|
+
exports.copyStaticTemplates = copyStaticTemplates;
|
|
5
|
+
exports.writeWorkspacePackageJson = writeWorkspacePackageJson;
|
|
6
|
+
exports.materializeLayers = materializeLayers;
|
|
7
|
+
exports.createFrontendModuleRegistry = createFrontendModuleRegistry;
|
|
8
|
+
exports.writeFrontendModuleRegistry = writeFrontendModuleRegistry;
|
|
9
|
+
exports.writeDmsMainCss = writeDmsMainCss;
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_path_1 = require("node:path");
|
|
12
|
+
const config_1 = require("./config");
|
|
13
|
+
const layers_1 = require("./layers");
|
|
14
|
+
const fs_sync_1 = require("./fs-sync");
|
|
15
|
+
function getPackageRoot() {
|
|
16
|
+
return (0, node_path_1.resolve)(__dirname, "..");
|
|
17
|
+
}
|
|
18
|
+
function getTemplateDir() {
|
|
19
|
+
return (0, node_path_1.join)(getPackageRoot(), "templates", "vue");
|
|
20
|
+
}
|
|
21
|
+
function copyTemplateFile(sourceName, destName, workspaceDir) {
|
|
22
|
+
const src = (0, node_path_1.join)(getTemplateDir(), sourceName);
|
|
23
|
+
if (!(0, node_fs_1.existsSync)(src)) {
|
|
24
|
+
throw new Error(`Template not found: ${src}`);
|
|
25
|
+
}
|
|
26
|
+
const destination = (0, node_path_1.join)(workspaceDir, destName);
|
|
27
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(destination), { recursive: true });
|
|
28
|
+
if ((0, node_fs_1.statSync)(src).isDirectory()) {
|
|
29
|
+
(0, node_fs_1.cpSync)(src, destination, { recursive: true });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
(0, node_fs_1.copyFileSync)(src, destination);
|
|
33
|
+
}
|
|
34
|
+
function copyStaticTemplates(workspaceDir) {
|
|
35
|
+
for (const [sourceName, destName] of config_1.TEMPLATE_FILES) {
|
|
36
|
+
copyTemplateFile(sourceName, destName, workspaceDir);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function writeWorkspacePackageJson(workspaceDir, layers) {
|
|
40
|
+
const templatePath = (0, node_path_1.join)(getTemplateDir(), "package.json");
|
|
41
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)(templatePath, "utf-8"));
|
|
42
|
+
pkg.dependencies ??= {};
|
|
43
|
+
for (const layer of layers) {
|
|
44
|
+
if (!layer.packageName)
|
|
45
|
+
continue;
|
|
46
|
+
pkg.dependencies[layer.packageName] = "workspace:*";
|
|
47
|
+
}
|
|
48
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
|
|
49
|
+
}
|
|
50
|
+
function materializeLayers(workspaceDir, layers) {
|
|
51
|
+
const layersRoot = (0, node_path_1.join)(workspaceDir, config_1.LAYERS_SUBDIR);
|
|
52
|
+
if ((0, node_fs_1.existsSync)(layersRoot)) {
|
|
53
|
+
(0, node_fs_1.rmSync)(layersRoot, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
(0, node_fs_1.mkdirSync)(layersRoot, { recursive: true });
|
|
56
|
+
for (const layer of layers) {
|
|
57
|
+
if (!layer.packageName)
|
|
58
|
+
continue;
|
|
59
|
+
const dest = (0, layers_1.getLayerWorkspacePath)(workspaceDir, layer);
|
|
60
|
+
copyLayerSource(layer.path, dest);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function copyLayerSource(src, dest) {
|
|
64
|
+
(0, node_fs_1.cpSync)(src, dest, {
|
|
65
|
+
recursive: true,
|
|
66
|
+
dereference: true,
|
|
67
|
+
filter: (srcPath) => !(0, fs_sync_1.isBlocklistedCopyPath)(src, srcPath),
|
|
68
|
+
});
|
|
69
|
+
sanitizeLayerPackage((0, node_path_1.join)(dest, "package.json"));
|
|
70
|
+
}
|
|
71
|
+
function sanitizeLayerPackage(pkgPath) {
|
|
72
|
+
if (!(0, node_fs_1.existsSync)(pkgPath))
|
|
73
|
+
return;
|
|
74
|
+
(0, fs_sync_1.applyContent)((0, fs_sync_1.sanitizedPackageContent)((0, node_fs_1.readFileSync)(pkgPath, "utf-8")), pkgPath);
|
|
75
|
+
}
|
|
76
|
+
const GENERATED_MODULE_ID = /^[A-Za-z0-9_-]+$/;
|
|
77
|
+
function validateFrontendModuleRegistry(registry, workspaceDir) {
|
|
78
|
+
const ids = new Set();
|
|
79
|
+
const moduleRoot = `${(0, node_path_1.resolve)(workspaceDir, config_1.LAYERS_SUBDIR)}${node_path_1.sep}`;
|
|
80
|
+
for (const module of registry.modules) {
|
|
81
|
+
if (!GENERATED_MODULE_ID.test(module.id) || ids.has(module.id))
|
|
82
|
+
throw new Error(`Invalid or duplicate frontend module id: ${module.id}`);
|
|
83
|
+
if (!Number.isFinite(module.priority))
|
|
84
|
+
throw new Error(`Invalid frontend module priority: ${module.id}`);
|
|
85
|
+
if (module.entry !== undefined && module.entry !== config_1.FRONTEND_MODULE_ENTRY)
|
|
86
|
+
throw new Error(`Invalid frontend module entry: ${module.id}`);
|
|
87
|
+
if (!(0, node_path_1.resolve)(module.root).startsWith(moduleRoot))
|
|
88
|
+
throw new Error(`Frontend module root escapes the workspace: ${module.id}`);
|
|
89
|
+
ids.add(module.id);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function createFrontendModuleRegistry(workspaceDir, layers) {
|
|
93
|
+
const modules = layers.map((layer) => {
|
|
94
|
+
const sourceEntry = (0, node_path_1.join)(layer.path, config_1.FRONTEND_MODULE_ENTRY);
|
|
95
|
+
const options = layer.options ?? {};
|
|
96
|
+
return {
|
|
97
|
+
id: (0, layers_1.getLayerSafeName)(layer),
|
|
98
|
+
packageName: layer.packageName,
|
|
99
|
+
root: (0, layers_1.getLayerWorkspacePath)(workspaceDir, layer),
|
|
100
|
+
priority: layer.priority ?? 0,
|
|
101
|
+
entry: (0, node_fs_1.existsSync)(sourceEntry) ? config_1.FRONTEND_MODULE_ENTRY : undefined,
|
|
102
|
+
options: layer.configKey ? { [layer.configKey]: options } : options,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
modules.sort((left, right) => right.priority - left.priority || left.id.localeCompare(right.id));
|
|
106
|
+
const registry = { modules };
|
|
107
|
+
validateFrontendModuleRegistry(registry, workspaceDir);
|
|
108
|
+
return registry;
|
|
109
|
+
}
|
|
110
|
+
function writeFrontendModuleRegistry(workspaceDir, layers) {
|
|
111
|
+
const registry = createFrontendModuleRegistry(workspaceDir, layers);
|
|
112
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, "generated-frontend-modules.json"), `${JSON.stringify(registry, null, 2)}\n`);
|
|
113
|
+
writeFrontendTypePaths(workspaceDir, registry);
|
|
114
|
+
writeFrontendModuleLoader(workspaceDir, registry);
|
|
115
|
+
writeLocaleMessages(workspaceDir, registry);
|
|
116
|
+
writeShortcutExports(workspaceDir, registry);
|
|
117
|
+
writePublicAssets(workspaceDir, registry);
|
|
118
|
+
}
|
|
119
|
+
function writeFrontendTypePaths(workspaceDir, registry) {
|
|
120
|
+
const paths = {
|
|
121
|
+
"#dms-inertia/frontend-module": ["./frontend-module.ts"],
|
|
122
|
+
"@frontend/*": ["./frontend-modules/*"],
|
|
123
|
+
};
|
|
124
|
+
[...registry.modules].reverse().forEach((module) => {
|
|
125
|
+
const layersRoot = (0, node_path_1.join)(module.root, "layers");
|
|
126
|
+
if (!(0, node_fs_1.existsSync)(layersRoot))
|
|
127
|
+
return;
|
|
128
|
+
(0, node_fs_1.readdirSync)(layersRoot).forEach((name) => {
|
|
129
|
+
paths[`#${name}/*`] = [
|
|
130
|
+
`./frontend-modules/${module.id}/layers/${name}/*`,
|
|
131
|
+
];
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
const config = { compilerOptions: { paths } };
|
|
135
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, "frontend-paths.generated.json"), `${JSON.stringify(config, null, 2)}\n`);
|
|
136
|
+
}
|
|
137
|
+
function writeFrontendModuleLoader(workspaceDir, registry) {
|
|
138
|
+
const modules = registry.modules.filter((module) => module.entry);
|
|
139
|
+
const imports = modules
|
|
140
|
+
.map((module, index) => `import module${index} from "@frontend/${module.id}/${module.entry}";`)
|
|
141
|
+
.join("\n");
|
|
142
|
+
const registrations = modules
|
|
143
|
+
.map((module, index) => `{ module: module${index}, options: { public: ${JSON.stringify(module.options)} } }`)
|
|
144
|
+
.join(", ");
|
|
145
|
+
const content = `${imports}\n\nimport type { DmsFrontendModuleRegistration } from "./frontend-module";\n\nexport const frontendModules: DmsFrontendModuleRegistration[] = [${registrations}];\n`;
|
|
146
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, "frontend-modules.generated.ts"), content);
|
|
147
|
+
}
|
|
148
|
+
function moduleAssetRoots(moduleRoot) {
|
|
149
|
+
const layersRoot = (0, node_path_1.join)(moduleRoot, "layers");
|
|
150
|
+
if (!(0, node_fs_1.existsSync)(layersRoot)) {
|
|
151
|
+
return [{ absolutePath: moduleRoot, relativePrefix: "" }];
|
|
152
|
+
}
|
|
153
|
+
return (0, node_fs_1.readdirSync)(layersRoot).map((name) => ({
|
|
154
|
+
absolutePath: (0, node_path_1.join)(layersRoot, name),
|
|
155
|
+
relativePrefix: `layers/${name}/`,
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
function writePublicAssets(workspaceDir, registry) {
|
|
159
|
+
const destination = (0, node_path_1.join)(workspaceDir, "public");
|
|
160
|
+
(0, node_fs_1.rmSync)(destination, { force: true, recursive: true });
|
|
161
|
+
for (const module of [...registry.modules].reverse()) {
|
|
162
|
+
for (const root of moduleAssetRoots(module.root)) {
|
|
163
|
+
const source = (0, node_path_1.join)(root.absolutePath, "public");
|
|
164
|
+
if ((0, node_fs_1.existsSync)(source)) {
|
|
165
|
+
(0, node_fs_1.cpSync)(source, destination, { force: true, recursive: true });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function discoverAssets(registry, directory, extensions) {
|
|
171
|
+
return registry.modules.flatMap((module) => {
|
|
172
|
+
return moduleAssetRoots(module.root).flatMap((assetRoot) => {
|
|
173
|
+
const root = (0, node_path_1.join)(assetRoot.absolutePath, directory);
|
|
174
|
+
return (0, fs_sync_1.collectFiles)(root)
|
|
175
|
+
.filter((file) => extensions.some((extension) => file.endsWith(extension)))
|
|
176
|
+
.sort()
|
|
177
|
+
.map((relativePath) => ({
|
|
178
|
+
moduleId: module.id,
|
|
179
|
+
relativePath: `${assetRoot.relativePrefix}${(0, node_path_1.join)(directory, relativePath).split(node_path_1.sep).join("/")}`,
|
|
180
|
+
}));
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function isRecord(value) {
|
|
185
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
186
|
+
}
|
|
187
|
+
function mergeLocaleMessages(source, existing) {
|
|
188
|
+
const merged = { ...source };
|
|
189
|
+
for (const [key, value] of Object.entries(existing)) {
|
|
190
|
+
const sourceValue = merged[key];
|
|
191
|
+
merged[key] =
|
|
192
|
+
isRecord(sourceValue) && isRecord(value)
|
|
193
|
+
? mergeLocaleMessages(sourceValue, value)
|
|
194
|
+
: value;
|
|
195
|
+
}
|
|
196
|
+
return merged;
|
|
197
|
+
}
|
|
198
|
+
function readLocaleMessages(assets, registry) {
|
|
199
|
+
const roots = new Map(registry.modules.map((module) => [module.id, module.root]));
|
|
200
|
+
const messages = {};
|
|
201
|
+
for (const asset of assets) {
|
|
202
|
+
const locale = asset.relativePath.match(/(?:^|[/-])([a-z]{2})-[A-Z]{2}\.json$/)?.[1];
|
|
203
|
+
const root = roots.get(asset.moduleId);
|
|
204
|
+
if (!locale || !root)
|
|
205
|
+
continue;
|
|
206
|
+
const value = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(root, asset.relativePath), "utf8"));
|
|
207
|
+
messages[locale] = mergeLocaleMessages(value, messages[locale] ?? {});
|
|
208
|
+
}
|
|
209
|
+
return messages;
|
|
210
|
+
}
|
|
211
|
+
function writeLocaleMessages(workspaceDir, registry) {
|
|
212
|
+
const locales = discoverAssets(registry, "i18n/locales", [".json"]);
|
|
213
|
+
const messages = readLocaleMessages(locales, registry);
|
|
214
|
+
const outputDir = (0, node_path_1.join)(workspaceDir, "locales.generated");
|
|
215
|
+
(0, node_fs_1.rmSync)(outputDir, { recursive: true, force: true });
|
|
216
|
+
(0, node_fs_1.mkdirSync)(outputDir, { recursive: true });
|
|
217
|
+
for (const [locale, value] of Object.entries(messages)) {
|
|
218
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(outputDir, `${locale}.json`), JSON.stringify(value));
|
|
219
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(outputDir, `${locale}.ts`), `const messages = ${JSON.stringify(value)};\nexport default messages;\n`);
|
|
220
|
+
}
|
|
221
|
+
const supportedLocales = Object.keys(messages);
|
|
222
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, "email-locales.generated.json"), JSON.stringify(supportedLocales));
|
|
223
|
+
const loaders = supportedLocales
|
|
224
|
+
.filter((locale) => locale !== "en")
|
|
225
|
+
.map((locale) => `${JSON.stringify(locale)}: () => import(${JSON.stringify(`./locales.generated/${locale}`)})`);
|
|
226
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, "locales.generated.ts"), `import defaultMessages from "./locales.generated/en";\ninterface LocaleModule { default: Record<string, unknown>; }\ntype LocaleLoader = () => Promise<LocaleModule>;\nexport const supportedLocales = ${JSON.stringify(supportedLocales)};\nexport const localeMessages: Record<string, Record<string, unknown>> = { en: defaultMessages };\nconst localeLoaders: Record<string, LocaleLoader> = { ${loaders.join(", ")} };\nexport async function loadLocaleMessages(locale: string): Promise<Record<string, unknown>> {\n const normalized = locale.slice(0, 2);\n const existing = localeMessages[normalized];\n if (existing) return existing;\n const loader = localeLoaders[normalized];\n if (!loader) return defaultMessages;\n const messages = (await loader()).default;\n localeMessages[normalized] = messages;\n return messages;\n}\n`);
|
|
227
|
+
}
|
|
228
|
+
function writeShortcutExports(workspaceDir, registry) {
|
|
229
|
+
const registries = discoverAssets(registry, "app/config", [
|
|
230
|
+
"shortcuts-registry.ts",
|
|
231
|
+
"shortcuts-registry.js",
|
|
232
|
+
]);
|
|
233
|
+
const imports = registries.map((asset, index) => `import registry${index} from "@frontend/${asset.moduleId}/${asset.relativePath}";`);
|
|
234
|
+
const spreads = registries.map((_, index) => `...registry${index}`);
|
|
235
|
+
const content = `${imports.join("\n")}\nconst aggregatedShortcuts = [${spreads.join(", ")}];\nexport { aggregatedShortcuts };\nexport default aggregatedShortcuts;\n`;
|
|
236
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, "shortcuts-aggregated.generated.ts"), content);
|
|
237
|
+
}
|
|
238
|
+
function writeDmsMainCss(workspaceDir, layers) {
|
|
239
|
+
const sources = layers
|
|
240
|
+
.map((layer) => `@source "${(0, layers_1.getLayerWorkspacePath)(workspaceDir, layer)}/${config_1.TAILWIND_SOURCE_GLOB}";`)
|
|
241
|
+
.join("\n");
|
|
242
|
+
const content = `@import "tailwindcss";
|
|
243
|
+
@import "@nuxt/ui";
|
|
244
|
+
@plugin "@tailwindcss/typography";
|
|
245
|
+
|
|
246
|
+
${sources}
|
|
247
|
+
`;
|
|
248
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, "dms-main.css"), content);
|
|
249
|
+
}
|
package/dist/ports.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PORT_FALLBACK_RANGE = void 0;
|
|
4
|
+
exports.reserveFreePort = reserveFreePort;
|
|
5
|
+
const node_net_1 = require("node:net");
|
|
6
|
+
exports.PORT_FALLBACK_RANGE = 20;
|
|
7
|
+
const MAX_TCP_PORT = 65535;
|
|
8
|
+
function tryListen(port) {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const server = (0, node_net_1.createServer)();
|
|
11
|
+
server.unref();
|
|
12
|
+
server.once("error", () => resolve(undefined));
|
|
13
|
+
server.listen({ port }, () => resolve(server));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
async function reserveFreePort(preferred, range = exports.PORT_FALLBACK_RANGE) {
|
|
17
|
+
const end = Math.min(preferred + range, MAX_TCP_PORT);
|
|
18
|
+
for (let port = preferred; port <= end; port++) {
|
|
19
|
+
const server = await tryListen(port);
|
|
20
|
+
if (server) {
|
|
21
|
+
return {
|
|
22
|
+
port,
|
|
23
|
+
release: () => new Promise((resolve) => {
|
|
24
|
+
server.close(() => resolve());
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
throw new Error(`No free port found between ${preferred} and ${end}.`);
|
|
30
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.NO_UPDATE_CHECK_FLAG = exports.UPDATE_CHECK_TIMEOUT_MS = exports.UPDATE_CHECK_INTERVAL_MS = exports.UPDATE_CHECK_PACKAGE = void 0;
|
|
7
|
+
exports.stripUpdateCheckFlag = stripUpdateCheckFlag;
|
|
8
|
+
exports.isUpdateCheckEnabled = isUpdateCheckEnabled;
|
|
9
|
+
exports.updateCheckCacheFile = updateCheckCacheFile;
|
|
10
|
+
exports.fetchLatestVersionFromRegistry = fetchLatestVersionFromRegistry;
|
|
11
|
+
exports.checkForUpdate = checkForUpdate;
|
|
12
|
+
const node_fs_1 = require("node:fs");
|
|
13
|
+
const node_http_1 = require("node:http");
|
|
14
|
+
const node_https_1 = require("node:https");
|
|
15
|
+
const node_path_1 = require("node:path");
|
|
16
|
+
const semver_1 = __importDefault(require("semver"));
|
|
17
|
+
const config_1 = require("./config");
|
|
18
|
+
exports.UPDATE_CHECK_PACKAGE = "@antelopejs/dms-frontend";
|
|
19
|
+
const DEFAULT_REGISTRY_URL = "https://registry.npmjs.org/";
|
|
20
|
+
exports.UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
21
|
+
exports.UPDATE_CHECK_TIMEOUT_MS = 3000;
|
|
22
|
+
exports.NO_UPDATE_CHECK_FLAG = "--no-update-check";
|
|
23
|
+
const HELP_OR_VERSION_FLAGS = ["--help", "-h", "--version", "-v"];
|
|
24
|
+
const VALUE_TAKING_OPTIONS = [
|
|
25
|
+
"-b",
|
|
26
|
+
"--backend-url",
|
|
27
|
+
"-p",
|
|
28
|
+
"--port",
|
|
29
|
+
"--bootstrap-secret",
|
|
30
|
+
"-l",
|
|
31
|
+
"--layer",
|
|
32
|
+
"-m",
|
|
33
|
+
"--module",
|
|
34
|
+
"--local-package",
|
|
35
|
+
];
|
|
36
|
+
function flagIndexes(argv, flags) {
|
|
37
|
+
const terminator = argv.indexOf("--");
|
|
38
|
+
const end = terminator === -1 ? argv.length : terminator;
|
|
39
|
+
const found = [];
|
|
40
|
+
for (let index = 0; index < end; index++) {
|
|
41
|
+
if (!flags.includes(argv[index]))
|
|
42
|
+
continue;
|
|
43
|
+
if (VALUE_TAKING_OPTIONS.includes(argv[index - 1] ?? ""))
|
|
44
|
+
continue;
|
|
45
|
+
found.push(index);
|
|
46
|
+
}
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
49
|
+
function stripUpdateCheckFlag(argv) {
|
|
50
|
+
const dropped = new Set(flagIndexes(argv, [exports.NO_UPDATE_CHECK_FLAG]));
|
|
51
|
+
return argv.filter((_, index) => !dropped.has(index));
|
|
52
|
+
}
|
|
53
|
+
function isUpdateCheckEnabled(argv, env) {
|
|
54
|
+
if (env.CI)
|
|
55
|
+
return false;
|
|
56
|
+
if (env.NO_UPDATE_NOTIFIER)
|
|
57
|
+
return false;
|
|
58
|
+
if (argv[0] === "help")
|
|
59
|
+
return false;
|
|
60
|
+
if (flagIndexes(argv, [exports.NO_UPDATE_CHECK_FLAG]).length)
|
|
61
|
+
return false;
|
|
62
|
+
return flagIndexes(argv, HELP_OR_VERSION_FLAGS).length === 0;
|
|
63
|
+
}
|
|
64
|
+
function updateCheckCacheFile() {
|
|
65
|
+
return (0, node_path_1.join)(config_1.DMS_FRONTEND_HOME, "update-check.json");
|
|
66
|
+
}
|
|
67
|
+
function readCache(file) {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(file, "utf-8"));
|
|
70
|
+
if (typeof parsed?.checkedAt !== "number")
|
|
71
|
+
return undefined;
|
|
72
|
+
const latestVersion = parsed.latestVersion;
|
|
73
|
+
if (latestVersion !== undefined && typeof latestVersion !== "string") {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
return { checkedAt: parsed.checkedAt, latestVersion };
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function writeCache(file, cache) {
|
|
83
|
+
try {
|
|
84
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(file), { recursive: true });
|
|
85
|
+
(0, node_fs_1.writeFileSync)(file, `${JSON.stringify(cache)}\n`);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function fetchLatestVersionFromRegistry(packageName, registryUrl = DEFAULT_REGISTRY_URL, timeoutMs = exports.UPDATE_CHECK_TIMEOUT_MS) {
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
let request;
|
|
93
|
+
let deadline;
|
|
94
|
+
let settled = false;
|
|
95
|
+
const settle = (version) => {
|
|
96
|
+
if (settled)
|
|
97
|
+
return;
|
|
98
|
+
settled = true;
|
|
99
|
+
if (deadline)
|
|
100
|
+
clearTimeout(deadline);
|
|
101
|
+
resolve(version);
|
|
102
|
+
};
|
|
103
|
+
try {
|
|
104
|
+
const url = new URL(`${packageName.replace("/", "%2f")}/latest`, registryUrl.endsWith("/") ? registryUrl : `${registryUrl}/`);
|
|
105
|
+
const get = url.protocol === "http:" ? node_http_1.get : node_https_1.get;
|
|
106
|
+
request = get(url, { headers: { accept: "application/json" } }, (response) => {
|
|
107
|
+
if (response.statusCode !== 200) {
|
|
108
|
+
response.resume();
|
|
109
|
+
settle();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
response.setEncoding("utf-8");
|
|
113
|
+
let body = "";
|
|
114
|
+
response.on("data", (chunk) => {
|
|
115
|
+
body += chunk;
|
|
116
|
+
});
|
|
117
|
+
response.on("end", () => {
|
|
118
|
+
try {
|
|
119
|
+
const version = JSON.parse(body)?.version;
|
|
120
|
+
settle(typeof version === "string" ? version : undefined);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
settle();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
response.on("error", () => settle());
|
|
127
|
+
});
|
|
128
|
+
request.on("socket", (socket) => socket.unref());
|
|
129
|
+
request.on("error", () => settle());
|
|
130
|
+
deadline = setTimeout(() => {
|
|
131
|
+
request?.destroy();
|
|
132
|
+
settle();
|
|
133
|
+
}, timeoutMs);
|
|
134
|
+
deadline.unref();
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
settle();
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function formatNotice(currentVersion, latestVersion) {
|
|
142
|
+
return (`A newer DMS frontend version is available: ${currentVersion} -> ${latestVersion}. ` +
|
|
143
|
+
"Run `ajs update dms` to update.\n");
|
|
144
|
+
}
|
|
145
|
+
function isNewer(currentVersion, latestVersion) {
|
|
146
|
+
const current = semver_1.default.valid(currentVersion);
|
|
147
|
+
const latest = semver_1.default.valid(latestVersion);
|
|
148
|
+
if (!current || !latest)
|
|
149
|
+
return false;
|
|
150
|
+
return semver_1.default.lt(current, latest);
|
|
151
|
+
}
|
|
152
|
+
async function checkForUpdate(options) {
|
|
153
|
+
try {
|
|
154
|
+
const { currentVersion, argv = process.argv.slice(2), env = process.env, now = Date.now, cacheFile = updateCheckCacheFile(), fetchLatestVersion = fetchLatestVersionFromRegistry, write = (message) => process.stderr.write(message), } = options;
|
|
155
|
+
if (!isUpdateCheckEnabled(argv, env))
|
|
156
|
+
return;
|
|
157
|
+
const cached = readCache(cacheFile);
|
|
158
|
+
const checkedAt = now();
|
|
159
|
+
const withinInterval = cached !== undefined &&
|
|
160
|
+
checkedAt - cached.checkedAt < exports.UPDATE_CHECK_INTERVAL_MS;
|
|
161
|
+
let latestVersion = cached?.latestVersion;
|
|
162
|
+
if (!withinInterval) {
|
|
163
|
+
latestVersion =
|
|
164
|
+
(await fetchLatestVersion(exports.UPDATE_CHECK_PACKAGE)) ?? latestVersion;
|
|
165
|
+
writeCache(cacheFile, { checkedAt, latestVersion });
|
|
166
|
+
}
|
|
167
|
+
if (latestVersion && isNewer(currentVersion, latestVersion)) {
|
|
168
|
+
write(formatNotice(currentVersion, latestVersion));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
}
|
|
173
|
+
}
|