@neosh/update 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/main.ts +148 -0
- package/package.json +21 -0
- package/plugin.toml +12 -0
package/main.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* There is a newer neosh, and here is what pressing this would do.
|
|
3
|
+
*
|
|
4
|
+
* Two things, and the second is the one that needs care. Saying *there is an update* is news you did
|
|
5
|
+
* not ask for and cannot otherwise see, which is the definition of an alert — so it is raised once,
|
|
6
|
+
* and never again for the same version. Saying *what updating means here* is not one sentence: a
|
|
7
|
+
* binary Homebrew owns is updated by `brew`, one inside `node_modules` by npm, and only a standalone
|
|
8
|
+
* one is neosh's to replace. The host works out which; this draws the answer.
|
|
9
|
+
*
|
|
10
|
+
* The row lives under the plan strip, because both are facts about the workspace rather than about
|
|
11
|
+
* a conversation, and the plan is the thing you look at when you look down there. It is only ever
|
|
12
|
+
* present when there is something to say — an update waiting, or a restart owed — so the column's
|
|
13
|
+
* job stays your conversations.
|
|
14
|
+
*/
|
|
15
|
+
import type { PluginContext, UpdateStatus } from "@neosh/api";
|
|
16
|
+
|
|
17
|
+
const NS = "update";
|
|
18
|
+
/** The section id, so the row can be taken off again by name. */
|
|
19
|
+
const ROW = "update";
|
|
20
|
+
/** How often to ask, once the workspace is up. A day: the thing being watched moves in weeks. */
|
|
21
|
+
const EVERY_MS = 6 * 60 * 60 * 1000;
|
|
22
|
+
|
|
23
|
+
export async function activate({ neosh, subscriptions }: PluginContext) {
|
|
24
|
+
/** The version we have already interrupted somebody about. */
|
|
25
|
+
let announced: string | null = null;
|
|
26
|
+
/** What is drawn, so a tick that changes nothing is not a round trip. */
|
|
27
|
+
let drawn: string | null = null;
|
|
28
|
+
|
|
29
|
+
/** What the row says, given what the host worked out. `null` when there is nothing to say. */
|
|
30
|
+
const rowFor = (s: UpdateStatus) => {
|
|
31
|
+
if (s.restart_pending) {
|
|
32
|
+
return {
|
|
33
|
+
text: `restart for ${s.latest ?? "the update"}`,
|
|
34
|
+
hl: "Status.Pending",
|
|
35
|
+
right: { text: "↵", hl: "Comment" },
|
|
36
|
+
command: `${NS}.restart`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (!s.behind || !s.latest) return null;
|
|
40
|
+
// What the key would *do* is on the row, because a key whose effect you cannot predict from
|
|
41
|
+
// the row it is pointed at is one people learn not to press.
|
|
42
|
+
return {
|
|
43
|
+
text: `neosh ${s.latest} available`,
|
|
44
|
+
hl: "Status.Unread",
|
|
45
|
+
right: { text: "↵", hl: "Comment" },
|
|
46
|
+
command: `${NS}.apply`,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const refresh = async (force = false) => {
|
|
51
|
+
const status = await neosh.update.check(force).catch(() => null);
|
|
52
|
+
if (!status) return;
|
|
53
|
+
|
|
54
|
+
const row = rowFor(status);
|
|
55
|
+
const key = JSON.stringify(row);
|
|
56
|
+
if (key !== drawn) {
|
|
57
|
+
drawn = key;
|
|
58
|
+
if (row) {
|
|
59
|
+
// Under the plan rather than above it: the plan is the thing somebody came to look at, and
|
|
60
|
+
// a row that pushes it down every time a release happens is a row that moved their gauge.
|
|
61
|
+
await neosh.ext.contribute("sidebar.section", ROW, { after: "plan", rows: [row] }, {
|
|
62
|
+
priority: -9,
|
|
63
|
+
}).catch(() => {});
|
|
64
|
+
} else {
|
|
65
|
+
await neosh.ext.remove("sidebar.section", ROW).catch(() => {});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Once per version. A notification that repeats is one people turn off, and this one has to
|
|
70
|
+
// survive being ignored for a week.
|
|
71
|
+
if (status.behind && status.latest && status.latest !== announced) {
|
|
72
|
+
announced = status.latest;
|
|
73
|
+
await neosh.alert(
|
|
74
|
+
`neosh ${status.latest}`,
|
|
75
|
+
status.self_updatable
|
|
76
|
+
? "A new version is out. Update from the sidebar, or run update.apply."
|
|
77
|
+
: `A new version is out. Update with: ${status.upgrade_command ?? "your package manager"}`,
|
|
78
|
+
{ level: "info" },
|
|
79
|
+
).catch(() => {});
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
subscriptions.push(
|
|
84
|
+
await neosh.cmd.register(`${NS}.check`, async () => {
|
|
85
|
+
await refresh(true);
|
|
86
|
+
const s = await neosh.update.check().catch(() => null);
|
|
87
|
+
if (!s) return neosh.notify("Could not check for updates", "warn");
|
|
88
|
+
if (s.error) return neosh.notify(`Update check failed: ${s.error}`, "warn");
|
|
89
|
+
if (!s.behind) return neosh.notify(`neosh ${s.current} is the newest`, "info");
|
|
90
|
+
return neosh.notify(`neosh ${s.latest} is available`, "info");
|
|
91
|
+
}, { desc: "Check for a newer neosh" }),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
subscriptions.push(
|
|
95
|
+
await neosh.cmd.register(`${NS}.apply`, async () => {
|
|
96
|
+
neosh.progress(`${NS}`, "Updating neosh…");
|
|
97
|
+
const outcome = await neosh.update.apply().catch((e) => ({
|
|
98
|
+
outcome: "failed" as const,
|
|
99
|
+
reason: String(e),
|
|
100
|
+
}));
|
|
101
|
+
switch (outcome.outcome) {
|
|
102
|
+
case "up_to_date":
|
|
103
|
+
return neosh.notify(`neosh ${outcome.version} is the newest`, "info");
|
|
104
|
+
case "applied":
|
|
105
|
+
await refresh(true);
|
|
106
|
+
// Asked, never done. A restart ends turns in conversations this terminal cannot see, so
|
|
107
|
+
// the decision belongs to a person who has been told that.
|
|
108
|
+
return neosh.notify(
|
|
109
|
+
`neosh ${outcome.version} is installed — restart to use it`,
|
|
110
|
+
"info",
|
|
111
|
+
);
|
|
112
|
+
case "delegated":
|
|
113
|
+
// Not run for them. A keypress that drives somebody's package manager is how a machine
|
|
114
|
+
// ends up in a state its owner cannot explain.
|
|
115
|
+
return neosh.notify(`Update with: ${outcome.command}`, "info");
|
|
116
|
+
default:
|
|
117
|
+
return neosh.notify(`Update failed: ${outcome.reason}`, "error");
|
|
118
|
+
}
|
|
119
|
+
}, { desc: "Update neosh to the newest version" }),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
subscriptions.push(
|
|
123
|
+
await neosh.cmd.register(`${NS}.restart`, async () => {
|
|
124
|
+
try {
|
|
125
|
+
await neosh.update.restart();
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// The host refuses while any turn is in flight, and names them. Passed through as-is:
|
|
128
|
+
// "something is running" sends somebody hunting for which conversation.
|
|
129
|
+
return neosh.notify(String(e), "warn");
|
|
130
|
+
}
|
|
131
|
+
}, { desc: "Restart the workspace to finish an update" }),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// No `sidebar.action` of its own, deliberately. A contributed row already runs its `command` on
|
|
135
|
+
// `↵`, and one row that means the one thing there is to do — update, or restart once it is
|
|
136
|
+
// downloaded — is better than two keys for two states that never coexist.
|
|
137
|
+
//
|
|
138
|
+
// It also sidesteps something worth fixing properly one day: `on: "custom"` matches *any*
|
|
139
|
+
// contributed row rather than the contributor's own, so a key added here is advertised on the
|
|
140
|
+
// plan strip too, and the strip's own `<Tab>` hint gets pushed off the end of the row.
|
|
141
|
+
|
|
142
|
+
await refresh();
|
|
143
|
+
const timer = await neosh.timer.every(EVERY_MS, () => void refresh(true));
|
|
144
|
+
subscriptions.push(timer);
|
|
145
|
+
subscriptions.push({
|
|
146
|
+
dispose: () => void neosh.ext.remove("sidebar.section", ROW).catch(() => {}),
|
|
147
|
+
});
|
|
148
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@neosh/update",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Says when there is a newer neosh, and becomes it.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"neosh",
|
|
9
|
+
"neosh-plugin"
|
|
10
|
+
],
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/neoswarm/neosh.git",
|
|
14
|
+
"directory": "plugins/builtin/update"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"*.ts",
|
|
18
|
+
"plugin.toml",
|
|
19
|
+
"!._*"
|
|
20
|
+
]
|
|
21
|
+
}
|
package/plugin.toml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
name = "update"
|
|
2
|
+
version = "0.2.0"
|
|
3
|
+
entry = "main.ts"
|
|
4
|
+
description = "Says when there is a newer neosh, and becomes it."
|
|
5
|
+
|
|
6
|
+
# Raises the one thing here you did not ask for and cannot see: a version newer than the one you
|
|
7
|
+
# are running. Everything else this plugin does is a row in a panel or a command you invoked.
|
|
8
|
+
permissions = ["notify"]
|
|
9
|
+
|
|
10
|
+
# Puts a row under the plan strip and a verb on it. Soft: without the sidebar this is still a
|
|
11
|
+
# command and still an alert, so there is nothing to require.
|
|
12
|
+
after = ["sidebar"]
|