@onslaughtsnail/caelis 0.0.31 → 0.0.33

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 CHANGED
@@ -18,8 +18,6 @@ npx @onslaughtsnail/caelis --help
18
18
 
19
19
  ## How it works
20
20
 
21
- This package downloads the platform-specific `caelis` binary from GitHub Releases during `postinstall`.
21
+ This package installs a platform-specific `caelis` binary from npm optional dependencies.
22
22
 
23
- If your environment blocks postinstall network access, download the binary from:
24
-
25
- - https://github.com/OnslaughtSnail/caelis/releases
23
+ This keeps installation traffic on the npm registry path instead of fetching binaries from GitHub Releases during `postinstall`.
package/bin/caelis.js CHANGED
@@ -4,12 +4,41 @@ const { spawn } = require('node:child_process');
4
4
  const fs = require('node:fs');
5
5
  const path = require('node:path');
6
6
 
7
- const exeName = process.platform === 'win32' ? 'caelis.exe' : 'caelis';
8
- const binPath = path.join(__dirname, '..', 'runtime', exeName);
7
+ const packageMap = {
8
+ 'darwin:arm64': '@onslaughtsnail/caelis-darwin-arm64',
9
+ 'darwin:x64': '@onslaughtsnail/caelis-darwin-x64',
10
+ 'linux:arm64': '@onslaughtsnail/caelis-linux-arm64',
11
+ 'linux:x64': '@onslaughtsnail/caelis-linux-x64',
12
+ };
13
+
14
+ function resolvePackageName() {
15
+ const key = `${process.platform}:${process.arch}`;
16
+ const packageName = packageMap[key];
17
+ if (!packageName) {
18
+ console.error(`[caelis] unsupported platform/arch: ${process.platform}/${process.arch}`);
19
+ process.exit(1);
20
+ }
21
+ return packageName;
22
+ }
23
+
24
+ function resolveBinaryPath(packageName) {
25
+ try {
26
+ const packageJsonPath = require.resolve(`${packageName}/package.json`);
27
+ return path.join(path.dirname(packageJsonPath), 'runtime', 'caelis');
28
+ } catch (err) {
29
+ console.error(`[caelis] platform package not installed: ${packageName}`);
30
+ console.error('[caelis] reinstall without --omit=optional, then try again.');
31
+ console.error('[caelis] resolve error:', err.message);
32
+ process.exit(1);
33
+ }
34
+ }
35
+
36
+ const packageName = resolvePackageName();
37
+ const binPath = resolveBinaryPath(packageName);
9
38
 
10
39
  if (!fs.existsSync(binPath)) {
11
40
  console.error('[caelis] binary not found at', binPath);
12
- console.error('[caelis] try: npm rebuild @onslaughtsnail/caelis');
41
+ console.error(`[caelis] reinstall ${packageName}, then try again.`);
13
42
  process.exit(1);
14
43
  }
15
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onslaughtsnail/caelis",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "description": "caelis CLI distributed via npm",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -17,7 +17,6 @@
17
17
  "files": [
18
18
  "bin",
19
19
  "scripts",
20
- "runtime",
21
20
  "README.md",
22
21
  "LICENSE"
23
22
  ],
@@ -30,14 +29,19 @@
30
29
  "arm64"
31
30
  ],
32
31
  "scripts": {
33
- "postinstall": "node ./scripts/postinstall.mjs",
34
- "prepare": "node ./scripts/prepare.mjs"
32
+ "prepare": "node ./scripts/prepare.mjs",
33
+ "set-version": "node ./scripts/set-version.mjs",
34
+ "stage-release": "node ./scripts/stage-release.mjs",
35
+ "publish-release": "node ./scripts/publish-release.mjs"
35
36
  },
36
- "dependencies": {
37
- "extract-zip": "^2.0.1",
38
- "tar": "^7.4.3"
37
+ "optionalDependencies": {
38
+ "@onslaughtsnail/caelis-darwin-arm64": "0.0.33",
39
+ "@onslaughtsnail/caelis-darwin-x64": "0.0.33",
40
+ "@onslaughtsnail/caelis-linux-arm64": "0.0.33",
41
+ "@onslaughtsnail/caelis-linux-x64": "0.0.33"
39
42
  },
40
43
  "publishConfig": {
41
- "access": "public"
44
+ "access": "public",
45
+ "provenance": true
42
46
  }
43
47
  }
@@ -0,0 +1,79 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { execFile, spawn } from 'node:child_process';
5
+ import { promisify } from 'node:util';
6
+
7
+ const execFileAsync = promisify(execFile);
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ const packageRoot = path.resolve(__dirname, '..');
11
+
12
+ function runCommand(command, args, options) {
13
+ return new Promise((resolve, reject) => {
14
+ const child = spawn(command, args, {
15
+ ...options,
16
+ stdio: 'inherit',
17
+ });
18
+ child.on('error', reject);
19
+ child.on('exit', (code) => {
20
+ if (code === 0) {
21
+ resolve();
22
+ return;
23
+ }
24
+ reject(new Error(`${command} exited with code ${code}`));
25
+ });
26
+ });
27
+ }
28
+
29
+ async function readManifest(packageDir) {
30
+ const manifestPath = path.join(packageDir, 'package.json');
31
+ const raw = await fs.readFile(manifestPath, 'utf8');
32
+ return JSON.parse(raw);
33
+ }
34
+
35
+ async function versionExists(name, version) {
36
+ try {
37
+ const { stdout } = await execFileAsync(
38
+ 'npm',
39
+ ['view', `${name}@${version}`, 'version', '--registry=https://registry.npmjs.org'],
40
+ { cwd: packageRoot },
41
+ );
42
+ return stdout.trim() === version;
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ async function publishPackage(packageDir) {
49
+ const manifest = await readManifest(packageDir);
50
+ if (await versionExists(manifest.name, manifest.version)) {
51
+ console.log(`[caelis] skip publish for ${manifest.name}@${manifest.version}; already exists`);
52
+ return;
53
+ }
54
+ console.log(`[caelis] publishing ${manifest.name}@${manifest.version}`);
55
+ const args = ['publish', '--access', 'public'];
56
+ if (process.env.CAELIS_NPM_PUBLISH_PROVENANCE === 'false') {
57
+ args.push('--provenance=false');
58
+ }
59
+ if (manifest.version.includes('-')) {
60
+ args.push('--tag', 'bootstrap');
61
+ }
62
+ await runCommand('npm', args, { cwd: packageDir });
63
+ }
64
+
65
+ async function main() {
66
+ const inputs = process.argv.slice(2);
67
+ if (inputs.length === 0) {
68
+ throw new Error('expected at least one package directory');
69
+ }
70
+ for (const input of inputs) {
71
+ const packageDir = path.resolve(input);
72
+ await publishPackage(packageDir);
73
+ }
74
+ }
75
+
76
+ main().catch((err) => {
77
+ console.error('[caelis] failed to publish packages:', err.message);
78
+ process.exit(1);
79
+ });
@@ -0,0 +1,52 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const packageRoot = path.resolve(__dirname, '..');
8
+
9
+ const platformPackages = [
10
+ '@onslaughtsnail/caelis-darwin-arm64',
11
+ '@onslaughtsnail/caelis-darwin-x64',
12
+ '@onslaughtsnail/caelis-linux-arm64',
13
+ '@onslaughtsnail/caelis-linux-x64',
14
+ ];
15
+
16
+ const manifestPaths = [
17
+ path.join(packageRoot, 'package.json'),
18
+ path.join(packageRoot, 'packages', 'caelis-darwin-arm64', 'package.json'),
19
+ path.join(packageRoot, 'packages', 'caelis-darwin-x64', 'package.json'),
20
+ path.join(packageRoot, 'packages', 'caelis-linux-arm64', 'package.json'),
21
+ path.join(packageRoot, 'packages', 'caelis-linux-x64', 'package.json'),
22
+ ];
23
+
24
+ function normalizeVersion(input) {
25
+ const version = String(input || '').trim().replace(/^v/, '');
26
+ if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(version)) {
27
+ throw new Error(`invalid version: ${input}`);
28
+ }
29
+ return version;
30
+ }
31
+
32
+ async function updateManifest(manifestPath, version) {
33
+ const raw = await fs.readFile(manifestPath, 'utf8');
34
+ const manifest = JSON.parse(raw);
35
+ manifest.version = version;
36
+ if (manifest.name === '@onslaughtsnail/caelis') {
37
+ manifest.optionalDependencies = Object.fromEntries(
38
+ platformPackages.map((packageName) => [packageName, version]),
39
+ );
40
+ }
41
+ await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
42
+ }
43
+
44
+ async function main() {
45
+ const version = normalizeVersion(process.argv[2]);
46
+ await Promise.all(manifestPaths.map((manifestPath) => updateManifest(manifestPath, version)));
47
+ }
48
+
49
+ main().catch((err) => {
50
+ console.error('[caelis] failed to update package versions:', err.message);
51
+ process.exit(1);
52
+ });
@@ -0,0 +1,78 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { execFile } from 'node:child_process';
6
+ import { promisify } from 'node:util';
7
+
8
+ const execFileAsync = promisify(execFile);
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ const packageRoot = path.resolve(__dirname, '..');
12
+
13
+ const targets = [
14
+ { os: 'darwin', arch: 'arm64', dir: 'caelis-darwin-arm64' },
15
+ { os: 'darwin', arch: 'amd64', dir: 'caelis-darwin-x64' },
16
+ { os: 'linux', arch: 'arm64', dir: 'caelis-linux-arm64' },
17
+ { os: 'linux', arch: 'amd64', dir: 'caelis-linux-x64' },
18
+ ];
19
+
20
+ function normalizeVersion(input) {
21
+ const version = String(input || '').trim().replace(/^v/, '');
22
+ if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(version)) {
23
+ throw new Error(`invalid version: ${input}`);
24
+ }
25
+ return version;
26
+ }
27
+
28
+ async function findFile(rootDir, expectedName) {
29
+ const queue = [rootDir];
30
+ while (queue.length > 0) {
31
+ const current = queue.shift();
32
+ const entries = await fs.readdir(current, { withFileTypes: true });
33
+ for (const entry of entries) {
34
+ const full = path.join(current, entry.name);
35
+ if (entry.isDirectory()) {
36
+ queue.push(full);
37
+ continue;
38
+ }
39
+ if (entry.isFile() && entry.name === expectedName) {
40
+ return full;
41
+ }
42
+ }
43
+ }
44
+ return '';
45
+ }
46
+
47
+ async function stageTarget(version, distDir, target) {
48
+ const archiveName = `caelis_${version}_${target.os}_${target.arch}.tar.gz`;
49
+ const archivePath = path.join(distDir, archiveName);
50
+ const packageDir = path.join(packageRoot, 'packages', target.dir);
51
+ const runtimeDir = path.join(packageDir, 'runtime');
52
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), `caelis-${target.dir}-`));
53
+ try {
54
+ await fs.access(archivePath);
55
+ await execFileAsync('tar', ['-xzf', archivePath, '-C', tempDir]);
56
+ const extractedBin = await findFile(tempDir, 'caelis');
57
+ if (!extractedBin) {
58
+ throw new Error(`binary not found in ${archiveName}`);
59
+ }
60
+ await fs.mkdir(runtimeDir, { recursive: true });
61
+ const destPath = path.join(runtimeDir, 'caelis');
62
+ await fs.copyFile(extractedBin, destPath);
63
+ await fs.chmod(destPath, 0o755);
64
+ } finally {
65
+ await fs.rm(tempDir, { recursive: true, force: true });
66
+ }
67
+ }
68
+
69
+ async function main() {
70
+ const version = normalizeVersion(process.argv[2]);
71
+ const distDir = path.resolve(process.argv[3] || path.join(packageRoot, '..', 'dist'));
72
+ await Promise.all(targets.map((target) => stageTarget(version, distDir, target)));
73
+ }
74
+
75
+ main().catch((err) => {
76
+ console.error('[caelis] failed to stage platform packages:', err.message);
77
+ process.exit(1);
78
+ });
package/runtime/.gitkeep DELETED
File without changes
@@ -1,196 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import { createWriteStream } from 'node:fs';
3
- import { constants as fsConstants } from 'node:fs';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
- import https from 'node:https';
8
- import { pipeline } from 'node:stream/promises';
9
-
10
- const __filename = fileURLToPath(import.meta.url);
11
- const __dirname = path.dirname(__filename);
12
- const packageDir = path.resolve(__dirname, '..');
13
- const runtimeDir = path.join(packageDir, 'runtime');
14
- const packageJsonPath = path.join(packageDir, 'package.json');
15
-
16
- const repoOwner = 'OnslaughtSnail';
17
- const repoName = 'caelis';
18
- const skipVar = process.env.CAELIS_NPM_SKIP_DOWNLOAD;
19
-
20
- function resolveTarget() {
21
- const platform = process.platform;
22
- const arch = process.arch;
23
-
24
- const osMap = {
25
- darwin: 'darwin',
26
- linux: 'linux',
27
- };
28
- const archMap = {
29
- x64: 'amd64',
30
- arm64: 'arm64',
31
- };
32
-
33
- const goos = osMap[platform];
34
- const goarch = archMap[arch];
35
- if (!goos || !goarch) {
36
- throw new Error(`unsupported platform/arch: ${platform}/${arch}; supported: darwin|linux with x64|arm64`);
37
- }
38
-
39
- return {
40
- platform,
41
- goos,
42
- goarch,
43
- archiveExt: goos === 'windows' ? 'zip' : 'tar.gz',
44
- binName: goos === 'windows' ? 'caelis.exe' : 'caelis',
45
- };
46
- }
47
-
48
- async function readPackageVersion() {
49
- const raw = await fs.readFile(packageJsonPath, 'utf8');
50
- const pkg = JSON.parse(raw);
51
- const version = String(pkg.version || '').trim();
52
- if (!version) {
53
- throw new Error('invalid package version for release download');
54
- }
55
- return version;
56
- }
57
-
58
- function buildAssetURL(version, target) {
59
- const tag = `v${version}`;
60
- const file = `${repoName}_${version}_${target.goos}_${target.goarch}.${target.archiveExt}`;
61
- return {
62
- url: `https://github.com/${repoOwner}/${repoName}/releases/download/${tag}/${file}`,
63
- filename: file,
64
- };
65
- }
66
-
67
- async function download(url, toFile, redirects = 0) {
68
- if (redirects > 5) {
69
- throw new Error(`too many redirects while downloading ${url}`);
70
- }
71
- await fs.mkdir(path.dirname(toFile), { recursive: true });
72
- await new Promise((resolve, reject) => {
73
- const req = https.get(url, (res) => {
74
- if ([301, 302, 307, 308].includes(res.statusCode || 0)) {
75
- const location = String(res.headers.location || '').trim();
76
- if (!location) {
77
- reject(new Error(`redirect without location header (${url})`));
78
- return;
79
- }
80
- const nextURL = new URL(location, url).toString();
81
- res.resume();
82
- download(nextURL, toFile, redirects + 1).then(resolve).catch(reject);
83
- return;
84
- }
85
- if (res.statusCode !== 200) {
86
- reject(new Error(`download failed: HTTP ${res.statusCode} (${url})`));
87
- res.resume();
88
- return;
89
- }
90
- pipeline(res, createWriteStream(toFile)).then(resolve).catch(reject);
91
- });
92
- req.on('error', reject);
93
- });
94
- }
95
-
96
- async function extractArchive(archivePath, tempDir, target) {
97
- if (target.archiveExt === 'zip') {
98
- const extractMod = await import('extract-zip');
99
- const extract = extractMod.default || extractMod;
100
- if (typeof extract !== 'function') {
101
- throw new Error('extract-zip module does not export a callable extractor');
102
- }
103
- await extract(archivePath, { dir: tempDir });
104
- return;
105
- }
106
- const tarMod = await import('tar');
107
- const tar = tarMod.default || tarMod;
108
- if (!tar || typeof tar.x !== 'function') {
109
- throw new Error('tar module does not export x() extractor');
110
- }
111
- await tar.x({
112
- file: archivePath,
113
- cwd: tempDir,
114
- gzip: true,
115
- });
116
- }
117
-
118
- async function findFile(rootDir, expectedName) {
119
- const queue = [rootDir];
120
- while (queue.length > 0) {
121
- const current = queue.shift();
122
- const entries = await fs.readdir(current, { withFileTypes: true });
123
- for (const entry of entries) {
124
- const full = path.join(current, entry.name);
125
- if (entry.isDirectory()) {
126
- queue.push(full);
127
- continue;
128
- }
129
- if (entry.isFile() && entry.name === expectedName) {
130
- return full;
131
- }
132
- }
133
- }
134
- return '';
135
- }
136
-
137
- async function installBinary(sourcePath, targetName) {
138
- await fs.mkdir(runtimeDir, { recursive: true });
139
- const destPath = path.join(runtimeDir, targetName);
140
- await fs.copyFile(sourcePath, destPath);
141
- if (process.platform !== 'win32') {
142
- await fs.chmod(destPath, 0o755);
143
- }
144
- return destPath;
145
- }
146
-
147
- async function fileExists(p) {
148
- try {
149
- await fs.access(p, fsConstants.F_OK);
150
- return true;
151
- } catch {
152
- return false;
153
- }
154
- }
155
-
156
- async function main() {
157
- if (skipVar === '1' || skipVar === 'true') {
158
- console.log('[caelis] skip download due to CAELIS_NPM_SKIP_DOWNLOAD');
159
- return;
160
- }
161
-
162
- const version = await readPackageVersion();
163
- if (version === '0.0.0') {
164
- console.log('[caelis] skip download for development version 0.0.0');
165
- return;
166
- }
167
- const target = resolveTarget();
168
- const existingPath = path.join(runtimeDir, target.binName);
169
- if (await fileExists(existingPath)) {
170
- return;
171
- }
172
-
173
- const { url, filename } = buildAssetURL(version, target);
174
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'caelis-npm-'));
175
- const archivePath = path.join(tempDir, filename);
176
-
177
- try {
178
- console.log(`[caelis] downloading ${url}`);
179
- await download(url, archivePath);
180
- await extractArchive(archivePath, tempDir, target);
181
- const extractedBin = await findFile(tempDir, target.binName);
182
- if (!extractedBin) {
183
- throw new Error(`binary ${target.binName} not found in release archive`);
184
- }
185
- const installed = await installBinary(extractedBin, target.binName);
186
- console.log(`[caelis] installed binary: ${installed}`);
187
- } finally {
188
- await fs.rm(tempDir, { recursive: true, force: true });
189
- }
190
- }
191
-
192
- main().catch((err) => {
193
- console.error('[caelis] postinstall failed:', err.message);
194
- console.error('[caelis] fallback: download release binary manually from GitHub Releases.');
195
- process.exit(1);
196
- });