sloss-cli 1.0.0 → 1.1.0
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 +35 -0
- package/bin/sloss.js +19 -0
- package/package.json +1 -1
- package/src/client.js +27 -0
- package/src/commands/build.js +115 -0
- package/src/format.js +16 -0
package/README.md
CHANGED
|
@@ -8,6 +8,14 @@ Command-line interface for [Sloss](https://github.com/aualdrich/sloss) — a sel
|
|
|
8
8
|
npm install -g sloss-cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
Or as a project dependency:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install sloss-cli
|
|
15
|
+
# or
|
|
16
|
+
bun add sloss-cli
|
|
17
|
+
```
|
|
18
|
+
|
|
11
19
|
## Quick Start
|
|
12
20
|
|
|
13
21
|
```bash
|
|
@@ -55,6 +63,33 @@ URL resolution order:
|
|
|
55
63
|
--help Show help
|
|
56
64
|
```
|
|
57
65
|
|
|
66
|
+
## Publishing to npm
|
|
67
|
+
|
|
68
|
+
Prerequisites:
|
|
69
|
+
- An [npm](https://www.npmjs.com) account with publish access
|
|
70
|
+
- An npm access token (Granular token with "Bypass 2FA" enabled, or an Automation classic token)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Set your npm token
|
|
74
|
+
echo "//registry.npmjs.org/:_authToken=YOUR_TOKEN" > ~/.npmrc
|
|
75
|
+
|
|
76
|
+
# Bump version (patch/minor/major)
|
|
77
|
+
npm version patch
|
|
78
|
+
|
|
79
|
+
# Publish
|
|
80
|
+
npm publish --access public
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The package is published as [`sloss-cli`](https://www.npmjs.com/package/sloss-cli) under the `front-porch-software` npm account.
|
|
84
|
+
|
|
85
|
+
### What gets published
|
|
86
|
+
|
|
87
|
+
Only `bin/`, `src/`, and `README.md` are included in the npm package (controlled by `files` in `package.json`). Build scripts and other development files are excluded.
|
|
88
|
+
|
|
89
|
+
## Related
|
|
90
|
+
|
|
91
|
+
- **[Sloss Server](https://github.com/aualdrich/sloss)** — The distribution server that this CLI talks to
|
|
92
|
+
|
|
58
93
|
## License
|
|
59
94
|
|
|
60
95
|
MIT
|
package/bin/sloss.js
CHANGED
|
@@ -11,6 +11,7 @@ import { listCommand } from '../src/commands/list.js';
|
|
|
11
11
|
import { uploadCommand } from '../src/commands/upload.js';
|
|
12
12
|
import { infoCommand } from '../src/commands/info.js';
|
|
13
13
|
import { deleteCommand } from '../src/commands/delete.js';
|
|
14
|
+
import { buildCommand } from '../src/commands/build.js';
|
|
14
15
|
|
|
15
16
|
const program = new Command();
|
|
16
17
|
|
|
@@ -100,4 +101,22 @@ program
|
|
|
100
101
|
}
|
|
101
102
|
});
|
|
102
103
|
|
|
104
|
+
// Build command
|
|
105
|
+
program
|
|
106
|
+
.command('build')
|
|
107
|
+
.description('Queue a build via the Sloss build agent')
|
|
108
|
+
.option('--platform <platform>', 'Platform (ios or android)', 'ios')
|
|
109
|
+
.option('--profile <profile>', 'Build profile (development, preview, production)', 'development')
|
|
110
|
+
.option('--bump <type>', 'Version bump type for production (patch, minor, major)', 'patch')
|
|
111
|
+
.option('--dir <path>', 'Project directory (default: current directory)', '.')
|
|
112
|
+
.action(async (options) => {
|
|
113
|
+
try {
|
|
114
|
+
const config = resolveConfig(program.opts());
|
|
115
|
+
await buildCommand(options, config);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error(`Error: ${error.message}`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
103
122
|
program.parse();
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -61,6 +61,33 @@ export class SlossClient {
|
|
|
61
61
|
return await this.request('DELETE', `/api/uploads/${id}`);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
async startBuild(tarballPath, metadata = {}) {
|
|
65
|
+
const fs = await import('fs/promises');
|
|
66
|
+
const path = await import('path');
|
|
67
|
+
|
|
68
|
+
const fileBuffer = await fs.readFile(tarballPath);
|
|
69
|
+
const fileName = path.basename(tarballPath);
|
|
70
|
+
|
|
71
|
+
const formData = new FormData();
|
|
72
|
+
const blob = new Blob([fileBuffer]);
|
|
73
|
+
formData.append('tarball', blob, fileName);
|
|
74
|
+
|
|
75
|
+
// Add build metadata
|
|
76
|
+
if (metadata.profile) formData.append('profile', metadata.profile);
|
|
77
|
+
if (metadata.platform) formData.append('platform', metadata.platform);
|
|
78
|
+
if (metadata.bump) formData.append('bump', metadata.bump);
|
|
79
|
+
if (metadata.appName) formData.append('app_name', metadata.appName);
|
|
80
|
+
if (metadata.bundleId) formData.append('bundle_id', metadata.bundleId);
|
|
81
|
+
if (metadata.version) formData.append('version', metadata.version);
|
|
82
|
+
if (metadata.buildNumber) formData.append('build_number', metadata.buildNumber);
|
|
83
|
+
|
|
84
|
+
return await this.request('POST', '/api/builds/start', formData);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async getBuild(id) {
|
|
88
|
+
return await this.request('GET', `/api/builds/${id}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
64
91
|
async uploadFile(filePath, platform, metadata = {}) {
|
|
65
92
|
const fs = await import('fs/promises');
|
|
66
93
|
const path = await import('path');
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build command - Tar up the project and queue a remote build via Sloss agent
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { SlossClient } from '../client.js';
|
|
6
|
+
import { existsSync, readFileSync } from 'fs';
|
|
7
|
+
import { resolve, basename } from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import { tmpdir } from 'os';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { formatBuild } from '../format.js';
|
|
12
|
+
|
|
13
|
+
export async function buildCommand(options, config) {
|
|
14
|
+
const platform = (options.platform || 'ios').toLowerCase();
|
|
15
|
+
const profile = (options.profile || 'development').toLowerCase();
|
|
16
|
+
const bump = options.bump || 'patch';
|
|
17
|
+
const projectDir = resolve(options.dir || '.');
|
|
18
|
+
|
|
19
|
+
// Validate platform
|
|
20
|
+
if (!['ios', 'android'].includes(platform)) {
|
|
21
|
+
throw new Error('Platform must be "ios" or "android"');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Validate profile
|
|
25
|
+
if (!['development', 'preview', 'production'].includes(profile)) {
|
|
26
|
+
throw new Error('Profile must be "development", "preview", or "production"');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check for .sloss.json
|
|
30
|
+
const slossConfigPath = join(projectDir, '.sloss.json');
|
|
31
|
+
if (!existsSync(slossConfigPath)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`.sloss.json not found in ${projectDir}\n` +
|
|
34
|
+
' Create a .sloss.json config file in your project root.\n' +
|
|
35
|
+
' See: https://github.com/aualdrich/sloss#build-agent'
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Read config for display
|
|
40
|
+
const slossConfig = JSON.parse(readFileSync(slossConfigPath, 'utf8'));
|
|
41
|
+
|
|
42
|
+
console.log('╔══════════════════════════════════════════╗');
|
|
43
|
+
console.log('║ SLOSS BUILD ║');
|
|
44
|
+
console.log('╠══════════════════════════════════════════╣');
|
|
45
|
+
console.log(`║ app : ${(slossConfig.app_name || '—').padEnd(27)} ║`);
|
|
46
|
+
console.log(`║ platform : ${platform.padEnd(27)} ║`);
|
|
47
|
+
console.log(`║ profile : ${profile.padEnd(27)} ║`);
|
|
48
|
+
console.log(`║ bump : ${bump.padEnd(27)} ║`);
|
|
49
|
+
console.log(`║ server : ${config.baseUrl.padEnd(27)} ║`);
|
|
50
|
+
console.log('╚══════════════════════════════════════════╝');
|
|
51
|
+
console.log('');
|
|
52
|
+
|
|
53
|
+
// Create tarball
|
|
54
|
+
console.log('📦 Packaging project...');
|
|
55
|
+
const tarballPath = join(tmpdir(), `sloss-build-${Date.now()}.tar.gz`);
|
|
56
|
+
|
|
57
|
+
// Use git archive if in a git repo (respects .gitignore), otherwise tar with excludes
|
|
58
|
+
let tarCmd;
|
|
59
|
+
try {
|
|
60
|
+
execSync('git rev-parse --git-dir', { cwd: projectDir, stdio: 'pipe' });
|
|
61
|
+
// git archive from the repo root, only including the project subdir if needed
|
|
62
|
+
const gitRoot = execSync('git rev-parse --show-toplevel', { cwd: projectDir, encoding: 'utf8' }).trim();
|
|
63
|
+
const relPath = projectDir.replace(gitRoot, '').replace(/^\//, '');
|
|
64
|
+
|
|
65
|
+
if (relPath) {
|
|
66
|
+
// Project is in a subdirectory — include only that dir
|
|
67
|
+
tarCmd = `cd "${gitRoot}" && git archive --format=tar HEAD -- "${relPath}" | gzip > "${tarballPath}"`;
|
|
68
|
+
} else {
|
|
69
|
+
tarCmd = `cd "${projectDir}" && git archive --format=tar.gz HEAD > "${tarballPath}"`;
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
// Not a git repo — fall back to tar with common excludes
|
|
73
|
+
tarCmd = `cd "${projectDir}" && tar czf "${tarballPath}" --exclude=node_modules --exclude=.git --exclude=ios/build --exclude=android/build .`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
execSync(tarCmd, { stdio: 'pipe' });
|
|
77
|
+
|
|
78
|
+
// Get tarball size for display
|
|
79
|
+
const { statSync } = await import('fs');
|
|
80
|
+
const tarSize = statSync(tarballPath).size;
|
|
81
|
+
const sizeMB = (tarSize / (1024 * 1024)).toFixed(1);
|
|
82
|
+
console.log(` → ${sizeMB} MB`);
|
|
83
|
+
|
|
84
|
+
// Read version info from .sloss.json's version_file
|
|
85
|
+
let version = '';
|
|
86
|
+
let buildNumber = '';
|
|
87
|
+
if (slossConfig.version_file) {
|
|
88
|
+
const versionFilePath = join(projectDir, slossConfig.version_file);
|
|
89
|
+
if (existsSync(versionFilePath)) {
|
|
90
|
+
const versionData = JSON.parse(readFileSync(versionFilePath, 'utf8'));
|
|
91
|
+
version = versionData.version || '';
|
|
92
|
+
buildNumber = versionData.buildNumber || '';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Upload tarball to start build
|
|
97
|
+
console.log('🚀 Queuing build...');
|
|
98
|
+
const client = new SlossClient(config.baseUrl, config.apiKey);
|
|
99
|
+
const result = await client.startBuild(tarballPath, {
|
|
100
|
+
profile,
|
|
101
|
+
platform,
|
|
102
|
+
bump,
|
|
103
|
+
appName: slossConfig.app_name || '',
|
|
104
|
+
bundleId: slossConfig.bundle_id || '',
|
|
105
|
+
version,
|
|
106
|
+
buildNumber,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Clean up tarball
|
|
110
|
+
const { unlinkSync } = await import('fs');
|
|
111
|
+
try { unlinkSync(tarballPath); } catch { /* ignore */ }
|
|
112
|
+
|
|
113
|
+
console.log('');
|
|
114
|
+
console.log(formatBuild(result, config.jsonMode));
|
|
115
|
+
}
|
package/src/format.js
CHANGED
|
@@ -82,6 +82,22 @@ export function formatUpload(result, jsonMode = false) {
|
|
|
82
82
|
return lines.join('\n');
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
export function formatBuild(result, jsonMode = false) {
|
|
86
|
+
if (jsonMode) {
|
|
87
|
+
return JSON.stringify(result, null, 2);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const lines = [
|
|
91
|
+
'✅ Build queued!',
|
|
92
|
+
'',
|
|
93
|
+
`Build ID: ${result.id}`,
|
|
94
|
+
`Status: ${result.status || 'queued'}`,
|
|
95
|
+
`Page URL: ${result.page_url}`,
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
return lines.join('\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
85
101
|
export function formatDelete(jsonMode = false) {
|
|
86
102
|
if (jsonMode) {
|
|
87
103
|
return JSON.stringify({ ok: true }, null, 2);
|