chapterjs 0.2.0 → 0.2.1
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/dev.js +12 -0
- package/dist/cli/update-check.d.ts +42 -0
- package/dist/cli/update-check.js +125 -0
- package/package.json +1 -1
package/dist/cli/dev.js
CHANGED
|
@@ -17,6 +17,7 @@ import { RestClient } from '../rest/rest.js';
|
|
|
17
17
|
import { readDevEnv } from './env.js';
|
|
18
18
|
import { describeIntents, ensureInDevGuild, ensurePrivilegedIntents, fetchApplication, } from './preflight.js';
|
|
19
19
|
import { createProject, explain, hasSources, writeTypes } from './project.js';
|
|
20
|
+
import { checkForUpdate, describeUpdate } from './update-check.js';
|
|
20
21
|
/** Runs until stopped. Resolves with the exit code of the process. */
|
|
21
22
|
export async function dev(options) {
|
|
22
23
|
const { cwd, log, signal } = options;
|
|
@@ -31,6 +32,13 @@ export async function dev(options) {
|
|
|
31
32
|
const src = join(cwd, 'src');
|
|
32
33
|
if (!(await hasSources(cwd, log)))
|
|
33
34
|
return 1;
|
|
35
|
+
// Asked now, said once the bot is online: the registry answers while the
|
|
36
|
+
// files load and Discord is checked, so nobody waits for it.
|
|
37
|
+
const update = checkForUpdate({
|
|
38
|
+
cwd,
|
|
39
|
+
version: options.version,
|
|
40
|
+
env: options.env,
|
|
41
|
+
});
|
|
34
42
|
// 2. The files of the project.
|
|
35
43
|
const project = createProject({
|
|
36
44
|
cwd,
|
|
@@ -178,6 +186,10 @@ export async function dev(options) {
|
|
|
178
186
|
}
|
|
179
187
|
};
|
|
180
188
|
notePrivateOnly();
|
|
189
|
+
update.then(found => {
|
|
190
|
+
if (found && !signal.aborted)
|
|
191
|
+
log.info(describeUpdate(found));
|
|
192
|
+
});
|
|
181
193
|
// 4. Every save reloads the project. Watching starts right away; the
|
|
182
194
|
// first thing done is to tell Discord the commands.
|
|
183
195
|
let reloading = syncCommands().then(updated => {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export declare const CHANGELOG_URL = "https://www.chapterjs.org/changelog";
|
|
2
|
+
export interface UpdateCheckOptions {
|
|
3
|
+
/** The folder of the project. */
|
|
4
|
+
cwd: string;
|
|
5
|
+
/** The version of the framework that runs. */
|
|
6
|
+
version: string;
|
|
7
|
+
env: Record<string, string | undefined>;
|
|
8
|
+
/** Only tests change these. */
|
|
9
|
+
registryUrl?: string;
|
|
10
|
+
timeout?: number;
|
|
11
|
+
now?: () => number;
|
|
12
|
+
}
|
|
13
|
+
/** A version newer than the one that runs. */
|
|
14
|
+
export interface Update {
|
|
15
|
+
current: string;
|
|
16
|
+
latest: string;
|
|
17
|
+
/** What to type to install it, with the package manager of the project. */
|
|
18
|
+
command: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The command that updates `chapterjs` in a project: with the package
|
|
22
|
+
* manager its lockfile belongs to, else the one that started the command
|
|
23
|
+
* (`pnpm dev` sets `npm_config_user_agent`), else npm, which every
|
|
24
|
+
* machine with Node has.
|
|
25
|
+
*/
|
|
26
|
+
export declare function updateCommand(cwd: string, env: Record<string, string | undefined>): string;
|
|
27
|
+
/**
|
|
28
|
+
* Whether `latest` is a newer version than `current`. Only the three
|
|
29
|
+
* numbers count: `1.0.0-beta.2` and `1.0.0` are not told apart, so a
|
|
30
|
+
* prerelease never says to install the final version it precedes.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isNewer(latest: string, current: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Whether a newer `chapterjs` is published. The registry is asked at most
|
|
35
|
+
* once a day (`.chapterjs/cache/update.json` keeps the answer), with a
|
|
36
|
+
* short timeout, and never throws: without network, or when the registry
|
|
37
|
+
* does not answer, there is nothing to say. Resolves with the update to
|
|
38
|
+
* install, or `null` when the bot runs the latest version.
|
|
39
|
+
*/
|
|
40
|
+
export declare function checkForUpdate(options: UpdateCheckOptions): Promise<Update | null>;
|
|
41
|
+
/** The line that tells the developer about an update. */
|
|
42
|
+
export declare function describeUpdate(update: Update): string;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Whether a newer chapterjs is published, and the command that installs it:
|
|
2
|
+
// told where the developer starts their bot, so nobody has to watch npm.
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
/** Where the package is published. */
|
|
7
|
+
const NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
8
|
+
/** Asked once a day: a version is not published more often. */
|
|
9
|
+
const CHECK_EVERY = 24 * 60 * 60 * 1000;
|
|
10
|
+
/** A registry that does not answer must never hold the bot. */
|
|
11
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
12
|
+
export const CHANGELOG_URL = 'https://www.chapterjs.org/changelog';
|
|
13
|
+
const PACKAGE_MANAGERS = ['pnpm', 'npm', 'yarn', 'bun'];
|
|
14
|
+
/** The lockfiles each package manager writes next to `package.json`. */
|
|
15
|
+
const LOCKFILES = {
|
|
16
|
+
pnpm: ['pnpm-lock.yaml'],
|
|
17
|
+
npm: ['package-lock.json', 'npm-shrinkwrap.json'],
|
|
18
|
+
yarn: ['yarn.lock'],
|
|
19
|
+
bun: ['bun.lock', 'bun.lockb'],
|
|
20
|
+
};
|
|
21
|
+
/** What installs the latest version with each package manager. */
|
|
22
|
+
const COMMANDS = {
|
|
23
|
+
pnpm: 'pnpm update chapterjs --latest',
|
|
24
|
+
npm: 'npm install chapterjs@latest',
|
|
25
|
+
yarn: 'yarn add chapterjs@latest',
|
|
26
|
+
bun: 'bun add chapterjs@latest',
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The command that updates `chapterjs` in a project: with the package
|
|
30
|
+
* manager its lockfile belongs to, else the one that started the command
|
|
31
|
+
* (`pnpm dev` sets `npm_config_user_agent`), else npm, which every
|
|
32
|
+
* machine with Node has.
|
|
33
|
+
*/
|
|
34
|
+
export function updateCommand(cwd, env) {
|
|
35
|
+
const fromLockfile = PACKAGE_MANAGERS.find(pm => LOCKFILES[pm].some(name => existsSync(join(cwd, name))));
|
|
36
|
+
const agent = env.npm_config_user_agent?.split('/')[0];
|
|
37
|
+
const fromAgent = PACKAGE_MANAGERS.find(pm => pm === agent);
|
|
38
|
+
return COMMANDS[fromLockfile ?? fromAgent ?? 'npm'];
|
|
39
|
+
}
|
|
40
|
+
/** `1.2.3`, with or without a prerelease suffix, as numbers. */
|
|
41
|
+
function parseVersion(version) {
|
|
42
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version.trim());
|
|
43
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Whether `latest` is a newer version than `current`. Only the three
|
|
47
|
+
* numbers count: `1.0.0-beta.2` and `1.0.0` are not told apart, so a
|
|
48
|
+
* prerelease never says to install the final version it precedes.
|
|
49
|
+
*/
|
|
50
|
+
export function isNewer(latest, current) {
|
|
51
|
+
const a = parseVersion(latest);
|
|
52
|
+
const b = parseVersion(current);
|
|
53
|
+
if (!a || !b)
|
|
54
|
+
return false;
|
|
55
|
+
for (let i = 0; i < 3; i++) {
|
|
56
|
+
if (a[i] !== b[i])
|
|
57
|
+
return a[i] > b[i];
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
/** The version the registry publishes as `latest`, or `null`. */
|
|
62
|
+
async function fetchLatest(registryUrl, timeout) {
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(`${registryUrl}/chapterjs/latest`, {
|
|
65
|
+
headers: { accept: 'application/json' },
|
|
66
|
+
signal: AbortSignal.timeout(timeout),
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok)
|
|
69
|
+
return null;
|
|
70
|
+
const { version } = (await response.json());
|
|
71
|
+
return typeof version === 'string' && parseVersion(version)
|
|
72
|
+
? version
|
|
73
|
+
: null;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Whether a newer `chapterjs` is published. The registry is asked at most
|
|
81
|
+
* once a day (`.chapterjs/cache/update.json` keeps the answer), with a
|
|
82
|
+
* short timeout, and never throws: without network, or when the registry
|
|
83
|
+
* does not answer, there is nothing to say. Resolves with the update to
|
|
84
|
+
* install, or `null` when the bot runs the latest version.
|
|
85
|
+
*/
|
|
86
|
+
export async function checkForUpdate(options) {
|
|
87
|
+
const { cwd, version, env } = options;
|
|
88
|
+
const now = options.now ?? Date.now;
|
|
89
|
+
const file = join(cwd, '.chapterjs', 'cache', 'update.json');
|
|
90
|
+
const cached = await readFile(file, 'utf8')
|
|
91
|
+
.then((text) => {
|
|
92
|
+
const value = JSON.parse(text);
|
|
93
|
+
return value &&
|
|
94
|
+
typeof value.checked === 'number' &&
|
|
95
|
+
typeof value.latest === 'string'
|
|
96
|
+
? { checked: value.checked, latest: value.latest }
|
|
97
|
+
: null;
|
|
98
|
+
})
|
|
99
|
+
.catch(() => null);
|
|
100
|
+
let latest;
|
|
101
|
+
if (cached && now() - cached.checked < CHECK_EVERY) {
|
|
102
|
+
latest = cached.latest;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
latest = await fetchLatest(options.registryUrl ?? env.CHAPTERJS_REGISTRY_URL ?? NPM_REGISTRY, options.timeout ?? DEFAULT_TIMEOUT);
|
|
106
|
+
if (latest) {
|
|
107
|
+
// A folder or a disk that can't be written changes nothing: the
|
|
108
|
+
// registry is asked again next time.
|
|
109
|
+
await mkdir(dirname(file), { recursive: true })
|
|
110
|
+
.then(() => writeFile(file, JSON.stringify({ checked: now(), latest })))
|
|
111
|
+
.catch(() => { });
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Yesterday's answer is better than none.
|
|
115
|
+
latest = cached?.latest ?? null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!latest || !isNewer(latest, version))
|
|
119
|
+
return null;
|
|
120
|
+
return { current: version, latest, command: updateCommand(cwd, env) };
|
|
121
|
+
}
|
|
122
|
+
/** The line that tells the developer about an update. */
|
|
123
|
+
export function describeUpdate(update) {
|
|
124
|
+
return `A new version of chapterjs is out: ${update.latest} (you have ${update.current}). To update, run:\n${update.command}\nWhat changed: ${CHANGELOG_URL}`;
|
|
125
|
+
}
|