arelos 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-args.js +6 -6
- package/dist/cli-context.js +80 -0
- package/dist/cli.js +45 -13
- package/dist/config.js +40 -11
- package/dist/health.js +62 -1
- package/dist/install-plan.js +73 -12
- package/dist/install.js +148 -88
- package/dist/list.js +57 -0
- package/dist/logs.js +14 -9
- package/dist/paths.js +78 -6
- package/dist/plist.js +19 -7
- package/dist/ports.js +27 -9
- package/dist/registry.js +41 -0
- package/dist/repair.js +11 -7
- package/dist/repo.js +4 -4
- package/dist/scaffold.js +8 -8
- package/dist/services.js +13 -9
- package/dist/status.js +12 -7
- package/dist/uninstall.js +40 -19
- package/dist/update.js +11 -9
- package/package.json +2 -3
package/dist/registry.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global multi-install registry (~/.arelos/installs.json, or
|
|
3
|
+
* ARELOS_REGISTRY_PATH override). Each entry records one named, self-contained
|
|
4
|
+
* install so `arelos` can find/list/select among several installs on one Mac
|
|
5
|
+
* without any of them clobbering a shared config file (0.2.0 mission).
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { dirname } from "node:path";
|
|
9
|
+
import { registryPath } from "./paths.js";
|
|
10
|
+
export function readRegistry() {
|
|
11
|
+
const p = registryPath();
|
|
12
|
+
if (!existsSync(p))
|
|
13
|
+
return [];
|
|
14
|
+
const raw = JSON.parse(readFileSync(p, "utf8"));
|
|
15
|
+
if (!Array.isArray(raw)) {
|
|
16
|
+
throw new Error(`Invalid registry at ${p}: expected an array`);
|
|
17
|
+
}
|
|
18
|
+
return raw;
|
|
19
|
+
}
|
|
20
|
+
/** Atomic write: tmp file + rename, so a reader never sees a partial file. */
|
|
21
|
+
function writeRegistry(entries) {
|
|
22
|
+
const p = registryPath();
|
|
23
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
24
|
+
const tmp = `${p}.tmp`;
|
|
25
|
+
writeFileSync(tmp, `${JSON.stringify(entries, null, 2)}\n`, { mode: 0o600 });
|
|
26
|
+
renameSync(tmp, p);
|
|
27
|
+
}
|
|
28
|
+
/** Append (or replace, by matching root) an entry — a reinstall at the same root updates it in place. */
|
|
29
|
+
export function addRegistryEntry(entry) {
|
|
30
|
+
const entries = readRegistry().filter((e) => e.root !== entry.root);
|
|
31
|
+
entries.push(entry);
|
|
32
|
+
writeRegistry(entries);
|
|
33
|
+
}
|
|
34
|
+
/** Remove the entry for a given root (uninstall). No-op if absent. */
|
|
35
|
+
export function removeRegistryEntry(root) {
|
|
36
|
+
const entries = readRegistry().filter((e) => e.root !== root);
|
|
37
|
+
writeRegistry(entries);
|
|
38
|
+
}
|
|
39
|
+
export function findRegistryEntryByName(name) {
|
|
40
|
+
return readRegistry().find((e) => e.name === name || e.slug === name) ?? null;
|
|
41
|
+
}
|
package/dist/repair.js
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as p from "@clack/prompts";
|
|
7
7
|
import pc from "picocolors";
|
|
8
|
-
import { resolveServiceLabels } from "./config.js";
|
|
8
|
+
import { resolveRoot, resolveServiceLabels } from "./config.js";
|
|
9
9
|
import { runUpdate } from "./update.js";
|
|
10
|
-
import { waitForHealthy } from "./health.js";
|
|
10
|
+
import { formatHealthTimeoutDiagnostics, waitForHealthy } from "./health.js";
|
|
11
|
+
import { lastLines, logPathFor } from "./logs.js";
|
|
11
12
|
import { bootstrapAndStart, installServiceFiles } from "./services.js";
|
|
12
13
|
import { runStreaming } from "./exec.js";
|
|
13
14
|
import { ensureBun } from "./bun-setup.js";
|
|
@@ -40,9 +41,8 @@ export async function runRepairMenu(existing) {
|
|
|
40
41
|
return runRepair(existing);
|
|
41
42
|
}
|
|
42
43
|
if (choice === "reinstall") {
|
|
43
|
-
p.log.message("
|
|
44
|
-
"
|
|
45
|
-
"for a side-by-side install with a different ARELOS_CONFIG_PATH.");
|
|
44
|
+
p.log.message("Just re-run `npx arelos` with a different name — every install is self-contained, so a new name " +
|
|
45
|
+
"creates a fresh, independent install side-by-side with this one (see `arelos list`).");
|
|
46
46
|
return 0;
|
|
47
47
|
}
|
|
48
48
|
return 1;
|
|
@@ -69,13 +69,17 @@ async function runRepair(existing) {
|
|
|
69
69
|
s.stop("Rebuilt.");
|
|
70
70
|
s.start("Re-rendering and re-bootstrapping services…");
|
|
71
71
|
const labels = resolveServiceLabels(existing);
|
|
72
|
-
installServiceFiles(existing.installDir, labels);
|
|
72
|
+
installServiceFiles(existing.installDir, resolveRoot(existing), labels);
|
|
73
73
|
const bootstrap = await bootstrapAndStart(labels);
|
|
74
74
|
s.stop(bootstrap.errors.length ? "Services re-registered with warnings." : "Services re-registered.");
|
|
75
75
|
for (const e of bootstrap.errors)
|
|
76
76
|
console.error(pc.yellow(e));
|
|
77
77
|
s.start("Health check…");
|
|
78
78
|
const health = await waitForHealthy(existing.webPort, existing.vaultPort);
|
|
79
|
-
s.stop(health.healthy ? "Healthy." : "Health check timed out
|
|
79
|
+
s.stop(health.healthy ? "Healthy." : "Health check timed out.");
|
|
80
|
+
if (!health.healthy) {
|
|
81
|
+
console.error(pc.red(formatHealthTimeoutDiagnostics(resolveRoot(existing), (p) => lastLines(p, 10), logPathFor)));
|
|
82
|
+
console.error(pc.dim("\nFull logs: arelos logs"));
|
|
83
|
+
}
|
|
80
84
|
return health.healthy ? 0 : 1;
|
|
81
85
|
}
|
package/dist/repo.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Getting the app source onto disk
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Getting the app source onto disk and keeping it updated (`arelos update`).
|
|
3
|
+
* Supports a `--local-repo <path>` override for development/dry-run testing
|
|
4
|
+
* so we never have to hit GitHub in tests.
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
@@ -40,7 +40,7 @@ export async function currentRevision(installDir) {
|
|
|
40
40
|
const res = await runCapture("git", ["-C", installDir, "rev-parse", "--short", "HEAD"]);
|
|
41
41
|
return res.code === 0 ? res.stdout.trim() : null;
|
|
42
42
|
}
|
|
43
|
-
/** Best-effort "is the local branch behind origin" check (
|
|
43
|
+
/** Best-effort "is the local branch behind origin" check (`arelos status`). */
|
|
44
44
|
export async function isBehindOrigin(installDir) {
|
|
45
45
|
const fetchRes = await runCapture("git", ["-C", installDir, "fetch", "--dry-run"]);
|
|
46
46
|
if (fetchRes.code !== 0)
|
package/dist/scaffold.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Vault scaffolding + supporting install-dir setup
|
|
3
|
-
*
|
|
4
|
-
* overwrites user data (spec §3.2 idempotency rule).
|
|
2
|
+
* Vault scaffolding + supporting install-dir setup. Only copies the template
|
|
3
|
+
* vault when the destination is empty — never overwrites user data.
|
|
5
4
|
*/
|
|
6
5
|
import { cpSync, existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
7
6
|
import { join } from "node:path";
|
|
8
7
|
export class TemplateVaultMissingError extends Error {
|
|
9
8
|
constructor(templateDir) {
|
|
10
9
|
super(`templates/vault/ is missing at ${templateDir}. This is a repo gap — ` +
|
|
11
|
-
`the app repo must ship a seed vault at templates/vault
|
|
10
|
+
`the app repo must ship a seed vault at templates/vault/. Cannot scaffold a new vault without it.`);
|
|
12
11
|
this.name = "TemplateVaultMissingError";
|
|
13
12
|
}
|
|
14
13
|
}
|
|
@@ -34,10 +33,11 @@ export function scaffoldVault(installDir, vaultPath) {
|
|
|
34
33
|
cpSync(templateDir, vaultPath, { recursive: true });
|
|
35
34
|
return { copied: true };
|
|
36
35
|
}
|
|
37
|
-
/** Create <
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
/** Create <root>/logs/service/ — must exist before launchd bootstraps the
|
|
37
|
+
* plists, since they reference it for stdout/stderr. Logs live at the
|
|
38
|
+
* self-contained root, not inside the app checkout (0.2.0 layout). */
|
|
39
|
+
export function ensureLogsDir(root) {
|
|
40
|
+
const dir = join(root, "logs", "service");
|
|
41
41
|
mkdirSync(dir, { recursive: true });
|
|
42
42
|
return dir;
|
|
43
43
|
}
|
package/dist/services.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ties together plist rendering + writing + launchd bootstrap
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Ties together plist rendering + writing + launchd bootstrap. Also used by
|
|
3
|
+
* `--no-service` dry runs, which stop before the launchctl calls (see
|
|
4
|
+
* install.ts).
|
|
5
|
+
*
|
|
6
|
+
* `installDir` is the app checkout (root/app); `root` is the self-contained
|
|
7
|
+
* install root that owns logs/ and config.json (0.2.0 layout). Both are
|
|
8
|
+
* needed because the plist bakes in both {{INSTALL_DIR}} and {{ROOT_DIR}}.
|
|
5
9
|
*/
|
|
6
10
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
11
|
import { join } from "node:path";
|
|
8
12
|
import { bootstrapService, kickstartService } from "./launchd.js";
|
|
9
13
|
import { deriveServiceLabels, launchAgentsDir, plistPath } from "./paths.js";
|
|
10
14
|
import { renderPlistTemplate } from "./plist.js";
|
|
11
|
-
export function renderServicePlists(installDir, labels = deriveServiceLabels(
|
|
15
|
+
export function renderServicePlists(installDir, root, labels = deriveServiceLabels(root)) {
|
|
12
16
|
const specs = [
|
|
13
17
|
{ label: labels.web, templateFile: "web.plist.tmpl" },
|
|
14
18
|
{ label: labels.vault, templateFile: "vault.plist.tmpl" },
|
|
@@ -19,22 +23,22 @@ export function renderServicePlists(installDir, labels = deriveServiceLabels(ins
|
|
|
19
23
|
throw new Error(`Missing plist template: ${templatePath}`);
|
|
20
24
|
}
|
|
21
25
|
const template = readFileSync(templatePath, "utf8");
|
|
22
|
-
const xml = renderPlistTemplate(template, installDir, label);
|
|
26
|
+
const xml = renderPlistTemplate(template, installDir, root, label);
|
|
23
27
|
return { label, templatePath, targetPath: plistPath(label), xml };
|
|
24
28
|
});
|
|
25
29
|
}
|
|
26
30
|
/** Write rendered plists to ~/Library/LaunchAgents and chmod the run scripts. */
|
|
27
|
-
export function installServiceFiles(installDir, labels = deriveServiceLabels(
|
|
31
|
+
export function installServiceFiles(installDir, root, labels = deriveServiceLabels(root)) {
|
|
28
32
|
mkdirSync(launchAgentsDir(), { recursive: true });
|
|
29
|
-
// The plists' StandardOutPath/StandardErrorPath point at <
|
|
33
|
+
// The plists' StandardOutPath/StandardErrorPath point at <root>/logs/service/*.log;
|
|
30
34
|
// launchd needs that directory to exist before it can bootstrap the job (a
|
|
31
35
|
// missing log dir is one confirmed cause of the "Bootstrap failed: 5:
|
|
32
36
|
// Input/output error" field report — see launchd.ts docstring). install.ts
|
|
33
37
|
// already calls ensureLogsDir before this, but repair/update funnel through
|
|
34
38
|
// here too, so guarantee it unconditionally rather than relying on callers
|
|
35
39
|
// to remember.
|
|
36
|
-
mkdirSync(join(
|
|
37
|
-
const rendered = renderServicePlists(installDir, labels);
|
|
40
|
+
mkdirSync(join(root, "logs", "service"), { recursive: true });
|
|
41
|
+
const rendered = renderServicePlists(installDir, root, labels);
|
|
38
42
|
for (const svc of rendered) {
|
|
39
43
|
writeFileSync(svc.targetPath, svc.xml);
|
|
40
44
|
}
|
package/dist/status.js
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `
|
|
3
|
-
*
|
|
2
|
+
* `arelos status`. Config summary + service state + port probes + git revision /
|
|
3
|
+
* behind-origin check. With multiple installs registered, resolves by name
|
|
4
|
+
* (or prompts/lists per resolveInstall's rule).
|
|
4
5
|
*/
|
|
5
6
|
import pc from "picocolors";
|
|
6
|
-
import {
|
|
7
|
+
import { resolveServiceLabels } from "./config.js";
|
|
8
|
+
import { resolveInstall } from "./cli-context.js";
|
|
7
9
|
import { checkVaultHealth, checkWebHealth } from "./health.js";
|
|
8
10
|
import { getServiceStatus } from "./launchd.js";
|
|
9
11
|
import { currentRevision, isBehindOrigin } from "./repo.js";
|
|
10
|
-
export async function statusCommand() {
|
|
11
|
-
const
|
|
12
|
-
if (!
|
|
13
|
-
console.error(
|
|
12
|
+
export async function statusCommand(name) {
|
|
13
|
+
const result = await resolveInstall({ name, interactive: process.stdout.isTTY === true });
|
|
14
|
+
if (!result.ok) {
|
|
15
|
+
console.error(result.message);
|
|
14
16
|
return 1;
|
|
15
17
|
}
|
|
18
|
+
const config = result.install.config;
|
|
16
19
|
console.log(pc.bold(config.displayName));
|
|
20
|
+
if (config.root)
|
|
21
|
+
console.log(` Root: ${config.root}`);
|
|
17
22
|
console.log(` Install dir: ${config.installDir}`);
|
|
18
23
|
console.log(` Vault path: ${config.vaultPath}`);
|
|
19
24
|
console.log(` Web port: ${config.webPort}`);
|
package/dist/uninstall.js
CHANGED
|
@@ -1,31 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* (
|
|
6
|
-
*
|
|
2
|
+
* `arelos uninstall`. Vault deletion is gated behind confirm + a literal typed
|
|
3
|
+
* "DELETE" — a single yes can never destroy notes. This module separates the
|
|
4
|
+
* pure decision logic (shouldDeleteVault) from the destructive I/O
|
|
5
|
+
* (performUninstall) so the gate can be unit tested without touching a
|
|
6
|
+
* filesystem.
|
|
7
|
+
*
|
|
8
|
+
* 0.2.0 self-contained layout: installDir (root/app) and vaultPath (root/vault)
|
|
9
|
+
* are siblings under root, so removing installDir never touches the vault —
|
|
10
|
+
* the pre-0.2.0 "install dir vs vault" gate semantics carry over unchanged.
|
|
11
|
+
* Removing the registry entry is separate from folder/vault deletion (always
|
|
12
|
+
* done, since a stale registry entry pointing at a gone or kept-around
|
|
13
|
+
* install is never useful).
|
|
7
14
|
*/
|
|
8
15
|
import * as p from "@clack/prompts";
|
|
9
16
|
import pc from "picocolors";
|
|
10
17
|
import { existsSync, rmSync, unlinkSync } from "node:fs";
|
|
11
|
-
import {
|
|
18
|
+
import { resolveRoot, resolveServiceLabels } from "./config.js";
|
|
19
|
+
import { resolveInstall } from "./cli-context.js";
|
|
12
20
|
import { bootoutService } from "./launchd.js";
|
|
13
|
-
import { plistPath,
|
|
21
|
+
import { plistPath, installConfigPath, legacyConfigPath } from "./paths.js";
|
|
22
|
+
import { removeRegistryEntry } from "./registry.js";
|
|
14
23
|
/**
|
|
15
24
|
* Pure gate: vault is deleted iff confirmed AND the typed word is exactly
|
|
16
25
|
* "DELETE". Any other input (including case variants, whitespace, empty)
|
|
17
|
-
* preserves the vault. This is the load-bearing safety rule
|
|
18
|
-
*
|
|
26
|
+
* preserves the vault. This is the load-bearing safety rule — kept as an
|
|
27
|
+
* isolated pure function so it's trivially unit-testable.
|
|
19
28
|
*/
|
|
20
29
|
export function shouldDeleteVault(confirmed, typedWord) {
|
|
21
30
|
return confirmed === true && typedWord === "DELETE";
|
|
22
31
|
}
|
|
23
|
-
export async function uninstallCommand() {
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
26
|
-
console.error(
|
|
32
|
+
export async function uninstallCommand(name) {
|
|
33
|
+
const result = await resolveInstall({ name, interactive: process.stdout.isTTY === true });
|
|
34
|
+
if (!result.ok) {
|
|
35
|
+
console.error(result.message);
|
|
27
36
|
return 1;
|
|
28
37
|
}
|
|
38
|
+
const { config, root } = result.install;
|
|
29
39
|
p.intro(pc.bold("Uninstall Arel OS"));
|
|
30
40
|
const labels = resolveServiceLabels(config);
|
|
31
41
|
await bootoutService(labels.web);
|
|
@@ -60,26 +70,37 @@ export async function uninstallCommand() {
|
|
|
60
70
|
}
|
|
61
71
|
}
|
|
62
72
|
const removeConfig = await p.confirm({
|
|
63
|
-
message: "Remove the saved config
|
|
73
|
+
message: "Remove the saved config? Keeping it lets a reinstall remember your settings.",
|
|
64
74
|
initialValue: false,
|
|
65
75
|
});
|
|
66
|
-
performUninstall(config.installDir, config.vaultPath, {
|
|
76
|
+
performUninstall(config.installDir, config.vaultPath, resolveRoot(config), {
|
|
67
77
|
removeInstallDir: !p.isCancel(removeInstallDir) && removeInstallDir,
|
|
68
78
|
deleteVault,
|
|
69
79
|
removeConfig: !p.isCancel(removeConfig) && removeConfig,
|
|
70
80
|
});
|
|
81
|
+
if (root)
|
|
82
|
+
removeRegistryEntry(root);
|
|
71
83
|
p.outro(pc.green("Arel OS uninstalled." + (deleteVault ? "" : " Your vault was preserved.")));
|
|
72
84
|
return 0;
|
|
73
85
|
}
|
|
74
|
-
/**
|
|
75
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Destructive I/O, isolated from prompt flow for testability via direct
|
|
88
|
+
* calls with explicit choices. `root` is passed separately from `installDir`
|
|
89
|
+
* so config.json (which lives at root, a parent of installDir) is removed
|
|
90
|
+
* correctly rather than assumed to live inside installDir.
|
|
91
|
+
*/
|
|
92
|
+
export function performUninstall(installDir, vaultPath, root, choices) {
|
|
76
93
|
if (choices.deleteVault && existsSync(vaultPath)) {
|
|
77
94
|
rmSync(vaultPath, { recursive: true, force: true });
|
|
78
95
|
}
|
|
79
96
|
if (choices.removeInstallDir && existsSync(installDir)) {
|
|
80
97
|
rmSync(installDir, { recursive: true, force: true });
|
|
81
98
|
}
|
|
82
|
-
if (choices.removeConfig
|
|
83
|
-
|
|
99
|
+
if (choices.removeConfig) {
|
|
100
|
+
const cfgPath = installConfigPath(root);
|
|
101
|
+
if (existsSync(cfgPath))
|
|
102
|
+
unlinkSync(cfgPath);
|
|
103
|
+
if (existsSync(legacyConfigPath()))
|
|
104
|
+
unlinkSync(legacyConfigPath());
|
|
84
105
|
}
|
|
85
106
|
}
|
package/dist/update.js
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `
|
|
3
|
-
* re-health-check. Config file untouched.
|
|
2
|
+
* `arelos update`. git pull --ff-only + bun install/build + restart +
|
|
3
|
+
* re-health-check. Config file untouched. With multiple installs registered,
|
|
4
|
+
* resolves by name (or prompts/lists per resolveInstall's rule).
|
|
4
5
|
*/
|
|
5
6
|
import pc from "picocolors";
|
|
6
7
|
import { ensureBun } from "./bun-setup.js";
|
|
7
|
-
import {
|
|
8
|
+
import { resolveRoot, resolveServiceLabels } from "./config.js";
|
|
9
|
+
import { resolveInstall } from "./cli-context.js";
|
|
8
10
|
import { runStreaming } from "./exec.js";
|
|
9
11
|
import { waitForHealthy } from "./health.js";
|
|
10
12
|
import { pullLatest } from "./repo.js";
|
|
11
13
|
import { bootstrapAndStart, installServiceFiles } from "./services.js";
|
|
12
|
-
export async function updateCommand() {
|
|
13
|
-
const
|
|
14
|
-
if (!
|
|
15
|
-
console.error(
|
|
14
|
+
export async function updateCommand(name) {
|
|
15
|
+
const result = await resolveInstall({ name, interactive: process.stdout.isTTY === true });
|
|
16
|
+
if (!result.ok) {
|
|
17
|
+
console.error(result.message);
|
|
16
18
|
return 1;
|
|
17
19
|
}
|
|
18
|
-
return runUpdate(config);
|
|
20
|
+
return runUpdate(result.install.config);
|
|
19
21
|
}
|
|
20
22
|
export async function runUpdate(config) {
|
|
21
23
|
console.log(`Updating ${config.installDir}…`);
|
|
@@ -45,7 +47,7 @@ export async function runUpdate(config) {
|
|
|
45
47
|
console.warn(pc.yellow("bun run build failed — keeping old dist/. The web service will keep serving it."));
|
|
46
48
|
}
|
|
47
49
|
const labels = resolveServiceLabels(config);
|
|
48
|
-
installServiceFiles(config.installDir, labels);
|
|
50
|
+
installServiceFiles(config.installDir, resolveRoot(config), labels);
|
|
49
51
|
const bootstrap = await bootstrapAndStart(labels);
|
|
50
52
|
for (const e of bootstrap.errors)
|
|
51
53
|
console.error(pc.yellow(e));
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arelos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Installer and service manager for a self-hosted Arel OS on macOS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"arelos": "dist/cli.js"
|
|
8
|
-
"rlo": "dist/cli.js"
|
|
7
|
+
"arelos": "dist/cli.js"
|
|
9
8
|
},
|
|
10
9
|
"files": [
|
|
11
10
|
"dist"
|