@mybase-dev/cli 1.0.4 → 1.0.6
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/mybase.js +122 -22
- package/package.json +1 -1
package/dist/mybase.js
CHANGED
|
@@ -139,7 +139,7 @@ function parseDotenv(text) {
|
|
|
139
139
|
// package.json
|
|
140
140
|
var package_default = {
|
|
141
141
|
name: "@mybase-dev/cli",
|
|
142
|
-
version: "1.0.
|
|
142
|
+
version: "1.0.6",
|
|
143
143
|
description: "The mybase CLI: projects, keys, cron, queues, cache, rate limits, analytics, docs, and agent setup for a self-hosted mybase.",
|
|
144
144
|
type: "module",
|
|
145
145
|
repository: {
|
|
@@ -305,6 +305,31 @@ function parseDelay(value) {
|
|
|
305
305
|
return Math.round(Number(m[1]) * UNITS[m[2]]);
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
// src/service-enable.ts
|
|
309
|
+
async function linkedEnvs(project) {
|
|
310
|
+
const overview = await api(`/projects/${encodeURIComponent(project)}/secrets`).catch(() => null);
|
|
311
|
+
return overview?.envs.filter((e) => e.linked).map((e) => e.name) ?? [];
|
|
312
|
+
}
|
|
313
|
+
async function enableService(project, id, flags) {
|
|
314
|
+
const envs = flags.has("no-link") ? [] : await linkedEnvs(project);
|
|
315
|
+
let link = envs.length > 0 && flags.has("link");
|
|
316
|
+
if (envs.length > 0 && !link && !jsonMode && process.stdin.isTTY) {
|
|
317
|
+
const answer = prompt(`Add the ${id} keys to the linked secrets envs (${envs.join(", ")})? [Y/n]`);
|
|
318
|
+
link = !answer?.trim() || /^y/i.test(answer.trim());
|
|
319
|
+
}
|
|
320
|
+
const path = `/projects/${encodeURIComponent(project)}/services/${id}`;
|
|
321
|
+
const result = await api(path, link ? { method: "PUT", body: JSON.stringify({ link: true }) } : { method: "PUT" });
|
|
322
|
+
return { result, linked: link ? envs : [] };
|
|
323
|
+
}
|
|
324
|
+
function printLinked(project, linked, flags) {
|
|
325
|
+
if (linked.length > 0)
|
|
326
|
+
console.log(`
|
|
327
|
+
Keys added to the linked secrets envs: ${linked.join(", ")} (mybase run -p ${project} -e ${linked[0]} -- \u2026)`);
|
|
328
|
+
else if (flags.has("link"))
|
|
329
|
+
console.log(`
|
|
330
|
+
No linked secrets envs yet: mybase secrets link -p ${project} -e prod`);
|
|
331
|
+
}
|
|
332
|
+
|
|
308
333
|
// src/cron.ts
|
|
309
334
|
var cronUsage = ` cron enable <project> Turn on cron for a project and print its signing secret
|
|
310
335
|
cron disable <project> [--yes] Turn it off, deleting every job and its history
|
|
@@ -388,12 +413,13 @@ async function cron(sub, args) {
|
|
|
388
413
|
switch (sub) {
|
|
389
414
|
case "enable": {
|
|
390
415
|
const p = need(project, "project name");
|
|
391
|
-
|
|
416
|
+
const { linked } = await enableService(p, "cron", flags);
|
|
392
417
|
const secret = await api(`/projects/${encodeURIComponent(p)}/cron/secret`);
|
|
393
|
-
print({ enabled: "cron", project: p, ...secret }, () => {
|
|
418
|
+
print({ enabled: "cron", project: p, linked, ...secret }, () => {
|
|
394
419
|
console.log(`Cron enabled for "${p}"
|
|
395
420
|
`);
|
|
396
421
|
printSecret(secret);
|
|
422
|
+
printLinked(p, linked, flags);
|
|
397
423
|
});
|
|
398
424
|
return true;
|
|
399
425
|
}
|
|
@@ -881,12 +907,14 @@ async function analytics(sub, args) {
|
|
|
881
907
|
switch (sub) {
|
|
882
908
|
case "enable": {
|
|
883
909
|
const p = need2(project, "project name");
|
|
884
|
-
|
|
910
|
+
const flags = parseArgs(args).flags;
|
|
911
|
+
const { linked } = await enableService(p, "analytics", flags);
|
|
885
912
|
const settings = await api(`${base(p)}/settings`);
|
|
886
|
-
print({ enabled: "analytics", project: p, ...settings }, () => {
|
|
913
|
+
print({ enabled: "analytics", project: p, linked, ...settings }, () => {
|
|
887
914
|
console.log(`Analytics enabled for "${p}"
|
|
888
915
|
`);
|
|
889
916
|
printSettings(p, settings);
|
|
917
|
+
printLinked(p, linked, flags);
|
|
890
918
|
});
|
|
891
919
|
return true;
|
|
892
920
|
}
|
|
@@ -1351,13 +1379,14 @@ async function cache(sub, args) {
|
|
|
1351
1379
|
switch (sub) {
|
|
1352
1380
|
case "enable": {
|
|
1353
1381
|
const p = need(project, "project name");
|
|
1354
|
-
const res = await
|
|
1355
|
-
if (res
|
|
1382
|
+
const { result: res, linked } = await enableService(p, "cache", flags);
|
|
1383
|
+
if (res?.service !== "cache")
|
|
1356
1384
|
throw new Error("unexpected answer from the API");
|
|
1357
|
-
print({ enabled: "cache", project: p, ...res.credentials }, () => {
|
|
1385
|
+
print({ enabled: "cache", project: p, linked, ...res.credentials }, () => {
|
|
1358
1386
|
console.log(`Cache enabled for "${p}"
|
|
1359
1387
|
`);
|
|
1360
1388
|
printCredentials(p, res.credentials);
|
|
1389
|
+
printLinked(p, linked, flags);
|
|
1361
1390
|
});
|
|
1362
1391
|
return true;
|
|
1363
1392
|
}
|
|
@@ -1419,12 +1448,13 @@ async function captcha(sub, args) {
|
|
|
1419
1448
|
switch (sub) {
|
|
1420
1449
|
case "enable": {
|
|
1421
1450
|
const p = need(project, "project name");
|
|
1422
|
-
|
|
1451
|
+
const { linked } = await enableService(p, "captcha", flags);
|
|
1423
1452
|
const s = await api(base3(p));
|
|
1424
|
-
print({ enabled: "captcha", project: p, ...s }, () => {
|
|
1453
|
+
print({ enabled: "captcha", project: p, linked, ...s }, () => {
|
|
1425
1454
|
console.log(`Captcha enabled for "${p}"
|
|
1426
1455
|
`);
|
|
1427
1456
|
printSettings2(p, s);
|
|
1457
|
+
printLinked(p, linked, flags);
|
|
1428
1458
|
});
|
|
1429
1459
|
return true;
|
|
1430
1460
|
}
|
|
@@ -1570,12 +1600,13 @@ async function queue(sub, args) {
|
|
|
1570
1600
|
switch (sub) {
|
|
1571
1601
|
case "enable": {
|
|
1572
1602
|
const p = need(project, "project name");
|
|
1573
|
-
|
|
1603
|
+
const { linked } = await enableService(p, "queue", flags);
|
|
1574
1604
|
const settings = await api(base4(p));
|
|
1575
|
-
print({ enabled: "queue", project: p, ...settings }, () => {
|
|
1605
|
+
print({ enabled: "queue", project: p, linked, ...settings }, () => {
|
|
1576
1606
|
console.log(`Queues enabled for "${p}"
|
|
1577
1607
|
`);
|
|
1578
1608
|
printUsage(p, settings);
|
|
1609
|
+
printLinked(p, linked, flags);
|
|
1579
1610
|
});
|
|
1580
1611
|
return true;
|
|
1581
1612
|
}
|
|
@@ -1770,12 +1801,13 @@ async function vector(sub, args) {
|
|
|
1770
1801
|
switch (sub) {
|
|
1771
1802
|
case "enable": {
|
|
1772
1803
|
const p = need(project, "project name");
|
|
1773
|
-
|
|
1804
|
+
const { linked } = await enableService(p, "vector", flags);
|
|
1774
1805
|
const settings = await api(base5(p));
|
|
1775
|
-
print({ enabled: "vector", project: p, ...settings }, () => {
|
|
1806
|
+
print({ enabled: "vector", project: p, linked, ...settings }, () => {
|
|
1776
1807
|
console.log(`Vector search enabled for "${p}"
|
|
1777
1808
|
`);
|
|
1778
1809
|
printUsage2(p, settings);
|
|
1810
|
+
printLinked(p, linked, flags);
|
|
1779
1811
|
});
|
|
1780
1812
|
return true;
|
|
1781
1813
|
}
|
|
@@ -1956,12 +1988,13 @@ async function ratelimit(sub, args) {
|
|
|
1956
1988
|
switch (sub) {
|
|
1957
1989
|
case "enable": {
|
|
1958
1990
|
const p = need(project, "project name");
|
|
1959
|
-
|
|
1991
|
+
const { linked } = await enableService(p, "ratelimit", flags);
|
|
1960
1992
|
const settings = await api(base6(p));
|
|
1961
|
-
print({ enabled: "ratelimit", project: p, ...settings }, () => {
|
|
1993
|
+
print({ enabled: "ratelimit", project: p, linked, ...settings }, () => {
|
|
1962
1994
|
console.log(`Rate limits enabled for "${p}"
|
|
1963
1995
|
`);
|
|
1964
1996
|
printUsage3(p, settings);
|
|
1997
|
+
printLinked(p, linked, flags);
|
|
1965
1998
|
});
|
|
1966
1999
|
return true;
|
|
1967
2000
|
}
|
|
@@ -2063,8 +2096,11 @@ var secretsUsage = ` secrets enable <project> Turn on secrets (dev, stagi
|
|
|
2063
2096
|
secrets history [--limit 20] Revisions, newest first
|
|
2064
2097
|
secrets restore <revision> [--yes] Put values back as they were after <revision>
|
|
2065
2098
|
secrets link --env <env,\u2026> [--yes] Rotate the project's DB/S3/cache credentials and keep them
|
|
2066
|
-
in these envs (DATABASE_URL, S3_*, REDIS_URL, MYBASE_URL)
|
|
2099
|
+
in these envs (DATABASE_URL, S3_*, REDIS_URL, MYBASE_URL), with
|
|
2100
|
+
every service's keys (MYBASE_API_KEY, MYBASE_CRON_SECRET, \u2026)
|
|
2067
2101
|
secrets unlink [--yes] Remove the credentials from the env (they stay valid)
|
|
2102
|
+
\`<service> enable\` asks to add that service's keys to linked
|
|
2103
|
+
envs (--link / --no-link to skip the question)
|
|
2068
2104
|
Project/env: -p/--project, -e/--env, else MYBASE_PROJECT/MYBASE_ENV,
|
|
2069
2105
|
else \`secrets setup\` for this directory`;
|
|
2070
2106
|
var expandShortFlags = (args) => args.map((a) => a === "-p" ? "--project" : a === "-e" ? "--env" : a);
|
|
@@ -2240,7 +2276,7 @@ async function secrets(sub, args) {
|
|
|
2240
2276
|
case "link": {
|
|
2241
2277
|
const { project, env } = await target(flags);
|
|
2242
2278
|
const envs = env.split(",").map((e) => e.trim()).filter(Boolean);
|
|
2243
|
-
confirm(yes, `This rotates the credentials of "${project}" (database password, storage key,
|
|
2279
|
+
confirm(yes, `This rotates the credentials of "${project}" (database password, storage key, cache password if the cache is on, and the API key link made before) and stores the new ones, with the other services' keys, in ${envs.join(", ")} plus any env linked before. Apps still using the old credentials will stop working.`, project, "the project name");
|
|
2244
2280
|
const result = await api(`${base7(project)}/link`, { method: "POST", body: JSON.stringify({ envs }) });
|
|
2245
2281
|
print(result, () => {
|
|
2246
2282
|
console.log(`Rotated and stored in ${result.envs.join(", ")}: ${result.keys.join(", ")}`);
|
|
@@ -2300,9 +2336,9 @@ async function search(sub, args) {
|
|
|
2300
2336
|
switch (sub) {
|
|
2301
2337
|
case "enable": {
|
|
2302
2338
|
const p = need(project, "project name");
|
|
2303
|
-
await
|
|
2339
|
+
const { linked } = await enableService(p, "search", flags);
|
|
2304
2340
|
const searchUrl = `${API_URL}/search/${p}`;
|
|
2305
|
-
print({ enabled: "search", project: p, searchUrl, fetchUrl: `${searchUrl}/fetch` }, () => {
|
|
2341
|
+
print({ enabled: "search", project: p, linked, searchUrl, fetchUrl: `${searchUrl}/fetch` }, () => {
|
|
2306
2342
|
console.log(`Web search enabled for "${p}"
|
|
2307
2343
|
`);
|
|
2308
2344
|
console.log(` search URL ${searchUrl}?q=\u2026`);
|
|
@@ -2313,6 +2349,7 @@ async function search(sub, args) {
|
|
|
2313
2349
|
`);
|
|
2314
2350
|
console.log(` const web = createSearchClient({ apiUrl: "${API_URL}", project: "${p}", apiKey: process.env.MYBASE_API_KEY! })`);
|
|
2315
2351
|
console.log(` const { results } = await web.search("\u2026") // or searchTools(web) for an LLM agent`);
|
|
2352
|
+
printLinked(p, linked, flags);
|
|
2316
2353
|
});
|
|
2317
2354
|
return true;
|
|
2318
2355
|
}
|
|
@@ -3722,6 +3759,7 @@ var compose_default = `# Runs mybase: the control plane (API + dashboard), the s
|
|
|
3722
3759
|
# MYBASE_S3_PUBLIC_URL=https://s3.<domain>
|
|
3723
3760
|
# MYBASE_TRUST_PROXY=cloudflare
|
|
3724
3761
|
# Optionally POSTGRES_BIND=127.0.0.1, GARAGE_S3_BIND=127.0.0.1, and VALKEY_BIND=127.0.0.1 to stop exposing those ports on the LAN.
|
|
3762
|
+
# Optionally CLOUDFLARED_REGION=us if the tunnel lands on far-away data centers (see \`mybase server status\`).
|
|
3725
3763
|
# 5. docker compose up -d
|
|
3726
3764
|
# Apps on serverless hosts reach Postgres at wss://mybase.<domain>/db with @neondatabase/serverless,
|
|
3727
3765
|
# or, with a router port forward, at a normal postgres:// URL (public Postgres, below).
|
|
@@ -3885,6 +3923,11 @@ services:
|
|
|
3885
3923
|
command: tunnel --no-autoupdate run
|
|
3886
3924
|
environment:
|
|
3887
3925
|
TUNNEL_TOKEN: \${CLOUDFLARE_TUNNEL_TOKEN:-}
|
|
3926
|
+
# Which Cloudflare data centers the tunnel connects to: empty = nearest by anycast, \`us\` = US only
|
|
3927
|
+
# (if \`mybase server status\` shows far-away colos, every request takes the detour).
|
|
3928
|
+
TUNNEL_REGION: \${CLOUDFLARED_REGION:-}
|
|
3929
|
+
# auto (QUIC, falls back to HTTP/2), quic, or http2 (if your network blocks or throttles UDP).
|
|
3930
|
+
TUNNEL_TRANSPORT_PROTOCOL: \${CLOUDFLARED_PROTOCOL:-auto}
|
|
3888
3931
|
depends_on:
|
|
3889
3932
|
- mybase
|
|
3890
3933
|
|
|
@@ -4216,6 +4259,25 @@ async function enableDockerOnBoot() {
|
|
|
4216
4259
|
return false;
|
|
4217
4260
|
}
|
|
4218
4261
|
}
|
|
4262
|
+
function tunnelConnections(logs) {
|
|
4263
|
+
const conns = new Map;
|
|
4264
|
+
for (const line of logs.split(`
|
|
4265
|
+
`)) {
|
|
4266
|
+
if (/Starting tunnel/.test(line))
|
|
4267
|
+
conns.clear();
|
|
4268
|
+
const index = Number(/connIndex=(\d+)/.exec(line)?.[1]);
|
|
4269
|
+
if (Number.isNaN(index))
|
|
4270
|
+
continue;
|
|
4271
|
+
if (/Registered tunnel connection/.test(line)) {
|
|
4272
|
+
const location = /location=(\S+)/.exec(line)?.[1];
|
|
4273
|
+
if (location)
|
|
4274
|
+
conns.set(index, { index, location, protocol: /protocol=(\S+)/.exec(line)?.[1] ?? null });
|
|
4275
|
+
} else if (/Unregistered tunnel connection|Connection terminated|Retrying connection|Serve tunnel error/.test(line)) {
|
|
4276
|
+
conns.delete(index);
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
return [...conns.values()].sort((a, b) => a.index - b.index);
|
|
4280
|
+
}
|
|
4219
4281
|
|
|
4220
4282
|
// src/server-setup.ts
|
|
4221
4283
|
import { chmod as chmod2, mkdir as mkdir3 } from "fs/promises";
|
|
@@ -5007,6 +5069,7 @@ var serverUsage = ` server setup [--dir <dir>] Install mybase on this machi
|
|
|
5007
5069
|
[--admin-user <u> --admin-password-stdin] Without a terminal: defaults plus these flags
|
|
5008
5070
|
server status [--dir <dir>] Containers, health, and whether mybase starts on boot
|
|
5009
5071
|
server update [--dir <dir>] Download new images and restart (also refreshes compose.yml)
|
|
5072
|
+
server restart [--dir <dir>] Restart every container (API, Postgres, storage, cache, search, tunnel)
|
|
5010
5073
|
server tunnel [--dir <dir>] Add or replace the Cloudflare Tunnel [--tunnel-token <t> --domain <d>] | --off
|
|
5011
5074
|
server database-public [--dir <dir>] A normal postgres:// URL from anywhere: router port forward +
|
|
5012
5075
|
Let's Encrypt TLS + DNS kept on your IP (Cloudflare)
|
|
@@ -5039,6 +5102,10 @@ async function status(args) {
|
|
|
5039
5102
|
const health = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3000) }).then(async (r) => r.ok ? (await r.json()).version ?? "unknown" : null).catch(() => null);
|
|
5040
5103
|
const autostart = values.MYBASE_RESTART !== "no";
|
|
5041
5104
|
const docker = dockerOnBoot();
|
|
5105
|
+
const tunnelOn = !!values.CLOUDFLARE_TUNNEL_TOKEN && containers.some((c) => c.Service === "cloudflared");
|
|
5106
|
+
const tunnelLogs = tunnelOn ? await compose(dir, ["logs", "--no-log-prefix", "--tail", "500", "cloudflared"]) : null;
|
|
5107
|
+
const tunnel = tunnelLogs?.ok ? tunnelConnections(`${tunnelLogs.stdout}
|
|
5108
|
+
${tunnelLogs.stderr}`) : null;
|
|
5042
5109
|
const data = {
|
|
5043
5110
|
dir,
|
|
5044
5111
|
url,
|
|
@@ -5047,6 +5114,7 @@ async function status(args) {
|
|
|
5047
5114
|
autostart: autostart && docker !== "disabled",
|
|
5048
5115
|
dockerOnBoot: docker,
|
|
5049
5116
|
publicUrl: values.CLOUDFLARE_TUNNEL_TOKEN ? values.MYBASE_PUBLIC_URL ?? null : null,
|
|
5117
|
+
tunnel,
|
|
5050
5118
|
containers: containers.map((c) => ({ service: c.Service, state: c.State, health: c.Health || null }))
|
|
5051
5119
|
};
|
|
5052
5120
|
print(data, () => {
|
|
@@ -5054,6 +5122,10 @@ async function status(args) {
|
|
|
5054
5122
|
console.log(` ${health ? green("\u25CF") : red("\u25CF")} ${url} ${health ? `version ${health}` : "not answering"}`);
|
|
5055
5123
|
if (data.publicUrl)
|
|
5056
5124
|
console.log(` ${dim2("public")} ${data.publicUrl}`);
|
|
5125
|
+
if (tunnel) {
|
|
5126
|
+
const where = tunnel.map((c) => `${c.location}${c.protocol ? ` ${c.protocol}` : ""}`).join(", ");
|
|
5127
|
+
console.log(` ${tunnel.length > 0 ? green("\u25CF") : red("\u25CF")} ${dim2("tunnel")} ${tunnel.length > 0 ? `${tunnel.length} connection(s): ${where}` : "not connected to Cloudflare (public URL answers 530)"}`);
|
|
5128
|
+
}
|
|
5057
5129
|
console.log(` ${dim2("starts on boot")} ${!autostart ? "no (mybase server autostart on)" : docker === "disabled" ? "no: Docker doesn't start at boot (sudo systemctl enable docker)" : "yes"}`);
|
|
5058
5130
|
console.log("");
|
|
5059
5131
|
if (containers.length === 0)
|
|
@@ -5084,6 +5156,31 @@ async function update(args) {
|
|
|
5084
5156
|
return print({ dir, version, composeUpdated: replaced }, () => {});
|
|
5085
5157
|
outro(`mybase ${version} is running at ${localUrl(values)}`);
|
|
5086
5158
|
}
|
|
5159
|
+
async function restart(args) {
|
|
5160
|
+
const dir = await serverDir(args);
|
|
5161
|
+
const { values } = await loadEnv(dir);
|
|
5162
|
+
if (!await stackExists(dir))
|
|
5163
|
+
throw new CliError("no containers to restart. Start them: mybase server update", 4);
|
|
5164
|
+
const s = ui.spinner();
|
|
5165
|
+
s.start("Restarting containers");
|
|
5166
|
+
const res = await compose(dir, ["restart"]);
|
|
5167
|
+
if (!res.ok) {
|
|
5168
|
+
s.stop("Restart failed");
|
|
5169
|
+
throw new CliError(`docker compose restart failed:
|
|
5170
|
+
${res.stderr}`);
|
|
5171
|
+
}
|
|
5172
|
+
s.message("Waiting for mybase to answer");
|
|
5173
|
+
const version = await waitHealthy(localUrl(values), 120000);
|
|
5174
|
+
s.stop(version ? "Restarted" : "Containers restarted");
|
|
5175
|
+
if (!version) {
|
|
5176
|
+
ui.warn("mybase didn't answer yet; check `mybase server status`.");
|
|
5177
|
+
process.exitCode = 1;
|
|
5178
|
+
}
|
|
5179
|
+
print({ dir, healthy: version !== null, version }, () => {
|
|
5180
|
+
if (version)
|
|
5181
|
+
console.log(`mybase ${version} is running at ${localUrl(values)}`);
|
|
5182
|
+
});
|
|
5183
|
+
}
|
|
5087
5184
|
async function tunnel(args) {
|
|
5088
5185
|
const dir = await serverDir(args);
|
|
5089
5186
|
const { text, values } = await loadEnv(dir);
|
|
@@ -5240,6 +5337,8 @@ async function server(command, args) {
|
|
|
5240
5337
|
await status(args);
|
|
5241
5338
|
else if (command === "update")
|
|
5242
5339
|
await update(args);
|
|
5340
|
+
else if (command === "restart")
|
|
5341
|
+
await restart(args);
|
|
5243
5342
|
else if (command === "tunnel")
|
|
5244
5343
|
await tunnel(args);
|
|
5245
5344
|
else if (command === "database-public")
|
|
@@ -5408,15 +5507,16 @@ async function users(sub, args) {
|
|
|
5408
5507
|
switch (sub) {
|
|
5409
5508
|
case "enable": {
|
|
5410
5509
|
const p = need(project, "project name");
|
|
5411
|
-
|
|
5510
|
+
const { linked } = await enableService(p, "users", flags);
|
|
5412
5511
|
const o = await api(base8(p));
|
|
5413
|
-
print({ enabled: "users", project: p, ...o }, () => {
|
|
5512
|
+
print({ enabled: "users", project: p, linked, ...o }, () => {
|
|
5414
5513
|
console.log(`Users enabled for "${p}". Better Auth's tables go in the "${o.schema}" schema.
|
|
5415
5514
|
`);
|
|
5416
5515
|
console.log("Next:");
|
|
5417
5516
|
console.log(` 1. An API key for the app: mybase token create ${p}-auth --project ${p} --perm users:write,cache:write`);
|
|
5418
5517
|
console.log(` 2. The cache, for sessions: mybase cache enable ${p}`);
|
|
5419
5518
|
console.log(` 3. An auth.ts to start from: mybase users init ${p}`);
|
|
5519
|
+
printLinked(p, linked, flags);
|
|
5420
5520
|
});
|
|
5421
5521
|
return true;
|
|
5422
5522
|
}
|
package/package.json
CHANGED