@antelopejs/dms-frontend 0.3.8 → 0.3.10

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.
Files changed (80) hide show
  1. package/README.md +87 -23
  2. package/dist/cancellation.d.ts +8 -0
  3. package/dist/cancellation.js +23 -0
  4. package/dist/child-output.d.ts +45 -0
  5. package/dist/child-output.js +183 -0
  6. package/dist/commands/build-action.d.ts +21 -0
  7. package/dist/commands/build-action.js +144 -0
  8. package/dist/commands/build.js +54 -62
  9. package/dist/commands/clean-action.d.ts +8 -0
  10. package/dist/commands/clean-action.js +97 -0
  11. package/dist/commands/clean.js +52 -39
  12. package/dist/commands/dev-action.d.ts +8 -0
  13. package/dist/commands/dev-action.js +120 -0
  14. package/dist/commands/dev.js +55 -115
  15. package/dist/commands/prepare-action.d.ts +8 -0
  16. package/dist/commands/prepare-action.js +73 -0
  17. package/dist/commands/prepare.js +55 -54
  18. package/dist/commands/start-action.d.ts +5 -0
  19. package/dist/commands/start-action.js +77 -0
  20. package/dist/commands/start.js +48 -48
  21. package/dist/commands/verify-source-action.d.ts +10 -0
  22. package/dist/commands/verify-source-action.js +104 -0
  23. package/dist/commands/verify-source.d.ts +0 -1
  24. package/dist/commands/verify-source.js +53 -31
  25. package/dist/commands/workspace-task.d.ts +2 -0
  26. package/dist/commands/workspace-task.js +26 -0
  27. package/dist/commands/workspaces-action.d.ts +25 -0
  28. package/dist/commands/workspaces-action.js +71 -0
  29. package/dist/commands/workspaces.d.ts +2 -0
  30. package/dist/commands/workspaces.js +60 -0
  31. package/dist/common.d.ts +1 -0
  32. package/dist/common.js +1 -0
  33. package/dist/config.d.ts +13 -4
  34. package/dist/config.js +115 -18
  35. package/dist/derived-outputs.d.ts +6 -1
  36. package/dist/derived-outputs.js +12 -3
  37. package/dist/discovery.d.ts +2 -1
  38. package/dist/discovery.js +28 -11
  39. package/dist/env-file.js +5 -2
  40. package/dist/help.d.ts +5 -0
  41. package/dist/help.js +71 -0
  42. package/dist/index.js +129 -38
  43. package/dist/layer-watch.d.ts +2 -1
  44. package/dist/layer-watch.js +129 -26
  45. package/dist/layers.d.ts +9 -2
  46. package/dist/layers.js +82 -23
  47. package/dist/manifest-secrets.d.ts +3 -2
  48. package/dist/manifest-secrets.js +25 -21
  49. package/dist/manifest.d.ts +22 -5
  50. package/dist/manifest.js +184 -28
  51. package/dist/materialize.js +2 -1
  52. package/dist/output.d.ts +43 -0
  53. package/dist/output.js +214 -0
  54. package/dist/ports.d.ts +2 -0
  55. package/dist/ports.js +23 -13
  56. package/dist/renderer-range.d.ts +3 -1
  57. package/dist/renderer-range.js +49 -17
  58. package/dist/server-process.d.ts +39 -0
  59. package/dist/server-process.js +226 -0
  60. package/dist/typecheck-output.d.ts +13 -0
  61. package/dist/typecheck-output.js +87 -0
  62. package/dist/update-check.d.ts +14 -2
  63. package/dist/update-check.js +97 -29
  64. package/dist/verify-source-result.d.ts +11 -0
  65. package/dist/verify-source-result.js +17 -0
  66. package/dist/verify-source-runner.js +266 -131
  67. package/dist/vite-output.d.ts +14 -0
  68. package/dist/vite-output.js +99 -0
  69. package/dist/workspace-setup.d.ts +17 -3
  70. package/dist/workspace-setup.js +122 -35
  71. package/dist/workspace.d.ts +8 -2
  72. package/dist/workspace.js +47 -12
  73. package/package.json +4 -12
  74. package/templates/vue/package.json +4 -0
  75. package/templates/vue/server/cli-report.mjs +91 -0
  76. package/templates/vue/server.mjs +18 -15
  77. package/templates/vue/vite.config.ts +8 -1
  78. package/templates/vue/vite.email.config.ts +5 -1
  79. package/dist/utils/cli-ui.d.ts +0 -27
  80. package/dist/utils/cli-ui.js +0 -178
@@ -0,0 +1,14 @@
1
+ import { type CliProblem, type Ui } from "@antelopejs/core/cli";
2
+ export declare const VITE_LOG_LEVEL_VARIABLE = "DMS_VITE_LOG_LEVEL";
3
+ export declare function viteLogLevel(isVerbose: boolean): string;
4
+ export declare const VERBOSE_VITE_HINT = "Run with --verbose for the full Vite output.";
5
+ export declare class ViteWarnings {
6
+ private readonly kept;
7
+ private opened;
8
+ private isAfterPluginTag;
9
+ read(line: string): void;
10
+ get count(): number;
11
+ lines(): string[];
12
+ }
13
+ export declare function reportViteWarnings(warnings: ViteWarnings, isVerbose: boolean, ui: Ui): void;
14
+ export declare function describeViteFailure(lines: string[], subject: string, isVerbose: boolean, rerun?: string, nameFile?: (file: string) => string): CliProblem | undefined;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ViteWarnings = exports.VERBOSE_VITE_HINT = exports.VITE_LOG_LEVEL_VARIABLE = void 0;
4
+ exports.viteLogLevel = viteLogLevel;
5
+ exports.reportViteWarnings = reportViteWarnings;
6
+ exports.describeViteFailure = describeViteFailure;
7
+ const cli_1 = require("@antelopejs/core/cli");
8
+ const output_1 = require("./output");
9
+ exports.VITE_LOG_LEVEL_VARIABLE = "DMS_VITE_LOG_LEVEL";
10
+ function viteLogLevel(isVerbose) {
11
+ return isVerbose ? "info" : "warn";
12
+ }
13
+ exports.VERBOSE_VITE_HINT = "Run with --verbose for the full Vite output.";
14
+ const WARNING_START = /^(\(!\)|\[plugin[ :][^\]]*\]|\(node:\d+\))/;
15
+ const PLUGIN_TAG_LINE = /^\[plugin[ :][^\]]*\]$/;
16
+ class ViteWarnings {
17
+ constructor() {
18
+ this.kept = [];
19
+ this.opened = 0;
20
+ this.isAfterPluginTag = false;
21
+ }
22
+ read(line) {
23
+ const text = line.trim();
24
+ if (text === "")
25
+ return;
26
+ this.kept.push(line.trimEnd());
27
+ const continuesTag = this.isAfterPluginTag && text.startsWith("(!)");
28
+ if (WARNING_START.test(text) && !continuesTag)
29
+ this.opened += 1;
30
+ this.isAfterPluginTag = PLUGIN_TAG_LINE.test(text);
31
+ }
32
+ get count() {
33
+ if (this.opened === 0 && this.kept.length > 0)
34
+ return 1;
35
+ return this.opened;
36
+ }
37
+ lines() {
38
+ return [...this.kept];
39
+ }
40
+ }
41
+ exports.ViteWarnings = ViteWarnings;
42
+ function reportViteWarnings(warnings, isVerbose, ui) {
43
+ if (isVerbose || warnings.count === 0)
44
+ return;
45
+ const title = (0, cli_1.pluralize)(warnings.count, "Vite warning");
46
+ const details = (0, output_1.isTerminalFeedback)()
47
+ ? [`${ui.symbols.levels.hint} Run with --verbose to list them`]
48
+ : warnings.lines();
49
+ ui.message("warn", title, { details });
50
+ }
51
+ const ERROR_START = "error during build:";
52
+ const FILE_LINE = /^file:\s+(.+)$/;
53
+ const CODE_FRAME_LINE = /^\s*\d*\s*\|/;
54
+ const STACK_FRAME = /^\s+at\s/;
55
+ const LOCATION_SUFFIX = /\s*\(\d+:\d+\)$/;
56
+ const VITE_PLUGIN_TAG = /^\[vite:[^\]]+\]\s+(?=\S)/;
57
+ function errorBlock(lines) {
58
+ let start = -1;
59
+ lines.forEach((line, index) => {
60
+ if (line.trim() === ERROR_START)
61
+ start = index;
62
+ });
63
+ if (start < 0)
64
+ return undefined;
65
+ const block = [];
66
+ for (const line of lines.slice(start + 1)) {
67
+ if (STACK_FRAME.test(line))
68
+ break;
69
+ if (line.trim() !== "")
70
+ block.push(line.trimEnd());
71
+ }
72
+ return block;
73
+ }
74
+ function describeViteFailure(lines, subject, isVerbose, rerun = "ajs dms build", nameFile = (file) => file) {
75
+ const block = errorBlock(lines);
76
+ if (!block)
77
+ return undefined;
78
+ const [message = "", ...rest] = block;
79
+ const reason = message
80
+ .replace(LOCATION_SUFFIX, "")
81
+ .replace(VITE_PLUGIN_TAG, "");
82
+ const hint = isVerbose ? [] : [exports.VERBOSE_VITE_HINT];
83
+ const file = rest
84
+ .map((line) => FILE_LINE.exec(line.trim())?.[1])
85
+ .find((match) => match !== undefined);
86
+ if (file) {
87
+ return {
88
+ title: `Vite could not compile ${nameFile(file)}`,
89
+ reason,
90
+ fixes: [`Fix the file and run ${rerun} again`],
91
+ details: [...rest.filter((line) => CODE_FRAME_LINE.test(line)), ...hint],
92
+ };
93
+ }
94
+ return {
95
+ title: `Vite could not build the ${subject}`,
96
+ reason,
97
+ details: [...rest, ...hint],
98
+ };
99
+ }
@@ -1,11 +1,24 @@
1
- import { type SpawnOptions } from "node:child_process";
1
+ import { type ChildProcess, type SpawnOptions } from "node:child_process";
2
+ import { type PathMapper } from "./child-output";
2
3
  import { type ManifestSecrets } from "./manifest-secrets";
3
4
  import { ResolvedLayer } from "./workspace";
4
5
  export declare function computeDepsHash(layerPaths: string[], workspacePackage?: string, installInputs?: readonly string[]): string;
5
6
  export declare function needsInstall(workspaceDir: string, layerPaths: string[], force: boolean): boolean;
6
7
  export declare function saveDepsHash(workspaceDir: string, layerPaths: string[]): void;
7
- export declare function installDeps(workspaceDir: string): void;
8
- export declare function runCommand(command: string, args: string[], options?: SpawnOptions): Promise<number>;
8
+ export declare function installDeps(workspaceDir: string, mapLine?: PathMapper, extraArgs?: string[]): Promise<void>;
9
+ export declare function runCommand(command: string, args: string[], options?: SpawnOptions, onSpawn?: (child: ChildProcess) => void): Promise<number>;
10
+ export interface FramedCommandOptions {
11
+ name: string;
12
+ cwd: string;
13
+ env?: NodeJS.ProcessEnv;
14
+ mapLine?: PathMapper;
15
+ onLine?: (line: string) => void;
16
+ }
17
+ export interface FramedCommandResult {
18
+ code: number;
19
+ lines: string[];
20
+ }
21
+ export declare function runFramedCommand(command: string, args: string[], options: FramedCommandOptions): Promise<FramedCommandResult>;
9
22
  export interface SetupWorkspaceOptions {
10
23
  backendUrl: string;
11
24
  force: boolean;
@@ -14,6 +27,7 @@ export interface SetupWorkspaceOptions {
14
27
  clientUrl?: string;
15
28
  workspaceKey?: string;
16
29
  bootstrapSecret?: string;
30
+ beforeInstall?: () => Promise<void>;
17
31
  }
18
32
  export interface SetupWorkspaceResult {
19
33
  workspaceDir: string;
@@ -5,18 +5,22 @@ exports.needsInstall = needsInstall;
5
5
  exports.saveDepsHash = saveDepsHash;
6
6
  exports.installDeps = installDeps;
7
7
  exports.runCommand = runCommand;
8
+ exports.runFramedCommand = runFramedCommand;
8
9
  exports.setupWorkspace = setupWorkspace;
9
10
  const node_child_process_1 = require("node:child_process");
10
11
  const node_crypto_1 = require("node:crypto");
11
12
  const node_fs_1 = require("node:fs");
12
- const node_os_1 = require("node:os");
13
13
  const node_path_1 = require("node:path");
14
+ const cli_1 = require("@antelopejs/core/cli");
15
+ const child_output_1 = require("./child-output");
14
16
  const layers_1 = require("./layers");
15
17
  const manifest_1 = require("./manifest");
16
18
  const manifest_secrets_1 = require("./manifest-secrets");
17
19
  const renderer_range_1 = require("./renderer-range");
18
20
  const materialize_1 = require("./materialize");
19
21
  const workspace_1 = require("./workspace");
22
+ const cancellation_1 = require("./cancellation");
23
+ const output_1 = require("./output");
20
24
  const config_1 = require("./config");
21
25
  const WORKSPACE_PNPM_CONFIG = "pnpm-workspace.yaml";
22
26
  const WORKSPACE_PATCHES_DIR = "patches";
@@ -61,48 +65,77 @@ function saveDepsHash(workspaceDir, layerPaths) {
61
65
  const hash = computeDepsHash(layerPaths, (0, node_path_1.join)(workspaceDir, "package.json"), workspaceInstallInputs(workspaceDir));
62
66
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, config_1.DEPS_HASH_FILE), hash);
63
67
  }
64
- function installDeps(workspaceDir) {
65
- const { execSync } = require("node:child_process");
66
- execSync("pnpm install", {
67
- cwd: workspaceDir,
68
- stdio: "inherit",
69
- });
68
+ const PNPM_INSTALL_ARGS = ["install", "--reporter=append-only"];
69
+ async function installDeps(workspaceDir, mapLine, extraArgs = []) {
70
+ const progress = new child_output_1.InstallProgress();
71
+ const task = (0, cli_1.getProcessTasks)().start(progress.label);
72
+ try {
73
+ const { code, lines } = await runFramedCommand("pnpm", [...PNPM_INSTALL_ARGS, ...extraArgs], {
74
+ name: "pnpm",
75
+ cwd: workspaceDir,
76
+ env: { ...process.env, npm_config_update_notifier: "false" },
77
+ mapLine,
78
+ onLine: (line) => {
79
+ if (progress.read(line))
80
+ task.update(progress.label);
81
+ },
82
+ });
83
+ if (code !== 0) {
84
+ throw new cli_1.CliError((0, child_output_1.describeChildFailure)({
85
+ title: "Dependency install failed",
86
+ command: "pnpm install",
87
+ code,
88
+ lines,
89
+ isVerbose: (0, cli_1.isVerboseRun)(),
90
+ }));
91
+ }
92
+ task.succeed(progress.doneLabel);
93
+ }
94
+ catch (error) {
95
+ (0, output_1.endInterruptedTask)(task, error, "Dependency install stopped");
96
+ throw error;
97
+ }
70
98
  }
71
99
  const FORWARDED_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
72
100
  const CHILD_SHUTDOWN_TIMEOUT_MS = 5000;
73
- function exitCodeForSignal(signal) {
74
- const number = node_os_1.constants.signals[signal];
75
- return 128 + (typeof number === "number" ? number : 15);
76
- }
77
- function runCommand(command, args, options) {
101
+ function runCommand(command, args, options, onSpawn) {
78
102
  const isWindows = process.platform === "win32";
79
103
  return new Promise((resolve, reject) => {
80
104
  const child = (0, node_child_process_1.spawn)(command, args, {
81
105
  stdio: "inherit",
82
106
  shell: isWindows,
83
107
  ...options,
108
+ detached: !isWindows,
84
109
  });
110
+ onSpawn?.(child);
85
111
  let escalation;
86
112
  let forwarded;
87
113
  const alive = () => child.exitCode === null && child.signalCode === null;
88
- const stopChild = (signal) => {
89
- if (!alive())
114
+ const signalTree = (signal) => {
115
+ if (child.pid === undefined)
90
116
  return;
91
117
  try {
92
- child.kill(isWindows ? "SIGTERM" : signal);
118
+ if (isWindows) {
119
+ (0, node_child_process_1.spawnSync)("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
120
+ stdio: "ignore",
121
+ });
122
+ }
123
+ else {
124
+ process.kill(-child.pid, signal);
125
+ }
93
126
  }
94
127
  catch {
95
128
  }
129
+ };
130
+ const stopChild = (signal) => {
131
+ if (!alive())
132
+ return;
133
+ signalTree(signal);
96
134
  if (escalation || isWindows)
97
135
  return;
98
136
  escalation = setTimeout(() => {
99
- if (alive()) {
100
- try {
101
- child.kill("SIGKILL");
102
- }
103
- catch {
104
- }
105
- }
137
+ if (alive())
138
+ signalTree("SIGKILL");
106
139
  }, CHILD_SHUTDOWN_TIMEOUT_MS);
107
140
  escalation.unref();
108
141
  };
@@ -111,13 +144,8 @@ function runCommand(command, args, options) {
111
144
  stopChild(signal);
112
145
  };
113
146
  const onParentExit = () => {
114
- if (alive()) {
115
- try {
116
- child.kill("SIGKILL");
117
- }
118
- catch {
119
- }
120
- }
147
+ if (alive())
148
+ signalTree("SIGKILL");
121
149
  };
122
150
  for (const signal of FORWARDED_SIGNALS)
123
151
  process.on(signal, onSignal);
@@ -135,22 +163,75 @@ function runCommand(command, args, options) {
135
163
  });
136
164
  child.on("exit", (code, signal) => {
137
165
  cleanup();
166
+ if (forwarded) {
167
+ if (!isWindows)
168
+ signalTree("SIGKILL");
169
+ return reject(new cancellation_1.CancelledError(forwarded));
170
+ }
138
171
  if (code !== null)
139
172
  return resolve(code);
140
- resolve(exitCodeForSignal(signal ?? forwarded ?? "SIGTERM"));
173
+ resolve((0, cancellation_1.exitCodeForSignal)(signal ?? "SIGTERM"));
141
174
  });
142
175
  });
143
176
  }
177
+ const OUTPUT_DRAIN_TIMEOUT_MS = 2000;
178
+ function drained(stream) {
179
+ if (!stream)
180
+ return Promise.resolve();
181
+ return new Promise((settle) => {
182
+ stream.once("end", settle);
183
+ stream.once("error", settle);
184
+ });
185
+ }
186
+ function drainTimeout() {
187
+ return new Promise((settle) => {
188
+ setTimeout(settle, OUTPUT_DRAIN_TIMEOUT_MS).unref();
189
+ });
190
+ }
191
+ async function runFramedCommand(command, args, options) {
192
+ const { name, cwd, env, mapLine = (line) => line, onLine } = options;
193
+ const isVerbose = (0, cli_1.isVerboseRun)();
194
+ const tails = [new child_output_1.OutputTail(), new child_output_1.OutputTail()];
195
+ const readLine = (tail, line) => {
196
+ const mapped = mapLine(line);
197
+ if (isVerbose)
198
+ (0, child_output_1.writeChildLine)(name, mapped);
199
+ const plain = (0, child_output_1.stripAnsi)(mapped);
200
+ tail.push(plain);
201
+ onLine?.(plain);
202
+ };
203
+ let ended = Promise.resolve();
204
+ const code = await runCommand(command, args, { cwd, env, stdio: ["ignore", "pipe", "pipe"] }, (child) => {
205
+ const streams = [child.stdout, child.stderr];
206
+ for (const [index, stream] of streams.entries()) {
207
+ const splitter = new child_output_1.LineSplitter((line) => readLine(tails[index], line));
208
+ stream?.setEncoding("utf8");
209
+ stream?.on("data", (chunk) => splitter.push(chunk));
210
+ stream?.once("end", () => splitter.flush());
211
+ }
212
+ ended = Promise.all(streams.map((stream) => drained(stream)));
213
+ });
214
+ await Promise.race([ended, drainTimeout()]);
215
+ return { code, lines: tails.flatMap((tail) => tail.lines()) };
216
+ }
217
+ function manifestCacheUse(mode, offline) {
218
+ if (offline)
219
+ return "offline";
220
+ return mode === "build" ? "never" : "fallback";
221
+ }
144
222
  async function setupWorkspace(opts) {
145
223
  const { backendUrl, force, mode, offline, clientUrl, bootstrapSecret } = opts;
146
224
  const workspaceKey = opts.workspaceKey ?? (0, config_1.canonicalizeBackendUrl)(backendUrl);
147
225
  const workspaceDir = (0, workspace_1.ensureWorkspace)(workspaceKey);
148
226
  (0, workspace_1.writeWorkspaceMeta)(workspaceDir, backendUrl, workspaceKey);
149
- const resolved = await (0, manifest_1.resolveManifest)(workspaceDir, backendUrl, !!offline, clientUrl, bootstrapSecret);
227
+ const resolved = await (0, manifest_1.resolveManifest)(workspaceDir, backendUrl, manifestCacheUse(mode, offline), clientUrl, bootstrapSecret);
150
228
  const { manifest, fromCache, fetchedAt } = resolved;
151
229
  let layers;
152
230
  if (mode === "dev") {
153
- (0, layers_1.assertLayerPathsServed)(manifest.modules);
231
+ (0, layers_1.assertLayerPathsServed)(manifest.modules, backendUrl, {
232
+ bootstrapSecret,
233
+ cache: fromCache ? { fetchedAt } : undefined,
234
+ });
154
235
  if (fromCache) {
155
236
  (0, manifest_1.assertCachedLayerPathsExist)(manifest.modules, fetchedAt);
156
237
  }
@@ -160,7 +241,12 @@ async function setupWorkspace(opts) {
160
241
  const cacheDir = (0, node_path_1.join)(workspaceDir, ".layers-cache");
161
242
  if (fromCache) {
162
243
  if (!(0, node_fs_1.existsSync)(cacheDir)) {
163
- throw new Error("Backend unreachable and no previously downloaded layers archive — run once with the backend reachable first.");
244
+ throw new cli_1.CliError({
245
+ title: "No layers archive was downloaded for this workspace",
246
+ fixes: [
247
+ "Run once without --offline, with the backend reachable, first",
248
+ ],
249
+ });
164
250
  }
165
251
  (0, layers_1.assertCachedArchivesExist)(manifest.modules, cacheDir, fetchedAt);
166
252
  }
@@ -169,7 +255,7 @@ async function setupWorkspace(opts) {
169
255
  }
170
256
  layers = await (0, layers_1.buildLayersFromCache)(manifest.modules, cacheDir);
171
257
  }
172
- (0, renderer_range_1.assertLayersSupportRenderer)(layers);
258
+ (0, renderer_range_1.assertLayersSupportRenderer)(layers, { workspaceDir });
173
259
  (0, materialize_1.copyStaticTemplates)(workspaceDir);
174
260
  (0, materialize_1.writeWorkspacePackageJson)(workspaceDir, layers);
175
261
  (0, materialize_1.materializeLayers)(workspaceDir, layers);
@@ -178,7 +264,8 @@ async function setupWorkspace(opts) {
178
264
  const layerPaths = layers.map((layer) => layer.path);
179
265
  const shouldInstall = needsInstall(workspaceDir, layerPaths, force);
180
266
  if (shouldInstall) {
181
- installDeps(workspaceDir);
267
+ await opts.beforeInstall?.();
268
+ await installDeps(workspaceDir, (0, child_output_1.createPathMapper)(workspaceDir, layers));
182
269
  saveDepsHash(workspaceDir, layerPaths);
183
270
  }
184
271
  return {
@@ -27,6 +27,7 @@ export interface FrontendManifest {
27
27
  }
28
28
  export interface ResolvedLayer {
29
29
  path: string;
30
+ sourcePath?: string;
30
31
  packageName?: string;
31
32
  priority?: number;
32
33
  configKey?: string;
@@ -42,9 +43,14 @@ export interface WorkspaceEntry {
42
43
  dir: string;
43
44
  backendUrl: string;
44
45
  workspaceKey?: string;
46
+ lastUsedAt: string;
45
47
  }
46
- export declare function describeWorkspace(entry: WorkspaceEntry): string;
47
- export declare function listWorkspaces(): WorkspaceEntry[];
48
+ export interface WorkspaceListing {
49
+ workspaces: WorkspaceEntry[];
50
+ skipped: string[];
51
+ }
52
+ export declare function listWorkspaces(): WorkspaceListing;
53
+ export declare function directorySize(dir: string): number;
48
54
  export declare function writeWorkspaceMeta(workspaceDir: string, backendUrl: string, workspaceKey: string): void;
49
55
  export declare const FRONTEND_MANIFEST_VERSION = 1;
50
56
  export declare function bootstrapHeaders(secret?: string): Record<string, string>;
package/dist/workspace.js CHANGED
@@ -6,8 +6,8 @@ exports.projectWorkspaceKey = projectWorkspaceKey;
6
6
  exports.projectDirFromWorkspaceKey = projectDirFromWorkspaceKey;
7
7
  exports.getWorkspaceDir = getWorkspaceDir;
8
8
  exports.ensureWorkspace = ensureWorkspace;
9
- exports.describeWorkspace = describeWorkspace;
10
9
  exports.listWorkspaces = listWorkspaces;
10
+ exports.directorySize = directorySize;
11
11
  exports.writeWorkspaceMeta = writeWorkspaceMeta;
12
12
  exports.bootstrapHeaders = bootstrapHeaders;
13
13
  exports.isUnauthorized = isUnauthorized;
@@ -38,32 +38,28 @@ function ensureWorkspace(workspaceKey) {
38
38
  (0, node_fs_1.chmodSync)(dir, config_1.WORKSPACE_DIR_MODE);
39
39
  return dir;
40
40
  }
41
- function describeWorkspace(entry) {
42
- const projectDir = projectDirFromWorkspaceKey(entry.workspaceKey);
43
- return projectDir
44
- ? `${entry.backendUrl}, keyed on project ${projectDir}`
45
- : entry.backendUrl;
46
- }
47
41
  function listWorkspaces() {
42
+ const listing = { workspaces: [], skipped: [] };
48
43
  if (!(0, node_fs_1.existsSync)(config_1.DMS_FRONTEND_HOME))
49
- return [];
50
- const workspaces = [];
44
+ return listing;
51
45
  for (const entry of (0, node_fs_1.readdirSync)(config_1.DMS_FRONTEND_HOME, { withFileTypes: true })) {
52
46
  if (!entry.isDirectory())
53
47
  continue;
54
48
  const dir = (0, node_path_1.join)(config_1.DMS_FRONTEND_HOME, entry.name);
55
49
  const meta = readWorkspaceMeta(dir);
56
50
  if (meta?.backendUrl === undefined) {
57
- console.warn(`⚠ Skipping ${dir}: no readable ${WORKSPACE_META_FILE}.`);
51
+ listing.skipped.push(dir);
58
52
  continue;
59
53
  }
60
- workspaces.push({
54
+ listing.workspaces.push({
61
55
  dir,
62
56
  backendUrl: meta.backendUrl,
63
57
  workspaceKey: meta.workspaceKey,
58
+ lastUsedAt: meta.updatedAt ?? metaModifiedAt(dir),
64
59
  });
65
60
  }
66
- return workspaces;
61
+ listing.workspaces.sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
62
+ return listing;
67
63
  }
68
64
  function readWorkspaceMeta(dir) {
69
65
  let meta;
@@ -79,8 +75,47 @@ function readWorkspaceMeta(dir) {
79
75
  workspaceKey: typeof record?.workspaceKey === "string"
80
76
  ? record.workspaceKey
81
77
  : undefined,
78
+ updatedAt: typeof record?.updatedAt === "string" &&
79
+ !Number.isNaN(Date.parse(record.updatedAt))
80
+ ? new Date(record.updatedAt).toISOString()
81
+ : undefined,
82
82
  };
83
83
  }
84
+ function metaModifiedAt(dir) {
85
+ return (0, node_fs_1.statSync)((0, node_path_1.join)(dir, WORKSPACE_META_FILE)).mtime.toISOString();
86
+ }
87
+ function directorySize(dir) {
88
+ const seen = new Set();
89
+ const pending = [dir];
90
+ let total = 0;
91
+ for (let current = pending.pop(); current; current = pending.pop()) {
92
+ let entries;
93
+ try {
94
+ entries = (0, node_fs_1.readdirSync)(current, { withFileTypes: true });
95
+ }
96
+ catch {
97
+ continue;
98
+ }
99
+ for (const entry of entries) {
100
+ const path = (0, node_path_1.join)(current, entry.name);
101
+ if (entry.isDirectory()) {
102
+ pending.push(path);
103
+ continue;
104
+ }
105
+ try {
106
+ const stats = (0, node_fs_1.lstatSync)(path);
107
+ const inode = `${stats.dev}:${stats.ino}`;
108
+ if (stats.nlink > 1 && seen.has(inode))
109
+ continue;
110
+ seen.add(inode);
111
+ total += stats.size;
112
+ }
113
+ catch {
114
+ }
115
+ }
116
+ }
117
+ return total;
118
+ }
84
119
  function writeWorkspaceMeta(workspaceDir, backendUrl, workspaceKey) {
85
120
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, WORKSPACE_META_FILE), JSON.stringify({
86
121
  backendUrl: (0, config_1.canonicalizeBackendUrl)(backendUrl),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms-frontend",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "description": "Frontend-agnostic loader for AntelopeJS DMS, shipping the Vue 3 renderer (Vite, Inertia, SSR)",
5
5
  "keywords": [
6
6
  "antelope",
@@ -55,7 +55,7 @@
55
55
  "scripts": {
56
56
  "build": "rimraf dist && tsc",
57
57
  "prepack": "pnpm run build",
58
- "release": "pnpm run lint && pnpm run test && pnpm run prepack && release-it",
58
+ "release": "pnpm run lint && pnpm run build && pnpm run test && release-it",
59
59
  "test": "node --import tsx --test tests/*.test.ts tests/*.test.mts",
60
60
  "test:real-source": "pnpm run build && node dist/verify-source-runner.js",
61
61
  "format": "oxfmt .",
@@ -66,21 +66,18 @@
66
66
  "knip:all": "knip"
67
67
  },
68
68
  "dependencies": {
69
- "boxen": "^5.1.2",
70
- "chalk": "^4.1.2",
71
69
  "chokidar": "^4.0.3",
72
70
  "commander": "^13.1.0",
73
- "figlet": "^1.9.4",
74
71
  "ignore": "^7.0.5",
75
72
  "semver": "^7.7.4",
76
73
  "unzipper": "^0.12.3"
77
74
  },
78
75
  "devDependencies": {
76
+ "@antelopejs/core": "^1.13.1",
79
77
  "@antelopejs/tooling-configs": ">=0.0.7 <1.0.0",
80
78
  "@iconify/utils": "^3.1.0",
81
79
  "@iconify/vue": "^5.0.1",
82
80
  "@inertiajs/vue3": "^3.7.1",
83
- "@types/figlet": "^1.7.0",
84
81
  "@types/node": "^24.8.1",
85
82
  "@types/semver": "^7.7.1",
86
83
  "@types/unzipper": "^0.10.11",
@@ -104,12 +101,7 @@
104
101
  "vue-i18n": "^11.2.7"
105
102
  },
106
103
  "peerDependencies": {
107
- "@antelopejs/core": ">=1.7.0 <2"
108
- },
109
- "peerDependenciesMeta": {
110
- "@antelopejs/core": {
111
- "optional": true
112
- }
104
+ "@antelopejs/core": ">=1.13.1 <2"
113
105
  },
114
106
  "optionalDependencies": {
115
107
  "@oxc-parser/binding-darwin-arm64": "^0.95.0",
@@ -5,6 +5,10 @@
5
5
  "scripts": {
6
6
  "dev": "vite",
7
7
  "build": "vite build && node compress-assets.mjs && vite build --ssr ssr-renderer.ts --outDir dist/ssr && vite build --config vite.email.config.ts",
8
+ "build:client": "vite build",
9
+ "build:compress": "node compress-assets.mjs",
10
+ "build:ssr": "vite build --ssr ssr-renderer.ts --outDir dist/ssr",
11
+ "build:email": "vite build --config vite.email.config.ts",
8
12
  "typecheck": "node --import ./typecheck-loader.mjs ./node_modules/vue-tsc/bin/vue-tsc.js --noEmit",
9
13
  "start": "node server.mjs"
10
14
  },
@@ -0,0 +1,91 @@
1
+ // What the server tells whoever started it. Started by `ajs dms dev` or
2
+ // `ajs dms start`, it reports over IPC and the CLI words what the user reads.
3
+ // Started on its own, it prints the line itself.
4
+
5
+ import { resolve } from "node:path";
6
+
7
+ const WILDCARD_ADDRESSES = ["0.0.0.0", "::"];
8
+
9
+ async function report(message, line, write) {
10
+ if (!process.send) return write(line);
11
+ await new Promise((sent) => process.send(message, () => sent()));
12
+ }
13
+
14
+ /** The server answers at `address`:`port`. */
15
+ export async function reportReady(address, port) {
16
+ const host = WILDCARD_ADDRESSES.includes(address)
17
+ ? "localhost"
18
+ : address.includes(":")
19
+ ? `[${address}]`
20
+ : address;
21
+ await report(
22
+ { type: "dms:ready", address, port },
23
+ `✓ Server ready on http://${host}:${port}`,
24
+ console.log,
25
+ );
26
+ }
27
+
28
+ /**
29
+ * The server cannot listen: a port taken by another process, an address this
30
+ * host does not have. Reported instead of Node's unhandled-error dump, then
31
+ * the server exits.
32
+ */
33
+ export async function reportListenError(error, host, port) {
34
+ await report(
35
+ {
36
+ type: "dms:listen-error",
37
+ code: error.code,
38
+ message: error.message,
39
+ host,
40
+ port,
41
+ },
42
+ `DMS server cannot listen on ${host}:${port}: ${error.message}`,
43
+ console.error,
44
+ );
45
+ process.exit(1);
46
+ }
47
+
48
+ /**
49
+ * Where a startup error points, when it says: esbuild's location for a config
50
+ * it could not bundle, Rollup's for a plugin that failed on a file.
51
+ */
52
+ function errorLocation(error) {
53
+ const location = error?.errors?.[0]?.location;
54
+ if (location?.file) {
55
+ return {
56
+ file: resolve(location.file),
57
+ line: location.line,
58
+ column: location.column,
59
+ lineText: location.lineText,
60
+ };
61
+ }
62
+ const file = error?.loc?.file ?? error?.id;
63
+ if (typeof file !== "string") return undefined;
64
+ return { file, line: error.loc?.line, column: error.loc?.column };
65
+ }
66
+
67
+ /** The first line of what went wrong, without esbuild's error count. */
68
+ function errorText(error) {
69
+ const text = error?.errors?.[0]?.text;
70
+ if (typeof text === "string") return text;
71
+ const message = error instanceof Error ? error.message : String(error);
72
+ return message.split(/\r?\n/)[0];
73
+ }
74
+
75
+ /**
76
+ * Vite could not start, so no page can be served: the error and where it
77
+ * points instead of a stack trace, then the server exits.
78
+ */
79
+ export async function reportStartError(error) {
80
+ const message = errorText(error);
81
+ const location = errorLocation(error);
82
+ const where = location
83
+ ? `${location.file}${location.line ? `:${location.line}:${location.column ?? 0}` : ""}: `
84
+ : "";
85
+ await report(
86
+ { type: "dms:start-error", message, code: error?.code, location },
87
+ `DMS development server failed to start: ${where}${message}`,
88
+ console.error,
89
+ );
90
+ process.exit(1);
91
+ }