stackbite 0.5.14 → 0.5.15
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 +47 -9
- package/package.json +1 -1
- package/packaging/npm/lib/bootstrap.js +29 -6
- package/packaging/npm/lib/cache.js +14 -1
- package/packaging/npm/lib/setup.js +24 -0
- package/packaging/npm/release.json +16 -16
package/README.md
CHANGED
|
@@ -15,16 +15,41 @@ Choose one method. For a single run, use the public npm package:
|
|
|
15
15
|
npx --yes stackbite
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
For a persistent command, install it globally
|
|
18
|
+
For a persistent command, install it globally, prepare the runtime, then launch:
|
|
19
19
|
|
|
20
20
|
```sh
|
|
21
21
|
npm install --global stackbite
|
|
22
|
+
stackbite setup
|
|
22
23
|
stackbite
|
|
23
24
|
```
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
Global installation installs the small launcher without a postinstall download.
|
|
27
|
+
`stackbite setup` downloads and fully validates its pinned runtime without starting
|
|
28
|
+
the app or backend. It prints short stages to stderr. If setup is omitted, the
|
|
29
|
+
first launch prepares the runtime with the same stages. Cached launches remain
|
|
30
|
+
quiet and fully validate the runtime every time. Running both installation methods
|
|
31
|
+
is unnecessary.
|
|
32
|
+
|
|
33
|
+
The same preparation command works through npx:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npx --yes stackbite setup
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Check a prepared runtime offline with `stackbite setup --check`. The launcher
|
|
40
|
+
checks all pinned file hashes, identity, inventory and link boundaries, without
|
|
41
|
+
downloading, extracting, writing cache files, or starting the app/backend. It exits
|
|
42
|
+
with status 0 when valid and status 1 when absent or invalid; an absent cache needs
|
|
43
|
+
`stackbite setup`. Only `setup` and `setup --check` are accepted setup forms.
|
|
44
|
+
The launcher check is read-only; npx itself may install its npm package before
|
|
45
|
+
invoking it, so use the installed command for an entirely offline check.
|
|
46
|
+
|
|
47
|
+
Binary caches use `%LOCALAPPDATA%\Stackbite\cache` on Windows and
|
|
48
|
+
`${XDG_CACHE_HOME:-~/.cache}/stackbite` on Linux. `LOCALAPPDATA`, `XDG_CACHE_HOME`
|
|
49
|
+
and `HOME` overrides retain their normal behavior. These caches are separate from
|
|
50
|
+
application state. Interrupted preparation removes only its own temporary staging
|
|
51
|
+
directory and can be retried. An invalid existing runtime is reported and left in
|
|
52
|
+
place; setup does not delete or overwrite it.
|
|
28
53
|
|
|
29
54
|
Update a registry installation with `npm install --global stackbite@latest`.
|
|
30
55
|
|
|
@@ -33,14 +58,15 @@ Update a registry installation with `npm install --global stackbite@latest`.
|
|
|
33
58
|
Run the package attached to the verified GitHub release:
|
|
34
59
|
|
|
35
60
|
```sh
|
|
36
|
-
npx --yes https://github.com/Shinick-Han/stackbite/releases/download/v0.5.
|
|
61
|
+
npx --yes https://github.com/Shinick-Han/stackbite/releases/download/v0.5.15/stackbite-0.5.15.tgz
|
|
37
62
|
```
|
|
38
63
|
|
|
39
64
|
Arguments go after the package URL, for example `--version` or `--status`. For a
|
|
40
65
|
persistent command, install that same package globally:
|
|
41
66
|
|
|
42
67
|
```sh
|
|
43
|
-
npm install --global https://github.com/Shinick-Han/stackbite/releases/download/v0.5.
|
|
68
|
+
npm install --global https://github.com/Shinick-Han/stackbite/releases/download/v0.5.15/stackbite-0.5.15.tgz
|
|
69
|
+
stackbite setup
|
|
44
70
|
stackbite
|
|
45
71
|
```
|
|
46
72
|
|
|
@@ -70,18 +96,30 @@ contracts are not renamed or migrated. Binary cache data is separate from this
|
|
|
70
96
|
state. Installing or launching the npm package does not stop an existing backend.
|
|
71
97
|
Check the connected server's capabilities before using newly added runtime features.
|
|
72
98
|
|
|
73
|
-
|
|
74
|
-
the native app.
|
|
99
|
+
For app commands, the launcher inherits terminal input/output and passes CLI
|
|
100
|
+
arguments directly to the native app. Its `setup` command only prepares/checks the
|
|
101
|
+
runtime. It does not insert a second screen renderer or an agent provider.
|
|
75
102
|
Global npm installation creates only the `stackbite` command; it does not claim an
|
|
76
103
|
external `helm` command or replace independent Kubernetes tooling.
|
|
77
104
|
|
|
78
105
|
## Maintainer release steps
|
|
79
106
|
|
|
107
|
+
Measure bounded fake runtime preparation and warm verification without network or
|
|
108
|
+
app processes:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
node packaging/npm/test/measure-setup.js
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The measurement reports fixture sizes and each preparation stage plus warm check
|
|
115
|
+
and launch validation costs. It uses disposable cache data and an inert child
|
|
116
|
+
stub; its timings are not measurements of released native bundles or providers.
|
|
117
|
+
|
|
80
118
|
Build and verify both native platform archives first. Prepare the pinned manifest
|
|
81
119
|
from their exact extracted bundles and archive bytes:
|
|
82
120
|
|
|
83
121
|
```sh
|
|
84
|
-
python scripts/prepare_npm_release.py --version 0.5.
|
|
122
|
+
python scripts/prepare_npm_release.py --version 0.5.15
|
|
85
123
|
npm test
|
|
86
124
|
npm pack
|
|
87
125
|
```
|
package/package.json
CHANGED
|
@@ -6,7 +6,8 @@ const { createHash } = require('node:crypto');
|
|
|
6
6
|
const { LIMITS, fail, memberPath, inside, hashFile, readSmall, exists, safeDirectory } = require('./common');
|
|
7
7
|
const { download } = require('./download');
|
|
8
8
|
const { extractArchive } = require('./archive');
|
|
9
|
-
const { cacheDirectory } = require('./cache');
|
|
9
|
+
const { cacheDirectory, existingCacheDirectory } = require('./cache');
|
|
10
|
+
const { parseSetup, stageReporter } = require('./setup');
|
|
10
11
|
const packageVersion = require('../../../package.json').version;
|
|
11
12
|
|
|
12
13
|
function releaseSpec(manifest, platform = process.platform, arch = process.arch, runtime = {}) {
|
|
@@ -95,28 +96,46 @@ async function validatePayload(directory, spec) {
|
|
|
95
96
|
if (spec.platform === 'linux' && process.platform !== 'win32' && !(image.mode & 0o111)) fail('Native release executable has no execute permission');
|
|
96
97
|
return executable;
|
|
97
98
|
}
|
|
98
|
-
async function ensureBinary(spec, { cache = cacheDirectory(), downloadImpl = download, extractImpl = extractArchive
|
|
99
|
+
async function ensureBinary(spec, { cache = cacheDirectory(), downloadImpl = download, extractImpl = extractArchive,
|
|
100
|
+
checkOnly = false, reportCached = false, progress = () => {} } = {}) {
|
|
99
101
|
cache = path.resolve(cache);
|
|
100
|
-
|
|
102
|
+
if (checkOnly) {
|
|
103
|
+
if (!await existingCacheDirectory(cache)) fail('Pinned runtime is not cached; run stackbite setup.');
|
|
104
|
+
} else await safeDirectory(cache);
|
|
101
105
|
const target = path.join(cache, `${spec.version}-${spec.commit}-${spec.key}-${spec.sha256}`);
|
|
102
|
-
if (await exists(target))
|
|
106
|
+
if (await exists(target)) {
|
|
107
|
+
if (checkOnly || reportCached) progress('validate');
|
|
108
|
+
const installed = { home: target, executable: await validatePayload(target, spec) };
|
|
109
|
+
if (checkOnly || reportCached) progress('ready');
|
|
110
|
+
return installed;
|
|
111
|
+
}
|
|
112
|
+
if (checkOnly) fail('Pinned runtime is not cached; run stackbite setup.');
|
|
103
113
|
const staging = await fs.mkdtemp(path.join(cache, '.staging-'));
|
|
104
114
|
try {
|
|
105
115
|
const archive = path.join(staging, spec.asset), extraction = path.join(staging, 'extract');
|
|
106
116
|
await fs.mkdir(extraction, { mode: 0o700 });
|
|
117
|
+
progress('download');
|
|
107
118
|
await downloadImpl(spec, archive);
|
|
108
119
|
// Recheck at the extraction boundary, including for injected test transports.
|
|
120
|
+
progress('archive');
|
|
109
121
|
if ((await fs.stat(archive)).size !== spec.size || await hashFile(archive, LIMITS.download) !== spec.sha256) fail('Pinned archive SHA-256 or size mismatch');
|
|
122
|
+
progress('extract');
|
|
110
123
|
await extractImpl(archive, extraction, spec);
|
|
111
124
|
const payload = path.join(extraction, spec.root);
|
|
125
|
+
progress('validate');
|
|
112
126
|
await validatePayload(payload, spec);
|
|
127
|
+
progress('publish');
|
|
113
128
|
try { await fs.rename(payload, target); }
|
|
114
129
|
catch (error) {
|
|
115
130
|
// Another process may have installed the same immutable payload first.
|
|
116
131
|
// Never delete/rewrite its directory or try a destructive rename retry.
|
|
117
132
|
if (!await exists(target)) throw error;
|
|
118
133
|
}
|
|
119
|
-
|
|
134
|
+
// Validate at the final path, including a concurrent publisher's payload.
|
|
135
|
+
progress('validate-published');
|
|
136
|
+
const installed = { home: target, executable: await validatePayload(target, spec) };
|
|
137
|
+
progress('ready');
|
|
138
|
+
return installed;
|
|
120
139
|
} finally {
|
|
121
140
|
// Only this mkdtemp handle is owned by this invocation.
|
|
122
141
|
if (!inside(cache, staging) || path.dirname(staging) !== cache) fail('Unsafe staging cleanup path');
|
|
@@ -138,11 +157,15 @@ async function launchNative(executable, argv, environment, { spawnImpl = spawn,
|
|
|
138
157
|
});
|
|
139
158
|
}
|
|
140
159
|
async function main(argv = process.argv.slice(2), options = {}) {
|
|
160
|
+
const setup = parseSetup(argv);
|
|
141
161
|
const manifest = options.manifest || require('../release.json');
|
|
142
162
|
const platform = options.platform || process.platform;
|
|
143
163
|
const environment = options.environment || process.env;
|
|
144
164
|
const spec = releaseSpec(manifest, platform, options.arch || process.arch, options);
|
|
145
|
-
const installed = await ensureBinary(spec, { ...options, cache: options.cache || cacheDirectory(environment, platform)
|
|
165
|
+
const installed = await ensureBinary(spec, { ...options, cache: options.cache || cacheDirectory(environment, platform),
|
|
166
|
+
checkOnly: setup?.checkOnly || false, reportCached: Boolean(setup),
|
|
167
|
+
progress: options.progress || stageReporter(options.stderr || process.stderr) });
|
|
168
|
+
if (setup) return { code: 0, signal: null };
|
|
146
169
|
// The bundled runtime owns state discovery and migration for every entrypoint.
|
|
147
170
|
return launchNative(installed.executable, argv, { ...environment,
|
|
148
171
|
STACKBITE_HOME: path.resolve(installed.home) }, options);
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
const os = require('node:os');
|
|
4
|
+
const fs = require('node:fs/promises');
|
|
5
|
+
const { fail } = require('./common');
|
|
4
6
|
|
|
5
7
|
function cacheDirectory(environment = process.env, platform = process.platform, home = os.homedir()) {
|
|
6
8
|
return platform === 'win32'
|
|
7
9
|
? path.resolve(environment.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'Stackbite', 'cache')
|
|
8
10
|
: path.resolve(environment.XDG_CACHE_HOME || path.join(environment.HOME || home, '.cache'), 'stackbite');
|
|
9
11
|
}
|
|
10
|
-
|
|
12
|
+
// Check every ancestor without creating anything, including for an absent cache.
|
|
13
|
+
async function existingCacheDirectory(directory) {
|
|
14
|
+
directory = path.resolve(directory);
|
|
15
|
+
const parent = path.dirname(directory);
|
|
16
|
+
if (parent !== directory && !await existingCacheDirectory(parent)) return false;
|
|
17
|
+
let info;
|
|
18
|
+
try { info = await fs.lstat(directory); }
|
|
19
|
+
catch (error) { if (error.code === 'ENOENT') return false; throw error; }
|
|
20
|
+
if (!info.isDirectory() || info.isSymbolicLink()) fail('Cache directory contains a link or non-directory');
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
module.exports = { cacheDirectory, existingCacheDirectory };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function parseSetup(argv) {
|
|
4
|
+
if (argv[0] !== 'setup') return null;
|
|
5
|
+
if (argv.length === 1) return { checkOnly: false };
|
|
6
|
+
if (argv.length === 2 && argv[1] === '--check') return { checkOnly: true };
|
|
7
|
+
throw new Error('Usage: stackbite setup [--check]');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Only static text goes to stderr: no URLs, environment, credentials or state.
|
|
11
|
+
function stageReporter(stderr) {
|
|
12
|
+
const messages = {
|
|
13
|
+
download: 'Downloading pinned runtime...',
|
|
14
|
+
archive: 'Verifying archive...',
|
|
15
|
+
extract: 'Extracting runtime...',
|
|
16
|
+
validate: 'Validating runtime files...',
|
|
17
|
+
publish: 'Publishing runtime cache...',
|
|
18
|
+
'validate-published': 'Validating published runtime...',
|
|
19
|
+
ready: 'Pinned runtime is ready.'
|
|
20
|
+
};
|
|
21
|
+
return stage => stderr.write(`Stackbite: ${messages[stage]}\n`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { parseSetup, stageReporter };
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"commit": "
|
|
3
|
+
"version": "0.5.15",
|
|
4
|
+
"commit": "c62e74d00187186f8c7f9fe6b80ce80d0aed00dc",
|
|
5
5
|
"repository": "Shinick-Han/stackbite",
|
|
6
6
|
"platforms": {
|
|
7
7
|
"win32-x64": {
|
|
8
|
-
"asset": "stackbite-0.5.
|
|
9
|
-
"url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.
|
|
10
|
-
"sha256": "
|
|
11
|
-
"size":
|
|
12
|
-
"root": "stackbite-0.5.
|
|
13
|
-
"manifestSha256": "
|
|
14
|
-
"metadataSha256": "
|
|
8
|
+
"asset": "stackbite-0.5.15-windows-x64-c62e74d00187.zip",
|
|
9
|
+
"url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.15/stackbite-0.5.15-windows-x64-c62e74d00187.zip",
|
|
10
|
+
"sha256": "f9c9483e0a63a05dde48ff77f0c90cf02138b0344b7335d4bd1ce3210ed8b693",
|
|
11
|
+
"size": 37096893,
|
|
12
|
+
"root": "stackbite-0.5.15-windows-x64-c62e74d00187",
|
|
13
|
+
"manifestSha256": "e2d466a8b747b1028ddcf41a1274c9eb0dd29d8583575fafb9d15fb96561d50e",
|
|
14
|
+
"metadataSha256": "e79c47fa501317285c08fb57c213354abeff9b3cc333f4e9260eacc90919303d"
|
|
15
15
|
},
|
|
16
16
|
"linux-x64": {
|
|
17
|
-
"asset": "stackbite-0.5.
|
|
18
|
-
"url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.
|
|
19
|
-
"sha256": "
|
|
20
|
-
"size":
|
|
21
|
-
"root": "stackbite-0.5.
|
|
22
|
-
"manifestSha256": "
|
|
23
|
-
"metadataSha256": "
|
|
17
|
+
"asset": "stackbite-0.5.15-linux-x86_64-c62e74d00187.tar.gz",
|
|
18
|
+
"url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.15/stackbite-0.5.15-linux-x86_64-c62e74d00187.tar.gz",
|
|
19
|
+
"sha256": "fe64f4f640d63f993137c441b9037d6130cec5677dc01d70b8abee69c8ba10e1",
|
|
20
|
+
"size": 21897574,
|
|
21
|
+
"root": "stackbite-0.5.15-linux-x86_64-c62e74d00187",
|
|
22
|
+
"manifestSha256": "a3afdd606ec2e45a970eecef2d2df16594587390dd3fbe8028865fa3968f3e64",
|
|
23
|
+
"metadataSha256": "ecfbaa1e0e96f9a8c814719c039b95f4f1859781ab7b95d00e8c4254956ac74d"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|