@remotedraw/cli 0.1.1 → 0.1.3
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 +189 -49
- package/dist/cli.d.ts +4 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +247 -65
- package/dist/index.js +19 -2
- package/dist/setup-wizard.d.ts +9 -1
- package/dist/setup-wizard.d.ts.map +1 -1
- package/dist/setup-wizard.js +554 -77
- package/dist/update.d.ts +53 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +235 -0
- package/package.json +3 -8
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type CloudRuntime } from "./cloud.js";
|
|
2
|
+
export declare const CLI_PACKAGE_NAME = "@remotedraw/cli";
|
|
3
|
+
export type UpdateRuntime = CloudRuntime & {
|
|
4
|
+
/**
|
|
5
|
+
* Runs an installer. Absent in unit tests and in any runtime that must not
|
|
6
|
+
* spawn processes, which makes `remotedraw update` report the command instead
|
|
7
|
+
* of running it.
|
|
8
|
+
*/
|
|
9
|
+
spawnCommand?: (command: string, args: string[]) => Promise<{
|
|
10
|
+
exitCode: number;
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
export type InstallMethod = {
|
|
14
|
+
/** Package manager the binary appears to have been installed with. */
|
|
15
|
+
manager: "npm" | "pnpm" | "yarn" | "bun";
|
|
16
|
+
/** True when the binary was resolved by npx/bunx, which never needs updating. */
|
|
17
|
+
ephemeral: boolean;
|
|
18
|
+
command: string;
|
|
19
|
+
args: string[];
|
|
20
|
+
};
|
|
21
|
+
export type UpdateCheck = {
|
|
22
|
+
current: string;
|
|
23
|
+
latest: string;
|
|
24
|
+
updateAvailable: boolean;
|
|
25
|
+
};
|
|
26
|
+
export declare function parseVersion(value: string): {
|
|
27
|
+
numbers: number[];
|
|
28
|
+
prerelease: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function compareVersions(a: string, b: string): 1 | 0 | -1;
|
|
31
|
+
/**
|
|
32
|
+
* Derives the update command from where the running binary lives. Guessing
|
|
33
|
+
* wrong only prints the wrong suggestion, so the fallback is plain npm.
|
|
34
|
+
*/
|
|
35
|
+
export declare function detectInstallMethod(binPath: string): InstallMethod;
|
|
36
|
+
export declare function installMethodDisplay(method: InstallMethod): string;
|
|
37
|
+
export declare function updateCachePath(runtime: UpdateRuntime): string;
|
|
38
|
+
export declare function latestPublishedVersion(runtime: UpdateRuntime, timeoutMs?: number): Promise<string | undefined>;
|
|
39
|
+
/** True when the environment asked us not to make an update request at all. */
|
|
40
|
+
export declare function updateChecksDisabled(env: Record<string, string | undefined>): boolean;
|
|
41
|
+
export declare function checkForUpdate(runtime: UpdateRuntime, currentVersion: string, options?: {
|
|
42
|
+
force?: boolean;
|
|
43
|
+
now?: number;
|
|
44
|
+
}): Promise<UpdateCheck | undefined>;
|
|
45
|
+
export declare function updateNotice(check: UpdateCheck, method: InstallMethod): string;
|
|
46
|
+
export declare function runInstall(runtime: UpdateRuntime, method: InstallMethod): Promise<{
|
|
47
|
+
ran: false;
|
|
48
|
+
exitCode?: never;
|
|
49
|
+
} | {
|
|
50
|
+
ran: true;
|
|
51
|
+
exitCode: number;
|
|
52
|
+
}>;
|
|
53
|
+
//# sourceMappingURL=update.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../src/update.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEjE,eAAO,MAAM,gBAAgB,oBAAoB,CAAC;AAOlD,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG;IACzC;;;;OAIG;IACH,YAAY,CAAC,EAAE,CACb,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,sEAAsE;IACtE,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IACzC,iFAAiF;IACjF,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAiBF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM;;;EAczC;AA2BD,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,cASnD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAoClE;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,UAEzD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,UAErD;AAgDD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,aAAa,EACtB,SAAS,SAAmB,+BAoC7B;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,WAO3E;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,aAAa,EACtB,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9C,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CA2BlC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,UAOrE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,aAAa;;;;;;GAM7E"}
|
package/dist/update.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { globalConfigPath } from "./cloud.js";
|
|
3
|
+
export const CLI_PACKAGE_NAME = "@remotedraw/cli";
|
|
4
|
+
const DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
|
|
5
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
6
|
+
/** The passive notice must never delay a command; an explicit check may wait. */
|
|
7
|
+
const CHECK_TIMEOUT_MS = 2_000;
|
|
8
|
+
const FORCED_CHECK_TIMEOUT_MS = 10_000;
|
|
9
|
+
/**
|
|
10
|
+
* npm registry semantics: a globally installed package is a pinned snapshot,
|
|
11
|
+
* so the CLI has to ask the registry itself to know it is stale.
|
|
12
|
+
*/
|
|
13
|
+
function registryUrl(env) {
|
|
14
|
+
const configured = env.REMOTEDRAW_CLI_REGISTRY?.trim() || env.npm_config_registry?.trim();
|
|
15
|
+
return (configured || DEFAULT_REGISTRY_URL).replace(/\/+$/, "");
|
|
16
|
+
}
|
|
17
|
+
export function parseVersion(value) {
|
|
18
|
+
const trimmed = value.trim().replace(/^v/, "");
|
|
19
|
+
const separator = trimmed.indexOf("-");
|
|
20
|
+
const core = separator === -1 ? trimmed : trimmed.slice(0, separator);
|
|
21
|
+
const prerelease = separator === -1 ? "" : trimmed.slice(separator + 1);
|
|
22
|
+
const numbers = core
|
|
23
|
+
.split(".")
|
|
24
|
+
.slice(0, 3)
|
|
25
|
+
.map((part) => {
|
|
26
|
+
const parsed = Number.parseInt(part, 10);
|
|
27
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
28
|
+
});
|
|
29
|
+
while (numbers.length < 3)
|
|
30
|
+
numbers.push(0);
|
|
31
|
+
return { numbers, prerelease };
|
|
32
|
+
}
|
|
33
|
+
function comparePrerelease(a, b) {
|
|
34
|
+
if (a === b)
|
|
35
|
+
return 0;
|
|
36
|
+
// A release always outranks any prerelease of the same core version.
|
|
37
|
+
if (!a)
|
|
38
|
+
return 1;
|
|
39
|
+
if (!b)
|
|
40
|
+
return -1;
|
|
41
|
+
const left = a.split(".");
|
|
42
|
+
const right = b.split(".");
|
|
43
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
44
|
+
const leftPart = left[index];
|
|
45
|
+
const rightPart = right[index];
|
|
46
|
+
if (leftPart == null)
|
|
47
|
+
return -1;
|
|
48
|
+
if (rightPart == null)
|
|
49
|
+
return 1;
|
|
50
|
+
const leftNumber = /^\d+$/.test(leftPart) ? Number(leftPart) : undefined;
|
|
51
|
+
const rightNumber = /^\d+$/.test(rightPart) ? Number(rightPart) : undefined;
|
|
52
|
+
if (leftNumber != null && rightNumber != null) {
|
|
53
|
+
if (leftNumber !== rightNumber)
|
|
54
|
+
return leftNumber < rightNumber ? -1 : 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (leftNumber != null)
|
|
58
|
+
return -1;
|
|
59
|
+
if (rightNumber != null)
|
|
60
|
+
return 1;
|
|
61
|
+
if (leftPart !== rightPart)
|
|
62
|
+
return leftPart < rightPart ? -1 : 1;
|
|
63
|
+
}
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
export function compareVersions(a, b) {
|
|
67
|
+
const left = parseVersion(a);
|
|
68
|
+
const right = parseVersion(b);
|
|
69
|
+
for (let index = 0; index < 3; index += 1) {
|
|
70
|
+
const leftNumber = left.numbers[index] ?? 0;
|
|
71
|
+
const rightNumber = right.numbers[index] ?? 0;
|
|
72
|
+
if (leftNumber !== rightNumber)
|
|
73
|
+
return leftNumber < rightNumber ? -1 : 1;
|
|
74
|
+
}
|
|
75
|
+
return comparePrerelease(left.prerelease, right.prerelease);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Derives the update command from where the running binary lives. Guessing
|
|
79
|
+
* wrong only prints the wrong suggestion, so the fallback is plain npm.
|
|
80
|
+
*/
|
|
81
|
+
export function detectInstallMethod(binPath) {
|
|
82
|
+
const normalized = binPath.replace(/\\/g, "/");
|
|
83
|
+
const update = `${CLI_PACKAGE_NAME}@latest`;
|
|
84
|
+
if (/\/(_npx|\.npm\/_npx|\.bun\/install\/cache)\//.test(normalized)) {
|
|
85
|
+
return { manager: "npm", ephemeral: true, command: "npx", args: [update] };
|
|
86
|
+
}
|
|
87
|
+
if (/\/\.bun\//.test(normalized)) {
|
|
88
|
+
return {
|
|
89
|
+
manager: "bun",
|
|
90
|
+
ephemeral: false,
|
|
91
|
+
command: "bun",
|
|
92
|
+
args: ["add", "--global", update],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (/\/(\.pnpm|pnpm)\//.test(normalized)) {
|
|
96
|
+
return {
|
|
97
|
+
manager: "pnpm",
|
|
98
|
+
ephemeral: false,
|
|
99
|
+
command: "pnpm",
|
|
100
|
+
args: ["add", "--global", update],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (/\/(\.yarn|Yarn)\//.test(normalized)) {
|
|
104
|
+
return {
|
|
105
|
+
manager: "yarn",
|
|
106
|
+
ephemeral: false,
|
|
107
|
+
command: "yarn",
|
|
108
|
+
args: ["global", "add", update],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
manager: "npm",
|
|
113
|
+
ephemeral: false,
|
|
114
|
+
command: "npm",
|
|
115
|
+
args: ["install", "--global", update],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export function installMethodDisplay(method) {
|
|
119
|
+
return [method.command, ...method.args].join(" ");
|
|
120
|
+
}
|
|
121
|
+
export function updateCachePath(runtime) {
|
|
122
|
+
return path.join(path.dirname(globalConfigPath(runtime)), "update-check.json");
|
|
123
|
+
}
|
|
124
|
+
async function readUpdateCache(runtime) {
|
|
125
|
+
const cachePath = updateCachePath(runtime);
|
|
126
|
+
if (!(await runtime.exists(cachePath)))
|
|
127
|
+
return undefined;
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse(await runtime.readFile(cachePath));
|
|
130
|
+
if (typeof parsed !== "object" ||
|
|
131
|
+
parsed === null ||
|
|
132
|
+
typeof parsed.latest !== "string" ||
|
|
133
|
+
typeof parsed.checkedAt !== "number") {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
return parsed;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// A corrupt cache is never fatal; the next check rewrites it.
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function writeUpdateCache(runtime, cache) {
|
|
144
|
+
try {
|
|
145
|
+
const cachePath = updateCachePath(runtime);
|
|
146
|
+
await runtime.mkdir(path.dirname(cachePath));
|
|
147
|
+
await runtime.writeFile(cachePath, `${JSON.stringify(cache)}\n`);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// A read-only config dir must not break the command the user actually ran.
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function registryJson(runtime, url, accept, timeoutMs) {
|
|
154
|
+
const response = await runtime.fetch?.(url, {
|
|
155
|
+
headers: { Accept: accept },
|
|
156
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
157
|
+
});
|
|
158
|
+
if (!response?.ok)
|
|
159
|
+
return undefined;
|
|
160
|
+
return (await response.json());
|
|
161
|
+
}
|
|
162
|
+
export async function latestPublishedVersion(runtime, timeoutMs = CHECK_TIMEOUT_MS) {
|
|
163
|
+
if (!runtime.fetch)
|
|
164
|
+
return undefined;
|
|
165
|
+
const base = `${registryUrl(runtime.env)}/${CLI_PACKAGE_NAME}`;
|
|
166
|
+
try {
|
|
167
|
+
// `/latest` is one version manifest rather than the whole packument, so it
|
|
168
|
+
// stays small as releases accumulate. It rejects npm's abbreviated-packument
|
|
169
|
+
// Accept header with a 406, hence plain JSON here.
|
|
170
|
+
const manifest = await registryJson(runtime, `${base}/latest`, "application/json", timeoutMs);
|
|
171
|
+
if (typeof manifest?.version === "string")
|
|
172
|
+
return manifest.version;
|
|
173
|
+
// Registries and mirrors that do not serve `/latest` still serve dist-tags
|
|
174
|
+
// on the abbreviated packument.
|
|
175
|
+
const packument = await registryJson(runtime, base, "application/vnd.npm.install-v1+json", timeoutMs);
|
|
176
|
+
const distTags = packument?.["dist-tags"];
|
|
177
|
+
if (typeof distTags === "object" &&
|
|
178
|
+
distTags !== null &&
|
|
179
|
+
typeof distTags.latest === "string") {
|
|
180
|
+
return distTags.latest;
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** True when the environment asked us not to make an update request at all. */
|
|
189
|
+
export function updateChecksDisabled(env) {
|
|
190
|
+
return Boolean(env.REMOTEDRAW_CLI_OFFLINE === "1" ||
|
|
191
|
+
env.REMOTEDRAW_CLI_NO_UPDATE_CHECK === "1" ||
|
|
192
|
+
env.NO_UPDATE_NOTIFIER ||
|
|
193
|
+
env.CI);
|
|
194
|
+
}
|
|
195
|
+
export async function checkForUpdate(runtime, currentVersion, options = {}) {
|
|
196
|
+
if (currentVersion === "unknown")
|
|
197
|
+
return undefined;
|
|
198
|
+
if (!options.force && updateChecksDisabled(runtime.env))
|
|
199
|
+
return undefined;
|
|
200
|
+
const now = options.now ?? Date.now();
|
|
201
|
+
if (!options.force) {
|
|
202
|
+
const cached = await readUpdateCache(runtime);
|
|
203
|
+
if (cached && now - cached.checkedAt < CHECK_INTERVAL_MS) {
|
|
204
|
+
return {
|
|
205
|
+
current: currentVersion,
|
|
206
|
+
latest: cached.latest,
|
|
207
|
+
updateAvailable: compareVersions(currentVersion, cached.latest) < 0,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const latest = await latestPublishedVersion(runtime, options.force ? FORCED_CHECK_TIMEOUT_MS : CHECK_TIMEOUT_MS);
|
|
212
|
+
if (!latest)
|
|
213
|
+
return undefined;
|
|
214
|
+
await writeUpdateCache(runtime, { checkedAt: now, latest });
|
|
215
|
+
return {
|
|
216
|
+
current: currentVersion,
|
|
217
|
+
latest,
|
|
218
|
+
updateAvailable: compareVersions(currentVersion, latest) < 0,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
export function updateNotice(check, method) {
|
|
222
|
+
return [
|
|
223
|
+
`Update available: ${CLI_PACKAGE_NAME} ${check.current} -> ${check.latest}`,
|
|
224
|
+
method.ephemeral
|
|
225
|
+
? `Run remotedraw with ${CLI_PACKAGE_NAME}@latest to pick it up.`
|
|
226
|
+
: `Run remotedraw update (or ${installMethodDisplay(method)}).`,
|
|
227
|
+
].join("\n");
|
|
228
|
+
}
|
|
229
|
+
export async function runInstall(runtime, method) {
|
|
230
|
+
if (!runtime.spawnCommand) {
|
|
231
|
+
return { ran: false };
|
|
232
|
+
}
|
|
233
|
+
const result = await runtime.spawnCommand(method.command, method.args);
|
|
234
|
+
return { ran: true, exitCode: result.exitCode };
|
|
235
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remotedraw/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Command-line tools for creating and inspecting RemoteDraw integrations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,15 +13,10 @@
|
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=20"
|
|
15
15
|
},
|
|
16
|
-
"repository": {
|
|
17
|
-
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/AxioSOzo/RemoteDraw.git",
|
|
19
|
-
"directory": "packages/cli"
|
|
20
|
-
},
|
|
21
16
|
"bugs": {
|
|
22
|
-
"
|
|
17
|
+
"email": "support@remotedraw.com"
|
|
23
18
|
},
|
|
24
|
-
"homepage": "https://
|
|
19
|
+
"homepage": "https://docs.remotedraw.com",
|
|
25
20
|
"keywords": [
|
|
26
21
|
"remotedraw",
|
|
27
22
|
"drawing",
|