@webinson/peer-core 0.1.975 → 0.1.976
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 +1 -1
- package/dist/bin.mjs +1 -1
- package/dist/{binCli-CwrAHPco.mjs → binCli-CO9uk2ZU.mjs} +178 -73
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,4 +16,4 @@ peer core serve --detach # or let the editor start it on demand
|
|
|
16
16
|
`peer core status` says who runs Peer here; `peer core stop` stops Peer Core. The Peer desktop app,
|
|
17
17
|
when it starts, takes coordination over. Any other `peer <command>` goes to the running Peer.
|
|
18
18
|
|
|
19
|
-
Version 0.1.
|
|
19
|
+
Version 0.1.976 of Peer Local (https://github.com/SemanS/peer-app, branch `peer`), based on T3 Code.
|
package/dist/bin.mjs
CHANGED
|
@@ -77,7 +77,7 @@ if (isEntrypoint({
|
|
|
77
77
|
} else {
|
|
78
78
|
const { attachConsoleStdin } = await import("./consoleStdin-DO-vIhyg.mjs");
|
|
79
79
|
attachConsoleStdin();
|
|
80
|
-
const { runCli } = await import("./binCli-
|
|
80
|
+
const { runCli } = await import("./binCli-CO9uk2ZU.mjs");
|
|
81
81
|
runCli(npmPeerBin ? "peer" : "t3");
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -27996,118 +27996,198 @@ async function findRepositories(home, limit = 25) {
|
|
|
27996
27996
|
await Promise.all([...new Set(roots.filter((root) => root !== null))].map((root) => look(root, 2)));
|
|
27997
27997
|
return [...found.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit).map(([dir]) => dir);
|
|
27998
27998
|
}
|
|
27999
|
+
const RECOMMENDED = "Recommended";
|
|
28000
|
+
/** `~/…` for a path under the home directory: the name matters, the full path is context. */
|
|
28001
|
+
const shortPath = (path, home) => path === home || path.startsWith(`${home}${NodePath.sep}`) ? `~${path.slice(home.length)}` : path;
|
|
28002
|
+
/** The lines above the menu: who, which workspace, and the project here. */
|
|
28003
|
+
const menuContext = (status, here, home) => {
|
|
28004
|
+
const names = status.workspaces.map((w) => w.slug);
|
|
28005
|
+
const lines = [`Signed in as ${status.email ?? "nobody"}`, names.length === 0 ? "Workspace: none yet" : names.length === 1 ? `Workspace: ${names[0]}` : `Workspaces: ${names.join(", ")}`];
|
|
28006
|
+
if (here !== null) lines.push(`Current project: ${NodePath.basename(here.root)} (${shortPath(here.root, home)}) · ${here.state === "connected" ? `connected to ${here.workspace}/${here.project}` : here.state === "clone" ? `a clone of ${here.project}, not connected` : "not connected"}`);
|
|
28007
|
+
return lines;
|
|
28008
|
+
};
|
|
27999
28009
|
/**
|
|
28000
|
-
* What the menu offers
|
|
28001
|
-
*
|
|
28002
|
-
*
|
|
28010
|
+
* What the menu offers, the step that gets the person to a shared project first and marked
|
|
28011
|
+
* recommended: an invitation that waits, else a workspace when there is none, else connecting
|
|
28012
|
+
* the project here, else inviting a teammate to it. Inviting is offered where they own or
|
|
28013
|
+
* administer a workspace; joining or creating one stays reachable, Exit last.
|
|
28003
28014
|
*/
|
|
28004
|
-
const menuItems = (status,
|
|
28005
|
-
...(status.foundings ?? []).filter((f) => f.expired !== true).map((f) => ({
|
|
28006
|
-
title: `Accept ${f.name}
|
|
28015
|
+
const menuItems = (status, here) => {
|
|
28016
|
+
const offers = [...(status.foundings ?? []).filter((f) => f.expired !== true).map((f) => ({
|
|
28017
|
+
title: `Accept ${f.name}, founded for you`,
|
|
28007
28018
|
value: {
|
|
28008
28019
|
kind: "founding",
|
|
28009
28020
|
slug: f.slug,
|
|
28010
28021
|
name: f.name
|
|
28011
28022
|
}
|
|
28012
|
-
})),
|
|
28013
|
-
|
|
28014
|
-
title: offer.reason === "invite" ? `Accept the invitation to ${offer.name} (${offer.slug})` : `Join ${offer.name} (${offer.slug}), which your email domain admits`,
|
|
28023
|
+
})), ...status.joinable.map((offer) => ({
|
|
28024
|
+
title: offer.reason === "invite" ? `Accept the invitation to ${offer.name}` : `Join ${offer.name} (your email domain is admitted)`,
|
|
28015
28025
|
value: {
|
|
28016
28026
|
kind: offer.reason === "invite" ? "accept" : "domain",
|
|
28017
28027
|
slug: offer.slug,
|
|
28018
28028
|
name: offer.name
|
|
28019
28029
|
}
|
|
28020
|
-
}))
|
|
28021
|
-
|
|
28022
|
-
|
|
28023
|
-
|
|
28024
|
-
|
|
28025
|
-
|
|
28026
|
-
}
|
|
28027
|
-
|
|
28028
|
-
...status.workspaces.length === 0 ? [] : [{
|
|
28029
|
-
title: "Share a project from this computer…",
|
|
28030
|
-
value: { kind: "browse" }
|
|
28031
|
-
}],
|
|
28032
|
-
...status.workspaces.filter((w) => inviter(w.role)).map((w) => ({
|
|
28033
|
-
title: `Invite someone to ${w.name} (${w.slug})`,
|
|
28030
|
+
}))];
|
|
28031
|
+
const inWorkspace = status.workspaces.length > 0;
|
|
28032
|
+
const canInvite = status.workspaces.some((w) => inviter(w.role));
|
|
28033
|
+
const name = here === null ? "" : NodePath.basename(here.root);
|
|
28034
|
+
const target = status.workspaces.length === 1 ? status.workspaces[0].slug : "the workspace you choose";
|
|
28035
|
+
const connectHere = !inWorkspace || here === null || here.state === "connected" ? [] : [{
|
|
28036
|
+
title: `Connect this project (${name})`,
|
|
28037
|
+
description: here.state === "clone" ? `It is a clone of ${here.project}: it joins that project, nothing new is created.` : `Your team in ${target} sees it and can clone it; the code stays in its repository.`,
|
|
28034
28038
|
value: {
|
|
28035
|
-
kind: "
|
|
28036
|
-
|
|
28039
|
+
kind: "here",
|
|
28040
|
+
folder: here.root
|
|
28037
28041
|
}
|
|
28038
|
-
}
|
|
28039
|
-
{
|
|
28040
|
-
title: "
|
|
28041
|
-
|
|
28042
|
-
|
|
28043
|
-
|
|
28044
|
-
|
|
28045
|
-
|
|
28046
|
-
|
|
28047
|
-
|
|
28042
|
+
}];
|
|
28043
|
+
const invite = canInvite ? [{
|
|
28044
|
+
title: "Invite a teammate",
|
|
28045
|
+
description: "They get an email with a link; you choose the projects.",
|
|
28046
|
+
value: { kind: "invite" }
|
|
28047
|
+
}] : [];
|
|
28048
|
+
const items = inWorkspace ? [
|
|
28049
|
+
...offers,
|
|
28050
|
+
...here?.state === "connected" ? [...invite, ...connectHere] : [...connectHere, ...invite],
|
|
28051
|
+
{
|
|
28052
|
+
title: "Connect another local project",
|
|
28053
|
+
description: "Pick one of your repositories, or browse for a folder.",
|
|
28054
|
+
value: { kind: "another" }
|
|
28055
|
+
},
|
|
28056
|
+
{
|
|
28057
|
+
title: "Join or create a workspace",
|
|
28058
|
+
value: { kind: "workspace" }
|
|
28059
|
+
}
|
|
28060
|
+
] : [
|
|
28061
|
+
...offers,
|
|
28062
|
+
{
|
|
28063
|
+
title: "Create a workspace",
|
|
28064
|
+
description: "A place for your team's projects; you become its owner.",
|
|
28065
|
+
value: { kind: "create" }
|
|
28066
|
+
},
|
|
28067
|
+
{
|
|
28068
|
+
title: "Join a workspace",
|
|
28069
|
+
description: "By the name in your invitation.",
|
|
28070
|
+
value: { kind: "join" }
|
|
28071
|
+
}
|
|
28072
|
+
];
|
|
28073
|
+
items.push({
|
|
28048
28074
|
title: "Connect an editor",
|
|
28075
|
+
description: "So your agents (Claude Code, Codex, Cursor, …) coordinate through Peer.",
|
|
28049
28076
|
value: { kind: "editor" }
|
|
28050
|
-
},
|
|
28051
|
-
|
|
28052
|
-
|
|
28053
|
-
|
|
28054
|
-
|
|
28055
|
-
|
|
28077
|
+
}, {
|
|
28078
|
+
title: "Exit",
|
|
28079
|
+
value: { kind: "exit" }
|
|
28080
|
+
});
|
|
28081
|
+
const [first, ...rest] = items;
|
|
28082
|
+
return first === void 0 || first.value.kind === "exit" || first.value.kind === "editor" ? items : [{
|
|
28083
|
+
...first,
|
|
28084
|
+
description: first.description === void 0 ? RECOMMENDED : `${RECOMMENDED} · ${first.description}`
|
|
28085
|
+
}, ...rest];
|
|
28086
|
+
};
|
|
28056
28087
|
/**
|
|
28057
|
-
* Asks what to do next until the person
|
|
28058
|
-
* again, so one refusal does not end it.
|
|
28088
|
+
* Asks what to do next until the person exits; a step that fails says why and the menu asks
|
|
28089
|
+
* again, so one refusal does not end it. A connected project ends in what is ready for the team.
|
|
28059
28090
|
*/
|
|
28060
28091
|
const menu = (ask, steps, io) => gen$1(function* () {
|
|
28092
|
+
let shown = "";
|
|
28093
|
+
const createStep = gen$1(function* () {
|
|
28094
|
+
const name = yield* ask.text("The workspace's name");
|
|
28095
|
+
if (name === null || name.trim() === "") return;
|
|
28096
|
+
yield* steps.create(name);
|
|
28097
|
+
shown = "";
|
|
28098
|
+
});
|
|
28099
|
+
const joinStep = gen$1(function* () {
|
|
28100
|
+
const slug = yield* ask.text("The workspace's short name (as your invitation names it)");
|
|
28101
|
+
if (slug === null || slug.trim() === "") return;
|
|
28102
|
+
yield* steps.join(slug.trim());
|
|
28103
|
+
shown = "";
|
|
28104
|
+
});
|
|
28061
28105
|
for (;;) {
|
|
28062
28106
|
const status = yield* steps.status;
|
|
28063
|
-
const
|
|
28064
|
-
|
|
28107
|
+
const here = yield* steps.here;
|
|
28108
|
+
const context = menuContext(status, here, steps.home).join("\n");
|
|
28109
|
+
if (context !== shown) {
|
|
28110
|
+
yield* io.say(context);
|
|
28111
|
+
shown = context;
|
|
28112
|
+
}
|
|
28113
|
+
const item = yield* ask.choose("What would you like to do?", menuItems(status, here));
|
|
28114
|
+
if (item === null || item.kind === "exit") return;
|
|
28115
|
+
const connect = (folder) => gen$1(function* () {
|
|
28116
|
+
const joined = yield* steps.share(folder);
|
|
28117
|
+
if (joined === null) return;
|
|
28118
|
+
const ready = yield* steps.agentsReady;
|
|
28119
|
+
yield* io.say([
|
|
28120
|
+
"",
|
|
28121
|
+
`✓ Signed in as ${status.email ?? ""}`,
|
|
28122
|
+
`✓ Project "${NodePath.basename(folder)}" connected to ${joined.workspace} (${joined.project})`,
|
|
28123
|
+
ready ? "✓ Agent integration ready" : "· Agent integration not set up yet: choose Connect an editor",
|
|
28124
|
+
"",
|
|
28125
|
+
"Your project is ready for collaboration."
|
|
28126
|
+
].join("\n"));
|
|
28127
|
+
shown = "";
|
|
28128
|
+
});
|
|
28065
28129
|
yield* gen$1(function* () {
|
|
28066
28130
|
switch (item.kind) {
|
|
28067
28131
|
case "accept":
|
|
28068
28132
|
case "domain": return yield* steps.join(item.slug);
|
|
28069
28133
|
case "founding": return yield* steps.founding(item.slug);
|
|
28070
|
-
case "
|
|
28071
|
-
case "
|
|
28134
|
+
case "here": return yield* connect(item.folder);
|
|
28135
|
+
case "another": {
|
|
28072
28136
|
const found = yield* steps.repositories;
|
|
28073
28137
|
const picked = yield* ask.choose(found.length === 0 ? "No repositories found in the usual folders" : "Which project?", [
|
|
28074
28138
|
...found.map((dir) => ({
|
|
28075
|
-
title: dir,
|
|
28139
|
+
title: NodePath.basename(dir),
|
|
28140
|
+
description: shortPath(dir, steps.home),
|
|
28076
28141
|
value: dir
|
|
28077
28142
|
})),
|
|
28078
28143
|
{
|
|
28079
28144
|
title: "Browse folders…",
|
|
28080
|
-
value: "
|
|
28145
|
+
value: "\0browse"
|
|
28081
28146
|
},
|
|
28082
28147
|
{
|
|
28083
28148
|
title: "Type a path…",
|
|
28084
|
-
value: "
|
|
28149
|
+
value: "\0type"
|
|
28085
28150
|
}
|
|
28086
28151
|
]);
|
|
28087
|
-
const folder = picked === "
|
|
28088
|
-
if (folder !== null && folder.trim() !== "") yield*
|
|
28152
|
+
const folder = picked === "\0browse" ? yield* ask.folder("The project's folder (Enter, then y opens a folder, n chooses it)") : picked === "\0type" ? yield* ask.text("The project's folder (a path)") : picked;
|
|
28153
|
+
if (folder !== null && folder.trim() !== "") yield* connect(folder.trim());
|
|
28089
28154
|
return;
|
|
28090
28155
|
}
|
|
28091
|
-
case "editor":
|
|
28092
|
-
|
|
28093
|
-
|
|
28094
|
-
if (name !== null && name.trim() !== "") yield* steps.create(name);
|
|
28156
|
+
case "editor":
|
|
28157
|
+
yield* steps.editor;
|
|
28158
|
+
shown = "";
|
|
28095
28159
|
return;
|
|
28096
|
-
|
|
28097
|
-
|
|
28098
|
-
|
|
28099
|
-
|
|
28160
|
+
case "workspace": {
|
|
28161
|
+
const next = yield* ask.choose("Join or create", [{
|
|
28162
|
+
title: "Join a workspace",
|
|
28163
|
+
description: "By the name in your invitation.",
|
|
28164
|
+
value: "join"
|
|
28165
|
+
}, {
|
|
28166
|
+
title: "Create a workspace",
|
|
28167
|
+
description: "You become its owner.",
|
|
28168
|
+
value: "create"
|
|
28169
|
+
}]);
|
|
28170
|
+
if (next === "create") return yield* createStep;
|
|
28171
|
+
if (next === "join") return yield* joinStep;
|
|
28100
28172
|
return;
|
|
28101
28173
|
}
|
|
28174
|
+
case "create": return yield* createStep;
|
|
28175
|
+
case "join": return yield* joinStep;
|
|
28102
28176
|
case "invite": {
|
|
28103
|
-
const
|
|
28177
|
+
const slugs = status.workspaces.filter((w) => inviter(w.role)).map((w) => w.slug);
|
|
28178
|
+
const slug = slugs.length === 1 ? slugs[0] : yield* ask.choose("Invite to which workspace?", slugs.map((s) => ({
|
|
28179
|
+
title: s,
|
|
28180
|
+
value: s
|
|
28181
|
+
})));
|
|
28182
|
+
if (slug === null) return;
|
|
28183
|
+
const email = yield* ask.text(`Their email (invited to ${slug})`);
|
|
28104
28184
|
if (email === null || email.trim() === "") return;
|
|
28105
|
-
const projects = status.workspaces.find((w) => w.slug ===
|
|
28106
|
-
const chosen = projects.length === 0 ? [] : yield* ask.pick("Which projects (none chosen: all
|
|
28185
|
+
const projects = status.workspaces.find((w) => w.slug === slug)?.projects.map((p) => p.project.id) ?? [];
|
|
28186
|
+
const chosen = projects.length === 0 ? [] : yield* ask.pick("Which projects (none chosen: all, now and later)", projects);
|
|
28107
28187
|
if (chosen === null) return;
|
|
28108
28188
|
return yield* steps.invite({
|
|
28109
28189
|
email,
|
|
28110
|
-
workspace:
|
|
28190
|
+
workspace: slug,
|
|
28111
28191
|
projects: chosen
|
|
28112
28192
|
});
|
|
28113
28193
|
}
|
|
@@ -28155,7 +28235,7 @@ const share = (input, ask, io, register) => gen$1(function* () {
|
|
|
28155
28235
|
}
|
|
28156
28236
|
const place = yield* state.placeOf(folder, { bindByRemote: false });
|
|
28157
28237
|
if (place !== null && place.workspace !== "@local") {
|
|
28158
|
-
yield* io.say(`
|
|
28238
|
+
yield* io.say(`Connected already: ${folder} is project ${place.project} of ${place.workspace}.`);
|
|
28159
28239
|
return;
|
|
28160
28240
|
}
|
|
28161
28241
|
const { persisted } = yield* state.read;
|
|
@@ -28176,7 +28256,7 @@ const share = (input, ask, io, register) => gen$1(function* () {
|
|
|
28176
28256
|
const what = `${folder} is a clone of ${offered.map(title).join(", ")}`;
|
|
28177
28257
|
if (asking) {
|
|
28178
28258
|
yield* io.say(`${what}.`);
|
|
28179
|
-
choice = yield* ask.choose("
|
|
28259
|
+
choice = yield* ask.choose("Connect it as", [...offered.map((match) => ({
|
|
28180
28260
|
title: `the checkout of ${title(match)}`,
|
|
28181
28261
|
value: {
|
|
28182
28262
|
kind: "bind",
|
|
@@ -28219,8 +28299,9 @@ const share = (input, ask, io, register) => gen$1(function* () {
|
|
|
28219
28299
|
return yield* bindTo(projectOf$1(match), match.repository);
|
|
28220
28300
|
}
|
|
28221
28301
|
const name = input.name?.trim() || NodePath.basename(folder);
|
|
28302
|
+
const shown = shortPath(folder, realpathOrSelf$2(NodeOS.homedir()));
|
|
28222
28303
|
if (asking) {
|
|
28223
|
-
if (!(yield* ask.confirm(`
|
|
28304
|
+
if (!(yield* ask.confirm(`Connect ${shown} to ${choice.workspace} as project "${name}"? Everyone in ${choice.workspace} sees it and can clone it; the code stays in its repository.`, true))) {
|
|
28224
28305
|
yield* io.say("Nothing changed.");
|
|
28225
28306
|
return;
|
|
28226
28307
|
}
|
|
@@ -28232,7 +28313,7 @@ const share = (input, ask, io, register) => gen$1(function* () {
|
|
|
28232
28313
|
...input.name?.trim() ? { name: input.name.trim() } : {}
|
|
28233
28314
|
});
|
|
28234
28315
|
const shared = yield* state.placeOf(folder, { bindByRemote: false });
|
|
28235
|
-
yield* io.say(shared === null ? `
|
|
28316
|
+
yield* io.say(shared === null ? `Connected ${shown} to ${choice.workspace}.` : `Connected ${shown} to ${choice.workspace} as project ${shared.project}.`);
|
|
28236
28317
|
});
|
|
28237
28318
|
//#endregion
|
|
28238
28319
|
//#region src/cli/peerCoreTasks.ts
|
|
@@ -28608,12 +28689,34 @@ const runMenu = (stateDir) => gen$1(function* () {
|
|
|
28608
28689
|
const status = workspaces.sync.pipe(hubFailure);
|
|
28609
28690
|
yield* menu(menuAsk, {
|
|
28610
28691
|
status,
|
|
28611
|
-
|
|
28692
|
+
home: NodeOS.homedir(),
|
|
28693
|
+
here: gen$1(function* () {
|
|
28612
28694
|
const checkout = yield* repositoryRoot(process.cwd());
|
|
28613
28695
|
if (checkout === null) return null;
|
|
28614
28696
|
const place = yield* state.placeOf(checkout.root, { bindByRemote: false });
|
|
28615
|
-
|
|
28697
|
+
if (place !== null && place.workspace !== "@local") return {
|
|
28698
|
+
root: checkout.root,
|
|
28699
|
+
state: "connected",
|
|
28700
|
+
workspace: place.workspace,
|
|
28701
|
+
project: place.project
|
|
28702
|
+
};
|
|
28703
|
+
const remote = yield* promise(() => remoteOf(checkout.remoteRoot));
|
|
28704
|
+
const { persisted } = yield* state.read;
|
|
28705
|
+
const match = remote === null ? void 0 : matchRepositories(remote, persisted.workspaces.map((w) => w.manifest))[0];
|
|
28706
|
+
return match === void 0 ? {
|
|
28707
|
+
root: checkout.root,
|
|
28708
|
+
state: "new"
|
|
28709
|
+
} : {
|
|
28710
|
+
root: checkout.root,
|
|
28711
|
+
state: "clone",
|
|
28712
|
+
project: `${match.workspace}/${match.project}`
|
|
28713
|
+
};
|
|
28616
28714
|
}),
|
|
28715
|
+
agentsReady: gen$1(function* () {
|
|
28716
|
+
const coordination = (yield* status).coordination;
|
|
28717
|
+
const mcp = yield* (yield* McpClients).connected;
|
|
28718
|
+
return coordination.claudeHooks || (coordination.codexHookHomes ?? []).some((home) => home.installed && home.trusted) || mcp.length > 0;
|
|
28719
|
+
}).pipe(orElseSucceed(() => false)),
|
|
28617
28720
|
join: (slug) => join({ target: slug }, terminalIo).pipe(flatMap$1((joined) => sayNextStep(joined, terminalIo)), hubFailure),
|
|
28618
28721
|
founding: (slug) => workspaces.acceptFounding({ slug }).pipe(andThen(log(`Accepted: ${slug}. You are its owner.`)), hubFailure),
|
|
28619
28722
|
create: (name) => create({ name }, terminalIo).pipe(asVoid, hubFailure),
|
|
@@ -28630,7 +28733,10 @@ const runMenu = (stateDir) => gen$1(function* () {
|
|
|
28630
28733
|
folder,
|
|
28631
28734
|
named: true,
|
|
28632
28735
|
yes: false
|
|
28633
|
-
}, shareAsk, terminalIo, registerIn(stateDir))),
|
|
28736
|
+
}, shareAsk, terminalIo, registerIn(stateDir)).pipe(andThen(state.placeOf(folder, { bindByRemote: false })), map$3((place) => place === null || place.workspace === "@local" ? null : {
|
|
28737
|
+
workspace: place.workspace,
|
|
28738
|
+
project: place.project
|
|
28739
|
+
}))), hubFailure),
|
|
28634
28740
|
invite: (input) => status.pipe(flatMap$1((now) => invite(input, now, terminalIo)), asVoid, hubFailure),
|
|
28635
28741
|
editor: gen$1(function* () {
|
|
28636
28742
|
const editor = yield* shareAsk.choose("Connect which editor?", editorChoices(yield* detectedEditors));
|
|
@@ -28656,7 +28762,6 @@ const loginCommand = make$62("login", {
|
|
|
28656
28762
|
if (email === void 0 || signedIn !== null && signedIn === email) {
|
|
28657
28763
|
if (signedIn === null) return yield* fail("peer core: nobody is signed in; run peer core login <email>.");
|
|
28658
28764
|
if (!isTerminal()) return yield* log(`Signed in: ${signedIn}`);
|
|
28659
|
-
yield* log(`Signed in: ${signedIn}`);
|
|
28660
28765
|
return yield* runMenu(config.stateDir);
|
|
28661
28766
|
}
|
|
28662
28767
|
yield* login({
|
|
@@ -55267,4 +55372,4 @@ function runCli(root = "t3") {
|
|
|
55267
55372
|
//#endregion
|
|
55268
55373
|
export { runCli };
|
|
55269
55374
|
|
|
55270
|
-
//# sourceMappingURL=binCli-
|
|
55375
|
+
//# sourceMappingURL=binCli-CO9uk2ZU.mjs.map
|