lecodes-cli 2.0.3 → 2.0.4
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/index.js +67 -20
- package/package.json +8 -8
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/VERSION.json +3 -3
- package/runtime/web/player.js +18 -18
- package/runtime/web-lite/assets/canvas-C6G_Pb2Q.js +1 -0
- package/runtime/web-lite/assets/pager-O712dZv9.js +1 -0
- package/runtime/web-lite/assets/roboto-cyrillic-DVBNKCCH.woff2 +0 -0
- package/runtime/web-lite/assets/roboto-cyrillic-ext-1gPz9dK9.woff2 +0 -0
- package/runtime/web-lite/assets/roboto-greek-DVLYEgdx.woff2 +0 -0
- package/runtime/web-lite/assets/roboto-greek-ext-D98D44EL.woff2 +0 -0
- package/runtime/web-lite/assets/roboto-latin-cAJwcdBQ.woff2 +0 -0
- package/runtime/web-lite/assets/roboto-latin-ext-t610Sqxq.woff2 +0 -0
- package/runtime/web-lite/assets/roboto-rest-C7JkyNHT.woff2 +0 -0
- package/runtime/web-lite/assets/roboto-vietnamese-w-p-nKZZ.woff2 +0 -0
- package/runtime/web-lite/assets/tween-t_XDy3Nd.js +1 -0
- package/runtime/web-lite/assets/vlist-gEB8Sn0S.js +1 -0
- package/runtime/web-lite/features.json +51 -0
- package/runtime/web-lite/lite.js +60 -12
- package/src/commands/update/index.ts +5 -4
- package/src/hosts/archives.ts +41 -7
- package/src/hosts/peerInstall.ts +7 -7
- package/src/web/build.ts +26 -3
- package/runtime/web-lite/assets/Roboto-Variable-DHm9jpd-.woff2 +0 -0
- package/runtime/web-lite/assets/creatorTween-PF8B6T8V.wasm +0 -0
|
@@ -25,7 +25,8 @@ import app from "../app"
|
|
|
25
25
|
* 6. cli the npm installs — every installed optional peer (lecodes-headless, lecodes-design,
|
|
26
26
|
* lecodes-3d-editor, lecodes-assets, @napi-rs/canvas) and the CLI itself that is behind:
|
|
27
27
|
* peers in ~/.lecodes/peers (first-use installs, peerInstall.ts) are re-installed there
|
|
28
|
-
* — the renderer at THIS CLI's version,
|
|
28
|
+
* — the renderer at THIS CLI's version, an editor at the newest one out that this
|
|
29
|
+
* CLI's peer range takes, the rest at npm's latest in range; global ones
|
|
29
30
|
* (`npm install -g`, the old way) via `npm install -g …@latest`; the CLI itself last
|
|
30
31
|
* (the standalone binary can't come from npm — it says so and does the peers only)
|
|
31
32
|
*
|
|
@@ -175,9 +176,9 @@ export default defineCommand({
|
|
|
175
176
|
const where = peerSource(name, probe)
|
|
176
177
|
const installed = peerVersion(name, probe)
|
|
177
178
|
// an archived peer (the headless host, the editors) has no "latest" of a registry: the
|
|
178
|
-
// version this CLI
|
|
179
|
+
// version this CLI installs is the one to have
|
|
179
180
|
const latest = !installed ? null
|
|
180
|
-
: archivedVersion(name) ?? await npmLatest(name)
|
|
181
|
+
: await archivedVersion(name) ?? await npmLatest(name)
|
|
181
182
|
rows.push({ name, installed, latest, where })
|
|
182
183
|
}
|
|
183
184
|
rows.push({ name: "lecodes-cli", installed: pkg.version, latest: await npmLatest("lecodes-cli"), where: "global" })
|
|
@@ -192,7 +193,7 @@ export default defineCommand({
|
|
|
192
193
|
}
|
|
193
194
|
if (behind.length === 0) return "ok"
|
|
194
195
|
const prefixBehind = behind.filter((r) => r.where === "prefix")
|
|
195
|
-
const globalSpecs = behind.filter((r) => r.where !== "prefix").map((r) => archiveSpec(r.name) ?? r.name + "@latest")
|
|
196
|
+
const globalSpecs = await Promise.all(behind.filter((r) => r.where !== "prefix").map(async (r) => await archiveSpec(r.name) ?? r.name + "@latest"))
|
|
196
197
|
if (flags.check) {
|
|
197
198
|
return ["updated", behind.length + " behind — run: lecodes update" + (globalSpecs.length ? " (global: npm install -g " + globalSpecs.join(" ") + ")" : "")]
|
|
198
199
|
}
|
package/src/hosts/archives.ts
CHANGED
|
@@ -5,8 +5,10 @@ import pkg from "../../package.json"
|
|
|
5
5
|
* `npm install <url>` puts the package in place under its own name.
|
|
6
6
|
*
|
|
7
7
|
* A url names one version, and which one is this CLI's to say: the headless host at the CLI's own
|
|
8
|
-
* version (one ABI set)
|
|
9
|
-
*
|
|
8
|
+
* version (one ABI set); an editor at the NEWEST version out that the CLI's peer range takes — the
|
|
9
|
+
* package's index.json in the bucket names what is out. So an editor is released with no release
|
|
10
|
+
* of the CLI: `lecodes update` brings it. With the index out of reach it is the range's own
|
|
11
|
+
* version, the one the CLI was published against (scripts/sync-peer-versions.ts). */
|
|
10
12
|
|
|
11
13
|
export const ARCHIVED_PEERS: ReadonlySet<string> = new Set(["lecodes-headless", "lecodes-design", "lecodes-3d-editor"])
|
|
12
14
|
|
|
@@ -16,17 +18,49 @@ export const packagesBase = (): string => (process.env.LECODES_PACKAGES_URL || B
|
|
|
16
18
|
|
|
17
19
|
export const archiveUrl = (name: string, version: string): string => `${packagesBase()}/${name}/${name}-${version}.tgz`
|
|
18
20
|
|
|
21
|
+
const numbers = (version: string): number[] => version.split(/[.-]/).slice(0, 3).map(Number)
|
|
22
|
+
const byVersion = (a: string, b: string): number => {
|
|
23
|
+
const pa = numbers(a), pb = numbers(b)
|
|
24
|
+
for (let i = 0; i < 3; i++) { const d = (pa[i] ?? 0) - (pb[i] ?? 0); if (d) return d }
|
|
25
|
+
return 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** `^floor` takes it: not older, and the same leftmost number that is not zero. */
|
|
29
|
+
export const caretTakes = (floor: string, version: string): boolean => {
|
|
30
|
+
if (!/^\d+\.\d+\.\d+$/.test(version) || byVersion(version, floor) < 0) return false
|
|
31
|
+
const f = numbers(floor), v = numbers(version)
|
|
32
|
+
const held = f[0] ? 1 : f[1] ? 2 : 3
|
|
33
|
+
for (let i = 0; i < held; i++) if (f[i] !== v[i]) return false
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const asked = new Map<string, Promise<string[]>>()
|
|
38
|
+
/** What is out of a package, as its index names it; none with the index out of reach. */
|
|
39
|
+
const versionsOut = (name: string): Promise<string[]> => {
|
|
40
|
+
const url = `${packagesBase()}/${name}/index.json`
|
|
41
|
+
let answer = asked.get(url)
|
|
42
|
+
if (!answer) {
|
|
43
|
+
answer = fetch(`${url}?t=${Date.now()}`, { signal: AbortSignal.timeout(4000) })
|
|
44
|
+
.then(async (res) => res.ok ? ((await res.json()) as { versions?: string[] }).versions ?? [] : [])
|
|
45
|
+
.catch(() => [])
|
|
46
|
+
asked.set(url, answer)
|
|
47
|
+
}
|
|
48
|
+
return answer
|
|
49
|
+
}
|
|
50
|
+
|
|
19
51
|
/** The version of an archived peer this CLI installs; null for a peer that is none. */
|
|
20
|
-
export const archivedVersion = (name: string): string | null => {
|
|
52
|
+
export const archivedVersion = async (name: string): Promise<string | null> => {
|
|
21
53
|
if (!ARCHIVED_PEERS.has(name)) return null
|
|
22
54
|
if (name === "lecodes-headless") return pkg.version
|
|
23
55
|
const range = (pkg.peerDependencies as Record<string, string> | undefined)?.[name]
|
|
24
|
-
const
|
|
25
|
-
|
|
56
|
+
const floor = range?.replace(/^[\^~>=\s]+/, "")
|
|
57
|
+
if (!floor || !/^\d+\.\d+\.\d+/.test(floor)) return null
|
|
58
|
+
const newest = (await versionsOut(name)).filter((version) => caretTakes(floor, version)).sort(byVersion).pop()
|
|
59
|
+
return newest ?? floor
|
|
26
60
|
}
|
|
27
61
|
|
|
28
62
|
/** What an archived peer is installed by; null for a peer that is none. */
|
|
29
|
-
export const archiveSpec = (name: string): string | null => {
|
|
30
|
-
const version = archivedVersion(name)
|
|
63
|
+
export const archiveSpec = async (name: string): Promise<string | null> => {
|
|
64
|
+
const version = await archivedVersion(name)
|
|
31
65
|
return version ? archiveUrl(name, version) : null
|
|
32
66
|
}
|
package/src/hosts/peerInstall.ts
CHANGED
|
@@ -31,10 +31,10 @@ const CANVAS_RANGE_FALLBACK = "^0.1.65"
|
|
|
31
31
|
const readJson = (path: string): any => { try { return JSON.parse(readFileSync(path, "utf8")) } catch { return null } }
|
|
32
32
|
|
|
33
33
|
/** What a peer installs by: the url of its archive — the renderer at exactly this CLI's version (one
|
|
34
|
-
* ABI set), an editor at the version this CLI
|
|
35
|
-
* at the range the installed renderer declares, lecodes-assets at this CLI's peer range. */
|
|
36
|
-
export const peerSpec = (name: string): string => {
|
|
37
|
-
const archive = archiveSpec(name)
|
|
34
|
+
* ABI set), an editor at the newest version out that this CLI's peer range takes — or, for npm's, a
|
|
35
|
+
* spec: canvas at the range the installed renderer declares, lecodes-assets at this CLI's peer range. */
|
|
36
|
+
export const peerSpec = async (name: string): Promise<string> => {
|
|
37
|
+
const archive = await archiveSpec(name)
|
|
38
38
|
if (archive) return archive
|
|
39
39
|
if (name === "@napi-rs/canvas") {
|
|
40
40
|
const renderer = readJson(join(peerDirInPrefix("lecodes-headless"), "package.json"))
|
|
@@ -71,7 +71,7 @@ export const installPeers = async (names: string[], why: string, opts: { yes?: b
|
|
|
71
71
|
}
|
|
72
72
|
// The renderer must be in place before canvas's range can be read off it — install in order.
|
|
73
73
|
const ordered = [...names].sort((a, b) => (a === "lecodes-headless" ? -1 : b === "lecodes-headless" ? 1 : 0))
|
|
74
|
-
const specsNow = ordered.map(peerSpec)
|
|
74
|
+
const specsNow = await Promise.all(ordered.map(peerSpec))
|
|
75
75
|
|
|
76
76
|
const yes = opts.yes === true || !!process.env.LECODES_YES
|
|
77
77
|
if (!yes) {
|
|
@@ -90,7 +90,7 @@ export const installPeers = async (names: string[], why: string, opts: { yes?: b
|
|
|
90
90
|
if (!tool) throw new CliError(`Neither npm nor bun is on PATH, so ${names.join(" + ")} can't be installed automatically. Install one, or run on a machine with Node: ${manualCommand(dir, specsNow)}`)
|
|
91
91
|
|
|
92
92
|
for (const name of ordered) {
|
|
93
|
-
const spec = peerSpec(name) // canvas's range comes from the renderer installed a step earlier
|
|
93
|
+
const spec = await peerSpec(name) // canvas's range comes from the renderer installed a step earlier
|
|
94
94
|
const argv = tool === "npm"
|
|
95
95
|
? ["install", "--prefix", dir, "--no-audit", "--no-fund", "--loglevel=error", spec]
|
|
96
96
|
: ["add", "--cwd", dir, spec]
|
|
@@ -157,7 +157,7 @@ export async function loadPeer<T>(name: string, subpath: string | undefined, opt
|
|
|
157
157
|
if (opts.install === false) return null
|
|
158
158
|
// A global install of a DIFFERENT version than the probe expects would have resolved above; here
|
|
159
159
|
// the peer is nowhere. (Globally installed but broken → the error above was not "not found".)
|
|
160
|
-
if (peerVersionGlobal(name, spec) !== null) throw new CliError(`${name} is installed but "${spec}" can't be loaded from it — reinstall it: npm install -g ${peerSpec(name)}`)
|
|
160
|
+
if (peerVersionGlobal(name, spec) !== null) throw new CliError(`${name} is installed but "${spec}" can't be loaded from it — reinstall it: npm install -g ${await peerSpec(name)}`)
|
|
161
161
|
await installPeers(companions(name), opts.for)
|
|
162
162
|
}
|
|
163
163
|
return (await import(pathToFileURL(resolveEntry(dir, subpath)).href)) as T
|
package/src/web/build.ts
CHANGED
|
@@ -14,8 +14,12 @@ import { CliError } from "../cli"
|
|
|
14
14
|
*
|
|
15
15
|
* index.html the page (name, icon, theme color of app.json)
|
|
16
16
|
* app.<hash>.js the bundle — the same code every platform runs
|
|
17
|
-
* lite.js, assets/… the page of Web Lite (lecodes-web-lite's dist): its script, its font
|
|
18
|
-
* the
|
|
17
|
+
* lite.js, assets/… the page of Web Lite (lecodes-web-lite's dist): its script, its font
|
|
18
|
+
* in parts by script (a browser fetches the ones a page shows; none
|
|
19
|
+
* with app.json `web.font: "system"`)
|
|
20
|
+
* and the FEATURES this app needs (the pager, the virtualized list, the
|
|
21
|
+
* tweens, the canvas — a chunk each; the page's features.json says what
|
|
22
|
+
* a bundle has to name for one to be needed, and whose files they are)
|
|
19
23
|
* assets/<path> the project's assets — only the ones the bundle names
|
|
20
24
|
* 404.html in `path` mode: the page again, for the hostings that answer an unknown
|
|
21
25
|
* path with it
|
|
@@ -36,6 +40,10 @@ export type WebConfig = {
|
|
|
36
40
|
themeColor?: string
|
|
37
41
|
/** The page's language (`<html lang>`). */
|
|
38
42
|
lang?: string
|
|
43
|
+
/** The font of a text that names none: `roboto` (default; the browser fetches the parts the
|
|
44
|
+
* page shows) or `system` — the device's own font, nothing fetched, the width of a text the
|
|
45
|
+
* device's. */
|
|
46
|
+
font?: "roboto" | "system"
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
export type WebBuildOptions = {
|
|
@@ -123,6 +131,17 @@ export const buildWeb = async (options: WebBuildOptions): Promise<WebBuildResult
|
|
|
123
131
|
rmSync(out, { recursive: true, force: true })
|
|
124
132
|
mkdirSync(out, { recursive: true })
|
|
125
133
|
cpSync(lite, out, { recursive: true })
|
|
134
|
+
// the features this bundle never names are not part of its page
|
|
135
|
+
const table = join(out, "features.json")
|
|
136
|
+
if (existsSync(table)) {
|
|
137
|
+
const { features, fonts } = JSON.parse(readFileSync(table, "utf8")) as { features: { name: string, uses: string[], files: string[] }[], fonts?: string[] }
|
|
138
|
+
if (config.font === "system") for (const file of fonts ?? []) rmSync(join(out, file), { force: true })
|
|
139
|
+
for (const feature of features) {
|
|
140
|
+
if (feature.uses.some((name) => js.includes(name))) continue
|
|
141
|
+
for (const file of feature.files) rmSync(join(out, file), { force: true })
|
|
142
|
+
}
|
|
143
|
+
rmSync(table)
|
|
144
|
+
}
|
|
126
145
|
|
|
127
146
|
const bundle = `app.${createHash("sha256").update(js).digest("hex").slice(0, 10)}.js`
|
|
128
147
|
writeFileSync(join(out, bundle), js)
|
|
@@ -154,7 +173,11 @@ export const buildWeb = async (options: WebBuildOptions): Promise<WebBuildResult
|
|
|
154
173
|
}
|
|
155
174
|
// in path mode a route is a path: what the page names relatively is under the base, whatever the route
|
|
156
175
|
if (mode === "path") head.unshift(`<base href="${escapeHtml(base)}" />`)
|
|
157
|
-
const attrs = [
|
|
176
|
+
const attrs = [
|
|
177
|
+
`data-src="${bundle}"`,
|
|
178
|
+
...(mode === "path" ? [`data-urls="path"`, `data-base="${escapeHtml(base)}"`] : []),
|
|
179
|
+
...(config.font === "system" ? [`data-font="system"`] : []),
|
|
180
|
+
].join(" ")
|
|
158
181
|
const replace = (from: string | RegExp, to: string) => {
|
|
159
182
|
const next = html.replace(from, to)
|
|
160
183
|
if (next === html) throw new CliError(`The page of Web Lite has no ${String(from)} to fill — this CLI and its page are of different versions.`)
|
|
Binary file
|
|
Binary file
|