@ejunz/ea-core 0.1.2-rc.5 → 0.1.3

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/lib/bin.js CHANGED
@@ -1,10 +1,42 @@
1
1
  #!/usr/bin/env node
2
- import { n as cliManifestPath } from "./cli-paths-Bh47PAKn.js";
3
- import { t as installHostToolBridge } from "./host-tools-CgS9hPeN.js";
4
- import { readFileSync } from "node:fs";
5
- import { loadLayeredEnv } from "@ejunz/app-boot";
2
+ import { n as cliManifestPath } from "./cli-paths-Cpxmk9zd.js";
3
+ import { t as installHostToolBridge } from "./host-tools-BVh5Ga7o.js";
4
+ import { copyFileSync, existsSync, readFileSync } from "node:fs";
5
+ import { resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { load } from "js-yaml";
8
+ import { loadLayeredEnv, loadProfile } from "@ejunz/ea-core/app-boot";
6
9
  import { Command, CommanderError } from "commander";
7
- //#region lib/types/commands/args.js
10
+ //#region packages/profile-ejunz/lib/index.js
11
+ /** Standalone Ejunz profile configuration file. */
12
+ const EXAMPLE_PATH = fileURLToPath(new URL("../config.agent.example.yaml", import.meta.url));
13
+ function record(value) {
14
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
15
+ }
16
+ /** Create config.agent.yaml from the shipped example, then validate its bridge values. */
17
+ function loadEjunzAgentConfig(cwd = process.cwd()) {
18
+ const configPath = resolve(cwd, "config.agent.yaml");
19
+ if (!existsSync(configPath)) {
20
+ copyFileSync(EXAMPLE_PATH, configPath);
21
+ console.info(`Created ${configPath} from ${EXAMPLE_PATH}; fill bridge.origin and bridge.token, then retry.`);
22
+ }
23
+ let document;
24
+ try {
25
+ document = load(readFileSync(configPath, "utf8"));
26
+ } catch (error) {
27
+ throw new Error(`profile-ejunz: ${configPath} is not valid YAML: ${String(error)}`);
28
+ }
29
+ const bridge = record(record(document)?.bridge);
30
+ const origin = typeof bridge?.origin === "string" ? bridge.origin.trim() : "";
31
+ const token = typeof bridge?.token === "string" ? bridge.token.trim() : "";
32
+ if (origin === "" || token === "") throw new Error(`profile-ejunz: fill bridge.origin and bridge.token in ${configPath}`);
33
+ return {
34
+ origin,
35
+ token
36
+ };
37
+ }
38
+ //#endregion
39
+ //#region src/commands/args.ts
8
40
  /**
9
41
  * Commander adapter for the `ea` command line.
10
42
  *
@@ -12,10 +44,10 @@ import { Command, CommanderError } from "commander";
12
44
  * patch overlays to apply, and the config dumps — and hands **everything after
13
45
  * its own flags** to the booted tree verbatim, where injected app plugins parse
14
46
  * their own flag families and print their own `--help` (see
15
- * `@ejunz/cmdline`). Launcher flags therefore come first: the first
47
+ * `@ejunz/ea-core/cmdline`). Launcher flags therefore come first: the first
16
48
  * token this parser does not recognize starts the inner arguments, so
17
- * `ea --profile tui --resume abc` boots the tui profile with `--resume abc`,
18
- * `plugin` manages a profile's plugin dependencies by forwarding to yarn.
49
+ * `ea --profile web --port 8080` boots the web profile with `--port 8080`,
50
+ * and `plugin` manages a profile's dependencies by forwarding to yarn.
19
51
  * @module @ejunz/ea-core/args
20
52
  */
21
53
  /**
@@ -26,11 +58,11 @@ const collect = (value, previous = []) => [...previous, value];
26
58
  /** The launcher's own help text; each app prints its own. */
27
59
  const HELP_EXAMPLES = `
28
60
  Examples:
29
- ea --profile headless "run the tests" answer one task, print the result, and exit
30
- ea --profile tui --patch ./extra.yml boot a custom profile with one extra overlay
31
- ea --profile tui --resume <session> arguments after the launcher flags reach the app
32
- ea node join the server named in $EA_HOME/agent.yaml
33
- ea plugin --profile tui add <package> install a plugin into the tui profile
61
+ ea --profile web boot the generic Web profile
62
+ ea --profile web --debug include Cordis plugin activation logs
63
+ ea --profile ejunz boot with Ejunz-hosted services
64
+ ea --profile web --port 8080 pass app arguments after launcher flags
65
+ ea plugin --profile web add <package> install a plugin into the web profile
34
66
  `;
35
67
  /**
36
68
  * Resolve a boot or dump invocation from the launcher flags and the leftover
@@ -48,7 +80,8 @@ function resolveBoot(program, profile, options, args) {
48
80
  mode: "profile",
49
81
  profile,
50
82
  patches,
51
- args
83
+ args,
84
+ debug: options.debug === true
52
85
  };
53
86
  if (options.dumpConfig === true && options.dumpDefaultConfig === true) program.error("error: --dump-config and --dump-default-config are mutually exclusive");
54
87
  if (args.length > 0) program.error(`error: config dumps take no app arguments, got ${args.map((argument) => JSON.stringify(argument)).join(" ")}`);
@@ -71,7 +104,7 @@ function resolveBoot(program, profile, options, args) {
71
104
  function parseEaArgs(argv, version) {
72
105
  let resolved;
73
106
  const program = new Command();
74
- program.name("ea").version(version, "-V, --version", "output the version number").description("ea: boot a EjunzAgent profile — an ordered stack of plugin-bundle patch layers under your own overrides.").addHelpText("after", HELP_EXAMPLES).exitOverride().helpOption(false).allowUnknownOption().passThroughOptions().enablePositionalOptions().argument("[args...]", "arguments for the booted profile's app (see: ea --profile <name> --help)").option("--profile <name>", "the profile under $EA_HOME/profiles to boot").option("--patch <path>", "extra patch-list overlay applied after the profile layer (repeatable)", collect).option("--dump-config", "print the composed profile tree and exit").option("--dump-default-config", "print the profile tree without its user layer or --patch overlays and exit").action((args, options) => {
107
+ program.name("ea").version(version, "-V, --version", "output the version number").description("ea: boot a EjunzAgent profile — an ordered stack of plugin-bundle patch layers under your own overrides.").addHelpText("after", HELP_EXAMPLES).exitOverride().helpOption(false).allowUnknownOption().passThroughOptions().enablePositionalOptions().argument("[args...]", "arguments for the booted profile's app (see: ea --profile <name> --help)").option("--profile <name>", "the profile under $EA_HOME/profiles to boot").option("--debug", "log profile plugin activation and lifecycle events").option("--patch <path>", "extra patch-list overlay applied after the profile layer (repeatable)", collect).option("--dump-config", "print the composed profile tree and exit").option("--dump-default-config", "print the profile tree without its user layer or --patch overlays and exit").action((args, options) => {
75
108
  if (options.profile === void 0) {
76
109
  if (args.some((argument) => argument === "-h" || argument === "--help")) program.help();
77
110
  program.error("error: --profile <name> is required");
@@ -83,15 +116,8 @@ function parseEaArgs(argv, version) {
83
116
  /** Reject parent options supplied before a subcommand. */
84
117
  const rejectParentOptions = (command) => {
85
118
  const parent = program.opts();
86
- if (parent.profile !== void 0 || parent.patch !== void 0 || parent.dumpConfig !== void 0 || parent.dumpDefaultConfig !== void 0) program.error(`error: ${command} takes none of parent --profile, --patch, --dump-config, or --dump-default-config`);
119
+ if (parent.profile !== void 0 || parent.patch !== void 0 || parent.debug !== void 0 || parent.dumpConfig !== void 0 || parent.dumpDefaultConfig !== void 0) program.error(`error: ${command} takes none of parent --profile, --debug, --patch, --dump-config, or --dump-default-config`);
87
120
  };
88
- program.command("node").description("join a server that runs elsewhere, reading $EA_HOME/agent.yaml; the node surface's own flags follow").helpOption(false).allowUnknownOption().passThroughOptions().enablePositionalOptions().argument("[args...]", "arguments for the node surface (e.g. --url <server>, --label <name>)").action((args) => {
89
- rejectParentOptions("node");
90
- resolved = {
91
- mode: "node",
92
- args
93
- };
94
- });
95
121
  program.command("plugin").description("manage a profile's plugins by forwarding the remaining arguments to yarn in the profile directory").requiredOption("--profile <name>", "the profile whose plugins to manage (initialized on first use)").allowUnknownOption().argument("[args...]", "yarn arguments, forwarded verbatim (add <pkg>, remove <pkg>, why <pkg>, ...)").action((args, options) => {
96
122
  rejectParentOptions("plugin");
97
123
  if (options.profile === "") program.error("error: --profile needs a name");
@@ -112,7 +138,7 @@ function parseEaArgs(argv, version) {
112
138
  return resolved;
113
139
  }
114
140
  //#endregion
115
- //#region lib/types/entry/bin.js
141
+ //#region src/entry/bin.ts
116
142
  /**
117
143
  * ea — command-line entry. Dynamic imports per mode keep unrelated modes out
118
144
  * of each dispatch path; the adapter prints and exits for
@@ -120,6 +146,11 @@ function parseEaArgs(argv, version) {
120
146
  * @module @ejunz/ea-core/bin
121
147
  */
122
148
  /* v8 ignore file -- built-bin acceptance exercises this self-executing dispatch. */
149
+ function configText(config, key) {
150
+ if (typeof config !== "object" || config === null) return void 0;
151
+ const value = config[key];
152
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
153
+ }
123
154
  /** This app's version, read from its checked-in package.json. */
124
155
  function readVersion() {
125
156
  const manifest = JSON.parse(readFileSync(cliManifestPath(), "utf8"));
@@ -128,44 +159,50 @@ function readVersion() {
128
159
  const invocation = parseEaArgs(process.argv.slice(2), readVersion());
129
160
  switch (invocation.mode) {
130
161
  case "profile": {
131
- const { runProfile } = await import("./profile-boot-DB0YtW82.js");
162
+ const { runProfile } = await import("./profile-boot-CSZwEw6O.js");
163
+ const ejunzConfig = loadProfile("ea", invocation.profile, cliManifestPath()).layers.some((layer) => layer.packageName === "@ejunz/profile-ejunz") ? loadEjunzAgentConfig() : void 0;
164
+ const profileConfigPatches = ejunzConfig === void 0 ? [] : [{
165
+ id: "storage-ejunz",
166
+ config: ejunzConfig
167
+ }, {
168
+ id: "session-persistence-ejunz",
169
+ config: ejunzConfig
170
+ }];
132
171
  await runProfile({
133
172
  environment: loadLayeredEnv("ea"),
134
173
  profile: invocation.profile,
135
174
  patchFiles: invocation.patches,
175
+ profileConfigPatches,
136
176
  args: invocation.args,
137
- prepare: async (ctx) => {
138
- const origin = process.env.EJUNZ_AGENT_DATA_ORIGIN;
139
- const token = process.env.EJUNZ_AGENT_DATA_TOKEN;
140
- if (origin && token) await installHostToolBridge(ctx, {
177
+ debug: invocation.debug,
178
+ prepare: async (ctx, composed) => {
179
+ if (!["storage-ejunz", "session-persistence-ejunz"].some((id) => {
180
+ const row = composed.rows.get(id);
181
+ return row !== void 0 && row.disabled !== true;
182
+ })) return;
183
+ const storageConfig = composed.rows.get("storage-ejunz")?.config;
184
+ const persistenceConfig = composed.rows.get("session-persistence-ejunz")?.config;
185
+ const origin = configText(storageConfig, "origin");
186
+ const token = configText(storageConfig, "token");
187
+ const persistenceOrigin = configText(persistenceConfig, "origin");
188
+ const persistenceToken = configText(persistenceConfig, "token");
189
+ if (!origin || !token || !persistenceOrigin || !persistenceToken) throw new Error("ea: config.agent.yaml must provide the Ejunz bridge for storage-ejunz and session-persistence-ejunz");
190
+ if (origin !== persistenceOrigin || token !== persistenceToken) throw new Error("ea: storage-ejunz and session-persistence-ejunz must use the same Ejunz bridge origin and token");
191
+ await installHostToolBridge(ctx, {
141
192
  origin,
142
193
  token
143
194
  });
144
- else console.warn(`[host-tools] bridge unavailable origin=${origin ? "set" : "missing"} token=${token ? "set" : "missing"}`);
145
195
  }
146
196
  });
147
197
  break;
148
198
  }
149
- case "node": {
150
- const { runNode, parseNodeArgs } = await import("./node-DzoSnntW.js");
151
- const { surfaceLogger } = await import("./surface-log-fjX8orGn.js");
152
- try {
153
- await runNode(parseNodeArgs(invocation.args));
154
- } catch (error) {
155
- const logger = surfaceLogger("ea-node");
156
- if (error instanceof Error) logger.error(error.stack ?? error.message);
157
- else logger.error(String(error));
158
- process.exitCode = 1;
159
- }
160
- break;
161
- }
162
199
  case "plugin": {
163
- const { runPlugin } = await import("./plugin-BBl1ViBd.js");
200
+ const { runPlugin } = await import("./plugin-CUTzbaEG.js");
164
201
  process.exit(runPlugin(invocation.profile, invocation.args));
165
202
  break;
166
203
  }
167
204
  case "dump-config": {
168
- const { runDumpConfig } = await import("./dump-config-CD_CP42p.js");
205
+ const { runDumpConfig } = await import("./dump-config-DxXgKDVl.js");
169
206
  runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches);
170
207
  break;
171
208
  }
@@ -0,0 +1,21 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ //#region src/lib/cli-paths.ts
5
+ const moduleDirectory = dirname(fileURLToPath(import.meta.url));
6
+ const sourceAppDirectory = join(moduleDirectory, "../..");
7
+ const bundledAppDirectory = join(moduleDirectory, "..");
8
+ /** Resolve an asset owned by the CLI package from source or bundled execution. */
9
+ function cliAssetPath(relativePath) {
10
+ const sourcePath = join(sourceAppDirectory, relativePath);
11
+ if (existsSync(sourcePath)) return sourcePath;
12
+ const bundledPath = join(bundledAppDirectory, relativePath);
13
+ if (existsSync(bundledPath)) return bundledPath;
14
+ return sourcePath;
15
+ }
16
+ /** Resolve the CLI package manifest from source or bundled execution. */
17
+ function cliManifestPath() {
18
+ return cliAssetPath("package.json");
19
+ }
20
+ //#endregion
21
+ export { cliManifestPath as n, cliAssetPath as t };
@@ -0,0 +1,52 @@
1
+ import { a as homePatchPath, n as PROFILE_ROOT_FILENAME, o as prepareProfile } from "./profile-boot-DRHYAiGs.js";
2
+ import { existsSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { loadOptionalPatches, loadOverlayPatches, renderConfigDump } from "@ejunz/ea-core/app-boot";
5
+ //#region src/commands/dump-config.ts
6
+ /**
7
+ * Config-dump entry for `ea --profile <name> --dump-config`: compose the
8
+ * profile's patch layers through the include plugin's patch algorithm without
9
+ * booting or evaluating `!!js`, with one source layer per bundle, the
10
+ * profile's own patch file, and each `--patch` overlay.
11
+ * @module @ejunz/ea-core/dump-config
12
+ */
13
+ const NAME = "ea";
14
+ /* v8 ignore start -- built-bin acceptance drives this boot-free dispatch */
15
+ /**
16
+ * Print a profile composition with comments naming each source file and patch layer.
17
+ * @param profile - the profile name.
18
+ * @param defaultOnly - omit the profile's user layer and `--patch` overlays
19
+ * (the recovery diagnostic for a broken `cordis.patch.yml`, which is then
20
+ * never parsed).
21
+ * @param patches - `--patch` overlay paths, in argv order.
22
+ */
23
+ function runDumpConfig(profile, defaultOnly, patches) {
24
+ const loaded = prepareProfile(profile, !defaultOnly);
25
+ const layers = loaded.layers.map((layer) => ({
26
+ label: layer.packageName,
27
+ patches: layer.patches
28
+ }));
29
+ if (!defaultOnly) {
30
+ if (existsSync(loaded.patchPath)) layers.push({
31
+ label: loaded.patchPath,
32
+ patches: loaded.patches
33
+ });
34
+ const homePatchFile = homePatchPath();
35
+ const homePatches = loadOptionalPatches(NAME, homePatchFile);
36
+ if (homePatches !== void 0) layers.push({
37
+ label: homePatchFile,
38
+ patches: homePatches
39
+ });
40
+ for (const file of patches) {
41
+ const absolute = resolve(file);
42
+ layers.push({
43
+ label: absolute,
44
+ patches: loadOverlayPatches(NAME, absolute)
45
+ });
46
+ }
47
+ }
48
+ process.stdout.write(renderConfigDump(NAME, join(loaded.dir, PROFILE_ROOT_FILENAME), layers));
49
+ }
50
+ /* v8 ignore stop */
51
+ //#endregion
52
+ export { runDumpConfig };
@@ -0,0 +1,52 @@
1
+ import { a as homePatchPath, n as PROFILE_ROOT_FILENAME, o as prepareProfile } from "./profile-boot-BqakeoCE.js";
2
+ import { existsSync } from "node:fs";
3
+ import { loadOptionalPatches, loadOverlayPatches, renderConfigDump } from "@ejunz/ea-core/app-boot";
4
+ import { join, resolve } from "node:path";
5
+ //#region src/commands/dump-config.ts
6
+ /**
7
+ * Config-dump entry for `ea --profile <name> --dump-config`: compose the
8
+ * profile's patch layers through the include plugin's patch algorithm without
9
+ * booting or evaluating `!!js`, with one source layer per bundle, the
10
+ * profile's own patch file, and each `--patch` overlay.
11
+ * @module @ejunz/ea-core/dump-config
12
+ */
13
+ const NAME = "ea";
14
+ /* v8 ignore start -- built-bin acceptance drives this boot-free dispatch */
15
+ /**
16
+ * Print a profile composition with comments naming each source file and patch layer.
17
+ * @param profile - the profile name.
18
+ * @param defaultOnly - omit the profile's user layer and `--patch` overlays
19
+ * (the recovery diagnostic for a broken `cordis.patch.yml`, which is then
20
+ * never parsed).
21
+ * @param patches - `--patch` overlay paths, in argv order.
22
+ */
23
+ function runDumpConfig(profile, defaultOnly, patches) {
24
+ const loaded = prepareProfile(profile, !defaultOnly);
25
+ const layers = loaded.layers.map((layer) => ({
26
+ label: layer.packageName,
27
+ patches: layer.patches
28
+ }));
29
+ if (!defaultOnly) {
30
+ if (existsSync(loaded.patchPath)) layers.push({
31
+ label: loaded.patchPath,
32
+ patches: loaded.patches
33
+ });
34
+ const homePatchFile = homePatchPath();
35
+ const homePatches = loadOptionalPatches(NAME, homePatchFile);
36
+ if (homePatches !== void 0) layers.push({
37
+ label: homePatchFile,
38
+ patches: homePatches
39
+ });
40
+ for (const file of patches) {
41
+ const absolute = resolve(file);
42
+ layers.push({
43
+ label: absolute,
44
+ patches: loadOverlayPatches(NAME, absolute)
45
+ });
46
+ }
47
+ }
48
+ process.stdout.write(renderConfigDump(NAME, join(loaded.dir, PROFILE_ROOT_FILENAME), layers));
49
+ }
50
+ /* v8 ignore stop */
51
+ //#endregion
52
+ export { runDumpConfig };