@antelopejs/dms-frontend 0.2.2 → 0.2.4

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 CHANGED
@@ -94,9 +94,11 @@ URL, either through `-b` or through the enclosing antelope project's
94
94
  `.antelope/dev.json`, and say so on exit 1 when they have neither;
95
95
  `verify-source` needs `--layer` instead.
96
96
 
97
- The CLI checks npm for a newer release at most once a day — a failed lookup
98
- counts as the day's attempt — and prints a one-line notice on stderr. The
99
- throttle stamp lives at `~/.antelopejs/dms-frontend/update-check.json`. Set
97
+ The CLI checks npm for a newer release at most once a day and prints a one-line
98
+ notice on stderr. A lookup that comes back empty — offline, throttled, or simply
99
+ raced by a command that blocked the event loop past the deadline — is retried
100
+ after an hour instead of counting as the day's attempt. The throttle stamp lives
101
+ at `~/.antelopejs/dms-frontend/update-check.json`. Set
100
102
  `NO_UPDATE_NOTIFIER=1`, pass `--no-update-check`, or run under `CI` to turn the
101
103
  check off.
102
104
 
@@ -131,6 +133,34 @@ export default frontendModule;
131
133
 
132
134
  The SDK also exposes `use` for Vue plugins. Entries execute by descending manifest priority, then stable module id. Modules without `dms.frontend.ts` are skipped by the generated loader. Registered page keys match a route's `fullSlug`, request path, or `default`; unregistered pages and components use the generic card renderer.
133
135
 
136
+ ### Layer aliases outside the workspace
137
+
138
+ Every layer a module ships under `layers/<name>/` answers to `#<name>` inside
139
+ the generated workspace. That convention is exported, so a module's own tooling
140
+ can reuse it instead of restating it — a unit test runs the module's Vue
141
+ sources outside the workspace, where nothing otherwise supplies `#dms-core` or
142
+ `#dms-ui`:
143
+
144
+ ```ts
145
+ // vitest.config.ts, in a DMS frontend module
146
+ import { frontendLayerAliases } from "@antelopejs/dms-frontend/common";
147
+
148
+ export default defineConfig({
149
+ resolve: {
150
+ alias: frontendLayerAliases(
151
+ resolve(__dirname, ".antelope/cache/@antelopejs/dms/frontend-vue"),
152
+ ),
153
+ },
154
+ });
155
+ ```
156
+
157
+ `frontendLayerAliases` reads a module root on disk and needs no backend,
158
+ manifest or materialized workspace. `frontendLayerTypePaths` returns the same
159
+ layers in tsconfig `paths` shape, and `resolveFrontendLayers` is the primitive
160
+ behind both. Pass several module roots in increasing precedence to overlay
161
+ them: when two modules expose a layer of the same name, the last one wins,
162
+ which is the order the loader itself materializes them in.
163
+
134
164
  ## Discovery, caching, and security
135
165
 
136
166
  In development, `ajs dms` discovers the backend from the nearest live `.antelope/dev.json`. It reads the local bootstrap credential from `.antelope/dms-dev.json` only when that discovered backend matches the destination URL. For production and CI, set `DMS_API_BASE_URL` and `DMS_BOOTSTRAP_SECRET` in the environment, or in the project's `.env`, rather than passing credentials on the command line.
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function cmdBuild(): Command;
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function cmdClean(): Command;
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function cmdDev(): Command;
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function cmdPrepare(): Command;
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function cmdStart(): Command;
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function parseLocalPackages(values: string[]): Record<string, string>;
3
+ export declare function cmdVerifySource(): Command;
@@ -0,0 +1,9 @@
1
+ export * from "./config";
2
+ export * from "./layers";
3
+ export * from "./layer-aliases";
4
+ export * from "./manifest";
5
+ export * from "./fs-sync";
6
+ export * from "./layer-watch";
7
+ export * from "./materialize";
8
+ export * from "./workspace";
9
+ export * from "./workspace-setup";
package/dist/common.js CHANGED
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./config"), exports);
18
18
  __exportStar(require("./layers"), exports);
19
+ __exportStar(require("./layer-aliases"), exports);
19
20
  __exportStar(require("./manifest"), exports);
20
21
  __exportStar(require("./fs-sync"), exports);
21
22
  __exportStar(require("./layer-watch"), exports);
@@ -0,0 +1,31 @@
1
+ import { Option } from "commander";
2
+ import ignore from "ignore";
3
+ import { type DiscoveryOptions } from "./discovery";
4
+ export declare const DMS_FRONTEND_HOME: string;
5
+ export declare const DEPS_HASH_FILE = ".deps-hash";
6
+ export declare const WORKSPACE_DIR_MODE = 448;
7
+ export declare function writeSecretBearingFile(file: string, content: string): void;
8
+ export declare const TEMPLATE_FILES: ReadonlyArray<readonly [string, string]>;
9
+ export declare const LAYERS_SUBDIR = "frontend-modules";
10
+ export declare const FRONTEND_MODULE_ENTRY = "dms.frontend.ts";
11
+ export declare const AUTH_ESTABLISH_FILE = "generated-auth-establish.json";
12
+ export declare const LAYER_COPY_BLOCKLIST: readonly string[];
13
+ export declare const layerCopyIgnore: ignore.Ignore;
14
+ export declare const PNPM_LIFECYCLE_SCRIPTS: readonly string[];
15
+ export declare const TAILWIND_SOURCE_GLOB = "**/*.{vue,ts,tsx,js,jsx,mjs,cjs}";
16
+ export declare const Options: {
17
+ backendUrl: Option;
18
+ port: Option;
19
+ force: Option;
20
+ readonly offline: Option;
21
+ bootstrapSecret: Option;
22
+ };
23
+ export interface BootstrapDiscoveryOptions extends DiscoveryOptions {
24
+ cwd?: string;
25
+ }
26
+ export declare function normalizeBootstrapSecret(value: string | undefined, source?: string): string | undefined;
27
+ export type SessionSecretMode = "dev" | "build" | "start";
28
+ export declare function resolveSessionSecret(mode: SessionSecretMode, value?: string | undefined): string;
29
+ export declare function resolveBootstrapSecret(explicit: string | undefined, backendUrl: string, options?: BootstrapDiscoveryOptions): string | undefined;
30
+ export declare function sha256Hex(input: string): string;
31
+ export declare function canonicalizeBackendUrl(backendUrl: string): string;
@@ -0,0 +1,48 @@
1
+ export interface DevRegistryEndpoint {
2
+ protocol: string;
3
+ host: string;
4
+ port: number;
5
+ }
6
+ export interface DevRegistry {
7
+ pid: number;
8
+ startedAt?: string;
9
+ servers: Record<string, {
10
+ endpoints: DevRegistryEndpoint[];
11
+ }>;
12
+ }
13
+ export declare const DEV_REGISTRY_RELATIVE_PATH: string;
14
+ export interface DiscoveredBackend {
15
+ projectDir: string;
16
+ registry: DevRegistry;
17
+ backendUrl: string;
18
+ }
19
+ export interface DiscoveryOptions {
20
+ isPidAlive?: (pid: number) => boolean;
21
+ }
22
+ export declare function isPidAlive(pid: number): boolean;
23
+ export declare function buildBackendUrl(endpoint: DevRegistryEndpoint): string;
24
+ export type DiscoveryResult = {
25
+ status: "found";
26
+ backend: DiscoveredBackend;
27
+ } | {
28
+ status: "not-found";
29
+ } | {
30
+ status: "stale";
31
+ projectDir: string;
32
+ pid: number;
33
+ } | {
34
+ status: "no-api-endpoint";
35
+ projectDir: string;
36
+ } | {
37
+ status: "malformed";
38
+ projectDir: string;
39
+ };
40
+ export declare function discoverBackend(cwd: string, options?: DiscoveryOptions): DiscoveryResult;
41
+ export declare function describeDiscoveryFailure(result: DiscoveryResult): string;
42
+ export interface DevHandshake {
43
+ pid: number;
44
+ bootstrapSecret: string;
45
+ updatedAt?: string;
46
+ }
47
+ export declare const DEV_HANDSHAKE_RELATIVE_PATH: string;
48
+ export declare function readDevBootstrapCredential(projectDir: string, options?: DiscoveryOptions): string | undefined;
@@ -0,0 +1,7 @@
1
+ export declare const ENV_FILE_NAMES: readonly [".env.local", ".env"];
2
+ export interface LoadProjectEnvOptions {
3
+ cwd?: string;
4
+ env?: NodeJS.ProcessEnv;
5
+ onWarning?: (message: string) => void;
6
+ }
7
+ export declare function loadProjectEnv(options?: LoadProjectEnvOptions): string[];
@@ -0,0 +1,9 @@
1
+ export declare function stripExtendedLengthPrefix(p: string): string;
2
+ export declare function toPosixPath(path: string, separator?: string): string;
3
+ export declare function isBlocklistedCopyPath(src: string, srcPath: string): boolean;
4
+ export declare function sanitizedPackageContent(raw: string): string;
5
+ export declare function filesIdentical(srcPath: string, destPath: string): boolean;
6
+ export declare function applyContent(content: string, destPath: string): void;
7
+ export declare function applyFile(srcPath: string, destPath: string): void;
8
+ export declare function collectFiles(dir: string, baseDir?: string): string[];
9
+ export declare function syncDirectories(src: string, dest: string): void;
package/dist/fs-sync.js CHANGED
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.stripExtendedLengthPrefix = stripExtendedLengthPrefix;
7
+ exports.toPosixPath = toPosixPath;
7
8
  exports.isBlocklistedCopyPath = isBlocklistedCopyPath;
8
9
  exports.sanitizedPackageContent = sanitizedPackageContent;
9
10
  exports.filesIdentical = filesIdentical;
@@ -18,11 +19,14 @@ const config_1 = require("./config");
18
19
  function stripExtendedLengthPrefix(p) {
19
20
  return p.replace(/^\\\\\?\\/, "");
20
21
  }
22
+ function toPosixPath(path, separator = node_path_1.sep) {
23
+ return separator === "/" ? path : path.split(separator).join("/");
24
+ }
21
25
  function isBlocklistedCopyPath(src, srcPath) {
22
26
  const rel = (0, node_path_1.relative)((0, node_path_1.resolve)(stripExtendedLengthPrefix(src)), (0, node_path_1.resolve)(stripExtendedLengthPrefix(srcPath)));
23
27
  if (!rel || rel.startsWith(".."))
24
28
  return false;
25
- return config_1.layerCopyIgnore.ignores(rel.split(node_path_1.sep).join("/"));
29
+ return config_1.layerCopyIgnore.ignores(toPosixPath(rel));
26
30
  }
27
31
  function sanitizedPackageContent(raw) {
28
32
  const pkg = JSON.parse(raw);
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,12 @@
1
+ export declare const MODULE_LAYERS_DIRNAME = "layers";
2
+ export interface FrontendLayer {
3
+ name: string;
4
+ alias: string;
5
+ directory: string;
6
+ }
7
+ export interface FrontendLayerPathOptions {
8
+ relativeTo?: string;
9
+ }
10
+ export declare function resolveFrontendLayers(moduleRoots: string | readonly string[]): FrontendLayer[];
11
+ export declare function frontendLayerAliases(moduleRoots: string | readonly string[]): Record<string, string>;
12
+ export declare function frontendLayerTypePaths(moduleRoots: string | readonly string[], options?: FrontendLayerPathOptions): Record<string, string[]>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MODULE_LAYERS_DIRNAME = void 0;
4
+ exports.resolveFrontendLayers = resolveFrontendLayers;
5
+ exports.frontendLayerAliases = frontendLayerAliases;
6
+ exports.frontendLayerTypePaths = frontendLayerTypePaths;
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = require("node:path");
9
+ const fs_sync_1 = require("./fs-sync");
10
+ exports.MODULE_LAYERS_DIRNAME = "layers";
11
+ const LAYER_ALIAS_PREFIX = "#";
12
+ function isDirectory(path) {
13
+ try {
14
+ return (0, node_fs_1.statSync)(path).isDirectory();
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ function readModuleLayers(moduleRoot) {
21
+ const layersRoot = (0, node_path_1.join)((0, node_path_1.resolve)(moduleRoot), exports.MODULE_LAYERS_DIRNAME);
22
+ if (!(0, node_fs_1.existsSync)(layersRoot))
23
+ return [];
24
+ return (0, node_fs_1.readdirSync)(layersRoot)
25
+ .map((name) => ({
26
+ name,
27
+ alias: `${LAYER_ALIAS_PREFIX}${name}`,
28
+ directory: (0, node_path_1.join)(layersRoot, name),
29
+ }))
30
+ .filter((layer) => isDirectory(layer.directory));
31
+ }
32
+ function resolveFrontendLayers(moduleRoots) {
33
+ const roots = typeof moduleRoots === "string" ? [moduleRoots] : moduleRoots;
34
+ const layers = new Map();
35
+ for (const root of roots) {
36
+ for (const layer of readModuleLayers(root)) {
37
+ layers.set(layer.name, layer);
38
+ }
39
+ }
40
+ return [...layers.values()];
41
+ }
42
+ function frontendLayerAliases(moduleRoots) {
43
+ return Object.fromEntries(resolveFrontendLayers(moduleRoots).map((layer) => [
44
+ layer.alias,
45
+ layer.directory,
46
+ ]));
47
+ }
48
+ function frontendLayerTypePaths(moduleRoots, options = {}) {
49
+ const base = options.relativeTo;
50
+ return Object.fromEntries(resolveFrontendLayers(moduleRoots).map((layer) => {
51
+ const target = base === undefined
52
+ ? (0, fs_sync_1.toPosixPath)(layer.directory)
53
+ : (0, fs_sync_1.toPosixPath)((0, node_path_1.relative)((0, node_path_1.resolve)(base), layer.directory));
54
+ const specifier = base === undefined || target.startsWith("..") ? target : `./${target}`;
55
+ return [`${layer.alias}/*`, [`${specifier}/*`]];
56
+ }));
57
+ }
@@ -0,0 +1,2 @@
1
+ import { ResolvedLayer } from "./workspace";
2
+ export declare function startLayerWatchers(workspaceDir: string, layers: ResolvedLayer[]): () => Promise<void>;
@@ -0,0 +1,9 @@
1
+ import { ServedWithPath } from "./manifest";
2
+ import { ManifestModule, ResolvedLayer } from "./workspace";
3
+ export declare function assertLayerPathsServed(modules: ManifestModule[]): asserts modules is ServedWithPath[];
4
+ export declare function assertCachedArchivesExist(modules: ManifestModule[], cacheDir: string, fetchedAt?: string): void;
5
+ export declare function buildLayersFromPaths(modules: ManifestModule[]): ResolvedLayer[];
6
+ export declare function downloadAndExtractLayers(backendUrl: string, packUrl: string, cacheDir: string, force: boolean, bootstrapSecret?: string): Promise<void>;
7
+ export declare function buildLayersFromCache(modules: ManifestModule[], cacheDir: string): Promise<ResolvedLayer[]>;
8
+ export declare function getLayerSafeName(layer: ResolvedLayer): string;
9
+ export declare function getLayerWorkspacePath(workspaceDir: string, layer: ResolvedLayer): string;
@@ -0,0 +1,22 @@
1
+ import { Manifest, ManifestModule } from "./workspace";
2
+ export declare class ManifestUnauthorizedError extends Error {
3
+ readonly url: string;
4
+ readonly status: number;
5
+ constructor(url: string, status: number);
6
+ }
7
+ export declare function fetchManifest(backendUrl: string, clientUrl?: string, bootstrapSecret?: string): Promise<Manifest>;
8
+ export interface ManifestCacheEntry {
9
+ manifest: Manifest;
10
+ fetchedAt: string;
11
+ }
12
+ export declare function readCachedManifest(workspaceDir: string): ManifestCacheEntry | undefined;
13
+ export interface ResolvedManifest {
14
+ manifest: Manifest;
15
+ fromCache: boolean;
16
+ fetchedAt?: string;
17
+ }
18
+ export declare function resolveManifest(workspaceDir: string, backendUrl: string, offline: boolean, clientUrl?: string, bootstrapSecret?: string): Promise<ResolvedManifest>;
19
+ export declare function assertCachedLayerPathsExist(modules: ServedWithPath[], fetchedAt?: string): void;
20
+ export type ServedWithPath = ManifestModule & {
21
+ path: string;
22
+ };
@@ -0,0 +1,20 @@
1
+ import { FrontendModuleOptions, ResolvedLayer } from "./workspace";
2
+ export declare function getPackageRoot(): string;
3
+ export declare function copyStaticTemplates(workspaceDir: string): void;
4
+ export declare function writeWorkspacePackageJson(workspaceDir: string, layers: ResolvedLayer[]): void;
5
+ export declare function materializeLayers(workspaceDir: string, layers: ResolvedLayer[]): void;
6
+ export interface FrontendModuleRegistryEntry {
7
+ id: string;
8
+ packageName?: string;
9
+ root: string;
10
+ priority: number;
11
+ entry?: string;
12
+ options: FrontendModuleOptions;
13
+ }
14
+ export interface FrontendModuleRegistry {
15
+ modules: FrontendModuleRegistryEntry[];
16
+ }
17
+ export declare function createFrontendModuleRegistry(workspaceDir: string, layers: ResolvedLayer[]): FrontendModuleRegistry;
18
+ export declare function collectAuthEstablishEndpoints(layers: ResolvedLayer[]): string[];
19
+ export declare function writeFrontendModuleRegistry(workspaceDir: string, layers: ResolvedLayer[]): void;
20
+ export declare function writeDmsMainCss(workspaceDir: string, layers: ResolvedLayer[]): void;
@@ -11,6 +11,7 @@ exports.writeDmsMainCss = writeDmsMainCss;
11
11
  const node_fs_1 = require("node:fs");
12
12
  const node_path_1 = require("node:path");
13
13
  const config_1 = require("./config");
14
+ const layer_aliases_1 = require("./layer-aliases");
14
15
  const layers_1 = require("./layers");
15
16
  const fs_sync_1 = require("./fs-sync");
16
17
  function getPackageRoot() {
@@ -142,17 +143,8 @@ function writeFrontendTypePaths(workspaceDir, registry) {
142
143
  const paths = {
143
144
  "#dms/frontend-module": ["./frontend-module.ts"],
144
145
  "@frontend/*": ["./frontend-modules/*"],
146
+ ...(0, layer_aliases_1.frontendLayerTypePaths)([...registry.modules].reverse().map((module) => module.root), { relativeTo: workspaceDir }),
145
147
  };
146
- [...registry.modules].reverse().forEach((module) => {
147
- const layersRoot = (0, node_path_1.join)(module.root, "layers");
148
- if (!(0, node_fs_1.existsSync)(layersRoot))
149
- return;
150
- (0, node_fs_1.readdirSync)(layersRoot).forEach((name) => {
151
- paths[`#${name}/*`] = [
152
- `./frontend-modules/${module.id}/layers/${name}/*`,
153
- ];
154
- });
155
- });
156
148
  const config = { compilerOptions: { paths } };
157
149
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, "frontend-paths.generated.json"), `${JSON.stringify(config, null, 2)}\n`);
158
150
  }
@@ -198,7 +190,7 @@ function discoverAssets(registry, directory, extensions) {
198
190
  .sort()
199
191
  .map((relativePath) => ({
200
192
  moduleId: module.id,
201
- relativePath: `${assetRoot.relativePrefix}${(0, node_path_1.join)(directory, relativePath).split(node_path_1.sep).join("/")}`,
193
+ relativePath: `${assetRoot.relativePrefix}${(0, fs_sync_1.toPosixPath)((0, node_path_1.join)(directory, relativePath))}`,
202
194
  }));
203
195
  });
204
196
  });
@@ -259,7 +251,7 @@ function writeShortcutExports(workspaceDir, registry) {
259
251
  }
260
252
  function writeDmsMainCss(workspaceDir, layers) {
261
253
  const sources = layers
262
- .map((layer) => `@source "${(0, layers_1.getLayerWorkspacePath)(workspaceDir, layer)}/${config_1.TAILWIND_SOURCE_GLOB}";`)
254
+ .map((layer) => `@source "${(0, fs_sync_1.toPosixPath)((0, layers_1.getLayerWorkspacePath)(workspaceDir, layer))}/${config_1.TAILWIND_SOURCE_GLOB}";`)
263
255
  .join("\n");
264
256
  const content = `@import "tailwindcss";
265
257
  @import "@nuxt/ui";
@@ -0,0 +1,6 @@
1
+ export declare const PORT_FALLBACK_RANGE = 20;
2
+ export interface ReservedPort {
3
+ port: number;
4
+ release: () => Promise<void>;
5
+ }
6
+ export declare function reserveFreePort(preferred: number, range?: number): Promise<ReservedPort>;
@@ -0,0 +1,19 @@
1
+ export declare const UPDATE_CHECK_PACKAGE = "@antelopejs/dms-frontend";
2
+ export declare const UPDATE_CHECK_INTERVAL_MS: number;
3
+ export declare const UPDATE_CHECK_RETRY_INTERVAL_MS: number;
4
+ export declare const UPDATE_CHECK_TIMEOUT_MS = 3000;
5
+ export declare const NO_UPDATE_CHECK_FLAG = "--no-update-check";
6
+ export interface UpdateCheckOptions {
7
+ currentVersion: string;
8
+ argv?: readonly string[];
9
+ env?: NodeJS.ProcessEnv;
10
+ now?: () => number;
11
+ cacheFile?: string;
12
+ fetchLatestVersion?: (packageName: string) => Promise<string | undefined>;
13
+ write?: (message: string) => void;
14
+ }
15
+ export declare function stripUpdateCheckFlag(argv: readonly string[]): string[];
16
+ export declare function isUpdateCheckEnabled(argv: readonly string[], env: NodeJS.ProcessEnv): boolean;
17
+ export declare function updateCheckCacheFile(): string;
18
+ export declare function fetchLatestVersionFromRegistry(packageName: string, registryUrl?: string, timeoutMs?: number): Promise<string | undefined>;
19
+ export declare function checkForUpdate(options: UpdateCheckOptions): Promise<void>;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
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;
6
+ exports.NO_UPDATE_CHECK_FLAG = exports.UPDATE_CHECK_TIMEOUT_MS = exports.UPDATE_CHECK_RETRY_INTERVAL_MS = exports.UPDATE_CHECK_INTERVAL_MS = exports.UPDATE_CHECK_PACKAGE = void 0;
7
7
  exports.stripUpdateCheckFlag = stripUpdateCheckFlag;
8
8
  exports.isUpdateCheckEnabled = isUpdateCheckEnabled;
9
9
  exports.updateCheckCacheFile = updateCheckCacheFile;
@@ -18,6 +18,7 @@ const config_1 = require("./config");
18
18
  exports.UPDATE_CHECK_PACKAGE = "@antelopejs/dms-frontend";
19
19
  const DEFAULT_REGISTRY_URL = "https://registry.npmjs.org/";
20
20
  exports.UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
21
+ exports.UPDATE_CHECK_RETRY_INTERVAL_MS = 60 * 60 * 1000;
21
22
  exports.UPDATE_CHECK_TIMEOUT_MS = 3000;
22
23
  exports.NO_UPDATE_CHECK_FLAG = "--no-update-check";
23
24
  const HELP_OR_VERSION_FLAGS = ["--help", "-h", "--version", "-v"];
@@ -73,7 +74,11 @@ function readCache(file) {
73
74
  if (latestVersion !== undefined && typeof latestVersion !== "string") {
74
75
  return undefined;
75
76
  }
76
- return { checkedAt: parsed.checkedAt, latestVersion };
77
+ const succeeded = parsed.succeeded;
78
+ if (succeeded !== undefined && typeof succeeded !== "boolean") {
79
+ return undefined;
80
+ }
81
+ return { checkedAt: parsed.checkedAt, latestVersion, succeeded };
77
82
  }
78
83
  catch {
79
84
  return undefined;
@@ -156,13 +161,20 @@ async function checkForUpdate(options) {
156
161
  return;
157
162
  const cached = readCache(cacheFile);
158
163
  const checkedAt = now();
159
- const withinInterval = cached !== undefined &&
160
- checkedAt - cached.checkedAt < exports.UPDATE_CHECK_INTERVAL_MS;
164
+ const lastAttemptAnswered = cached?.succeeded ?? cached?.latestVersion !== undefined;
165
+ const interval = lastAttemptAnswered
166
+ ? exports.UPDATE_CHECK_INTERVAL_MS
167
+ : exports.UPDATE_CHECK_RETRY_INTERVAL_MS;
168
+ const withinInterval = cached !== undefined && checkedAt - cached.checkedAt < interval;
161
169
  let latestVersion = cached?.latestVersion;
162
170
  if (!withinInterval) {
163
- latestVersion =
164
- (await fetchLatestVersion(exports.UPDATE_CHECK_PACKAGE)) ?? latestVersion;
165
- writeCache(cacheFile, { checkedAt, latestVersion });
171
+ const published = await fetchLatestVersion(exports.UPDATE_CHECK_PACKAGE);
172
+ latestVersion = published ?? latestVersion;
173
+ writeCache(cacheFile, {
174
+ checkedAt,
175
+ latestVersion,
176
+ succeeded: published !== undefined,
177
+ });
166
178
  }
167
179
  if (latestVersion && isNewer(currentVersion, latestVersion)) {
168
180
  write(formatNotice(currentVersion, latestVersion));
@@ -0,0 +1,27 @@
1
+ import { type Options as BoxenOptions } from "boxen";
2
+ export declare class Spinner {
3
+ private text;
4
+ private isRunning;
5
+ private interval?;
6
+ private currentCharIndex;
7
+ private isTerminal;
8
+ constructor(text: string);
9
+ start(text?: string): Promise<Spinner>;
10
+ update(text: string): Spinner;
11
+ log(stream: NodeJS.WriteStream, message: string): Spinner;
12
+ succeed(text?: string): Promise<void>;
13
+ fail(text?: string): Promise<void>;
14
+ info(text?: string): Promise<void>;
15
+ warn(text?: string): Promise<void>;
16
+ pause(): Promise<void>;
17
+ stop(): Promise<void>;
18
+ clear(): Promise<void>;
19
+ }
20
+ export declare function displayBox(message: string, title?: string, options?: BoxenOptions): void;
21
+ export declare function displayBanner(text: string, font?: string): void;
22
+ export declare function success(message: string): void;
23
+ export declare function error(message: string): void;
24
+ export declare function warning(message: string): void;
25
+ export declare function info(message: string): void;
26
+ export declare function header(text: string): void;
27
+ export declare function keyValue(key: string, value: string | number | boolean): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { type SpawnOptions } from "node:child_process";
2
+ import { ResolvedLayer } from "./workspace";
3
+ export declare function computeDepsHash(layerPaths: string[], workspacePackage?: string): string;
4
+ export declare function needsInstall(workspaceDir: string, layerPaths: string[], force: boolean): boolean;
5
+ export declare function saveDepsHash(workspaceDir: string, layerPaths: string[]): void;
6
+ export declare function installDeps(workspaceDir: string): void;
7
+ export declare function runCommand(command: string, args: string[], options?: SpawnOptions): Promise<number>;
8
+ export interface SetupWorkspaceOptions {
9
+ backendUrl: string;
10
+ force: boolean;
11
+ mode: "dev" | "build";
12
+ offline?: boolean;
13
+ clientUrl?: string;
14
+ workspaceKey?: string;
15
+ bootstrapSecret?: string;
16
+ }
17
+ export interface SetupWorkspaceResult {
18
+ workspaceDir: string;
19
+ layers: ResolvedLayer[];
20
+ manifestFromCache: boolean;
21
+ manifestFetchedAt?: string;
22
+ }
23
+ export declare function setupWorkspace(opts: SetupWorkspaceOptions): Promise<SetupWorkspaceResult>;
@@ -0,0 +1,51 @@
1
+ export type FrontendModuleValue = string | number | boolean | null | FrontendModuleValue[] | FrontendModuleOptions;
2
+ export interface FrontendModuleOptions {
3
+ [key: string]: FrontendModuleValue;
4
+ }
5
+ export interface ManifestModule {
6
+ name: string;
7
+ archiveName: string;
8
+ renderer?: {
9
+ name: string;
10
+ version: string;
11
+ };
12
+ path?: string;
13
+ priority: number;
14
+ options?: FrontendModuleOptions;
15
+ privateOptions?: FrontendModuleOptions;
16
+ configKey?: string;
17
+ authEstablishEndpoints?: string[];
18
+ }
19
+ export interface Manifest {
20
+ pack: string;
21
+ modules: ManifestModule[];
22
+ }
23
+ export interface FrontendManifest {
24
+ version: number;
25
+ archive: string;
26
+ modules: ManifestModule[];
27
+ }
28
+ export interface ResolvedLayer {
29
+ path: string;
30
+ packageName?: string;
31
+ priority?: number;
32
+ configKey?: string;
33
+ options?: FrontendModuleOptions;
34
+ authEstablishEndpoints?: string[];
35
+ }
36
+ export declare function getWorkspaceDirForKey(key: string): string;
37
+ export declare function projectWorkspaceKey(projectDir: string): string;
38
+ export declare function projectDirFromWorkspaceKey(workspaceKey: string | undefined): string | undefined;
39
+ export declare function getWorkspaceDir(backendUrl: string): string;
40
+ export declare function ensureWorkspace(workspaceKey: string): string;
41
+ export interface WorkspaceEntry {
42
+ dir: string;
43
+ backendUrl: string;
44
+ workspaceKey?: string;
45
+ }
46
+ export declare function describeWorkspace(entry: WorkspaceEntry): string;
47
+ export declare function listWorkspaces(): WorkspaceEntry[];
48
+ export declare function writeWorkspaceMeta(workspaceDir: string, backendUrl: string, workspaceKey: string): void;
49
+ export declare const FRONTEND_MANIFEST_VERSION = 1;
50
+ export declare function bootstrapHeaders(secret?: string): Record<string, string>;
51
+ export declare function isUnauthorized(status: number): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms-frontend",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Frontend-agnostic loader for AntelopeJS DMS, shipping the Vue 3 renderer (Vite, Inertia, SSR)",
5
5
  "keywords": [
6
6
  "antelope",
@@ -27,6 +27,25 @@
27
27
  "dist/",
28
28
  "templates/"
29
29
  ],
30
+ "main": "./dist/common.js",
31
+ "types": "./dist/common.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/common.d.ts",
35
+ "default": "./dist/common.js"
36
+ },
37
+ "./common": {
38
+ "types": "./dist/common.d.ts",
39
+ "default": "./dist/common.js"
40
+ },
41
+ "./dist/*.js": {
42
+ "types": "./dist/*.d.ts",
43
+ "default": "./dist/*.js"
44
+ },
45
+ "./dist/*": "./dist/*",
46
+ "./templates/*": "./templates/*",
47
+ "./package.json": "./package.json"
48
+ },
30
49
  "publishConfig": {
31
50
  "access": "public",
32
51
  "provenance": true,
@@ -16,7 +16,7 @@
16
16
  // any pre-existing link with that attribute and then skips `updateStyle` for
17
17
  // it, which would leave CSS edits stranded.
18
18
 
19
- import { join } from "node:path";
19
+ import { join, sep } from "node:path";
20
20
  import { fileURLToPath } from "node:url";
21
21
  import { pageModulePreloads } from "./client-manifest.mjs";
22
22
 
@@ -32,8 +32,16 @@ const STYLE_EXTENSION =
32
32
  * one `@source` per materialized module — nearly every rule a page needs.
33
33
  * Naming it is the one enumeration this module accepts, and the comment is the
34
34
  * reason why.
35
+ *
36
+ * `ENTRY_STYLESHEET_ID` is matched against the `data-vite-dev-id` Vite puts on
37
+ * its own copy, and Vite normalizes every module id onto POSIX separators — so
38
+ * the native path `join` returns has to be normalized too, or the handover
39
+ * never happens on Windows and both copies stay in the document.
35
40
  */
36
41
  const ENTRY_STYLESHEET = "/dms-main.css";
42
+ const ENTRY_STYLESHEET_ID = join(PROJECT_ROOT, "dms-main.css")
43
+ .split(sep)
44
+ .join("/");
37
45
 
38
46
  function ssrModuleGraph(devServer) {
39
47
  return devServer.environments?.ssr?.moduleGraph ?? devServer.moduleGraph;
@@ -75,7 +83,7 @@ async function inlineTag(devServer, { url, id }) {
75
83
  export async function developmentStyleTags(devServer) {
76
84
  const modules = styleModules(devServer);
77
85
  const links = [
78
- { url: ENTRY_STYLESHEET, id: join(PROJECT_ROOT, "dms-main.css") },
86
+ { url: ENTRY_STYLESHEET, id: ENTRY_STYLESHEET_ID },
79
87
  ...modules.filter(({ url }) => !SFC_STYLE.test(url)),
80
88
  ].map(linkTag);
81
89
  const inlined = await Promise.all(
@@ -1,6 +1,6 @@
1
1
  import { createReadStream, existsSync, readFileSync, statSync } from "node:fs";
2
2
  import { createServer } from "node:http";
3
- import { extname, join, resolve } from "node:path";
3
+ import { extname, join, resolve, sep } from "node:path";
4
4
  import { Readable } from "node:stream";
5
5
  import { pipeline } from "node:stream/promises";
6
6
  import { fileURLToPath } from "node:url";
@@ -254,7 +254,9 @@ function acceptedAsset(pathname, request) {
254
254
 
255
255
  function serveAsset(pathname, request, response) {
256
256
  const selected = acceptedAsset(pathname, request);
257
- const assetRoot = `${resolve(PROJECT_ROOT, "dist/client")}/`;
257
+ // Native separator: `selected.source` comes from `resolve`, so a hardcoded
258
+ // `/` here would make the containment check fail for every asset on Windows.
259
+ const assetRoot = `${resolve(PROJECT_ROOT, "dist/client")}${sep}`;
258
260
  if (
259
261
  !selected.source.startsWith(assetRoot) ||
260
262
  !existsSync(selected.file) ||
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, resolve } from "node:path";
3
3
  import ui from "@nuxt/ui/vite";
4
4
  import vue from "@vitejs/plugin-vue";
5
- import { defineConfig, type Plugin } from "vite";
5
+ import { defineConfig, normalizePath, type Plugin } from "vite";
6
6
  import { orderHeadForFirstPaint } from "./head-order.mjs";
7
7
 
8
8
  interface FrontendModuleRegistryEntry {
@@ -38,6 +38,14 @@ const stableModuleAliases = Object.fromEntries(
38
38
  return [[`#${moduleName}`, module.root]];
39
39
  }),
40
40
  );
41
+ /**
42
+ * Globs are POSIX, always.
43
+ *
44
+ * `resolve` returns a native path, so on Windows every pattern below would
45
+ * carry backslashes — which a glob matcher reads as escape characters, not as
46
+ * separators, and which therefore match nothing at all. `normalizePath` is
47
+ * Vite's own answer to this and is the identity function on POSIX.
48
+ */
41
49
  const importDirectories = frontendSourceRoots.flatMap((root) =>
42
50
  [
43
51
  "app/composables/**/*",
@@ -45,7 +53,7 @@ const importDirectories = frontendSourceRoots.flatMap((root) =>
45
53
  "app/utils/**/*",
46
54
  "app/build/composables/**/*",
47
55
  "app/build/types/**/*",
48
- ].map((glob) => ({ glob: resolve(root, glob), types: true })),
56
+ ].map((glob) => ({ glob: normalizePath(resolve(root, glob)), types: true })),
49
57
  );
50
58
  const optimizedDependencies = [
51
59
  "vue",
@@ -80,6 +88,9 @@ const optimizedDependencies = [
80
88
  * told to reload the whole document. Crawling the module sources once, at
81
89
  * startup, pays that cost a single time and in a place where it reads as
82
90
  * startup rather than as a crash.
91
+ *
92
+ * Normalized for the same reason as `importDirectories`: Vite hands these
93
+ * patterns straight to its glob matcher without touching the separators.
83
94
  */
84
95
  const optimizerEntries = [
85
96
  resolve(__dirname, "main.ts"),
@@ -88,7 +99,7 @@ const optimizerEntries = [
88
99
  resolve(root, "app/**/*.vue"),
89
100
  resolve(root, "app/**/*.ts"),
90
101
  ]),
91
- ];
102
+ ].map(normalizePath);
92
103
 
93
104
  function paintBeforeHydrate(): Plugin {
94
105
  return {
@@ -125,7 +136,13 @@ export default defineConfig({
125
136
  imports: [
126
137
  "vue",
127
138
  {
128
- [resolve(__dirname, "frontend-module.ts")]: [
139
+ // This key is inlined verbatim as the module specifier of the
140
+ // import the auto-importer prepends to each file. A native Windows
141
+ // path would land inside a single-quoted JavaScript string with its
142
+ // backslashes intact, where `\U`, `\M` and `\f` are read as escape
143
+ // sequences: the specifier the bundler then resolves is a mangled
144
+ // path that cannot exist. Keep it POSIX.
145
+ [normalizePath(resolve(__dirname, "frontend-module.ts"))]: [
129
146
  "$fetch",
130
147
  "abortNavigation",
131
148
  "clearError",
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, resolve } from "node:path";
3
3
  import vue from "@vitejs/plugin-vue";
4
4
  import AutoImport from "unplugin-auto-import/vite";
5
- import { defineConfig } from "vite";
5
+ import { defineConfig, normalizePath } from "vite";
6
6
 
7
7
  interface FrontendModuleRegistryEntry {
8
8
  id: string;
@@ -55,7 +55,10 @@ export default defineConfig({
55
55
  dts: false,
56
56
  imports: [
57
57
  {
58
- [resolve(__dirname, "email-runtime.ts")]: [
58
+ // Inlined verbatim as a module specifier in the generated import, so
59
+ // it has to be POSIX: a native Windows path would reach the parser
60
+ // with its backslashes read as string escapes. See vite.config.ts.
61
+ [normalizePath(resolve(__dirname, "email-runtime.ts"))]: [
59
62
  "useDmsAppConfig",
60
63
  "useDmsRuntimeConfig",
61
64
  "defineAppConfig",