@tutti-os/tutti-admin-cli 0.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 +44 -0
- package/dist/appApi.d.ts +31 -0
- package/dist/appApi.js +153 -0
- package/dist/appApi.js.map +1 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +37 -0
- package/dist/config.js.map +1 -0
- package/dist/http.d.ts +17 -0
- package/dist/http.js +60 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +170 -0
- package/dist/index.js.map +1 -0
- package/dist/upload.d.ts +25 -0
- package/dist/upload.js +100 -0
- package/dist/upload.js.map +1 -0
- package/dist/zip.d.ts +17 -0
- package/dist/zip.js +66 -0
- package/dist/zip.js.map +1 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# tutti-admin-cli
|
|
2
|
+
|
|
3
|
+
Internal CLI for Tutti admin operations. The first workflow is App Center
|
|
4
|
+
release publishing, but the command surface is intentionally broader than app
|
|
5
|
+
uploads so it can grow with admin automation.
|
|
6
|
+
|
|
7
|
+
## Auth
|
|
8
|
+
|
|
9
|
+
Create a management token in TSH Admin Dashboard -> 管理密钥, then run:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
tutti-admin auth login --admin-url https://admin.nextop.sh
|
|
13
|
+
tutti-admin auth whoami
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The CLI stores the token in `~/.config/tutti-admin/config.json` with `0600`
|
|
17
|
+
permissions. CI can provide `TUTTI_ADMIN_TOKEN` instead.
|
|
18
|
+
|
|
19
|
+
## App Release
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
tutti-admin app list --brand nextop --query design
|
|
23
|
+
tutti-admin app detail --brand nextop --app-id vibe-design --include latest,versions
|
|
24
|
+
tutti-admin app validate --zip ./output/vibe-design.zip
|
|
25
|
+
tutti-admin app release --brand nextop --app-id vibe-design --zip ./output/vibe-design.zip --publish --latest
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`app release` validates the zip, creates an artifact upload session, uploads the
|
|
29
|
+
zip, creates the App Center version, and optionally publishes it and moves the
|
|
30
|
+
latest pointer.
|
|
31
|
+
|
|
32
|
+
## Maintenance
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
tutti-admin app create --brand nextop --app-id vibe-design --name "Vibe Design"
|
|
36
|
+
tutti-admin app edit --brand nextop --app-id vibe-design --name "Vibe Design" --icon ./assets/icon.png
|
|
37
|
+
tutti-admin app publish --brand nextop --app-id vibe-design --version 0.1.47 --latest
|
|
38
|
+
tutti-admin app rollback --brand nextop --app-id vibe-design --version 0.1.46
|
|
39
|
+
tutti-admin app archive --brand nextop --app-id vibe-design --version 0.1.45
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`app create` and `app edit` accept either `--icon <file>` to upload a PNG/JPEG/WebP
|
|
43
|
+
icon through App Center asset upload, or `--icon-url <url>` when the asset is
|
|
44
|
+
already hosted.
|
package/dist/appApi.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { HttpClient } from './http.js';
|
|
2
|
+
export interface AppReleaseOptions {
|
|
3
|
+
brand: string;
|
|
4
|
+
appId: string;
|
|
5
|
+
zip: string;
|
|
6
|
+
publish?: boolean;
|
|
7
|
+
latest?: boolean;
|
|
8
|
+
reason?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare class AppApi {
|
|
11
|
+
private readonly http;
|
|
12
|
+
constructor(http: HttpClient);
|
|
13
|
+
list(query?: string): Promise<unknown[]>;
|
|
14
|
+
detail(appId: string, include?: string[]): Promise<Record<string, unknown>>;
|
|
15
|
+
create(data: Record<string, unknown>): Promise<unknown>;
|
|
16
|
+
edit(appId: string, data: Record<string, unknown>): Promise<unknown>;
|
|
17
|
+
uploadAppIcon(appId: string, filePath: string): Promise<string>;
|
|
18
|
+
versions(appId: string): Promise<unknown[]>;
|
|
19
|
+
publish(appId: string, version: string, latest: boolean, reason?: string): Promise<unknown>;
|
|
20
|
+
setLatest(appId: string, version: string, reason?: string): Promise<unknown>;
|
|
21
|
+
archive(appId: string, version: string, reason?: string): Promise<unknown>;
|
|
22
|
+
release(options: AppReleaseOptions): Promise<{
|
|
23
|
+
version: unknown;
|
|
24
|
+
artifact: {
|
|
25
|
+
artifactUrl: string;
|
|
26
|
+
objectKey: string | undefined;
|
|
27
|
+
};
|
|
28
|
+
manifest: import("./zip.js").TuttiAppManifest;
|
|
29
|
+
}>;
|
|
30
|
+
private findApp;
|
|
31
|
+
}
|
package/dist/appApi.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import { lookup } from 'mime-types';
|
|
4
|
+
import { uploadAdminAsset, uploadAppZip } from './upload.js';
|
|
5
|
+
import { hashFile, inspectZip } from './zip.js';
|
|
6
|
+
const APP_CENTER_BASE = '/api/desktop/v1/admin/app-center';
|
|
7
|
+
export class AppApi {
|
|
8
|
+
http;
|
|
9
|
+
constructor(http) {
|
|
10
|
+
this.http = http;
|
|
11
|
+
}
|
|
12
|
+
list(query) {
|
|
13
|
+
return this.http.get(`${APP_CENTER_BASE}/apps`).then((reply) => {
|
|
14
|
+
const apps = reply.apps ?? [];
|
|
15
|
+
if (!query) {
|
|
16
|
+
return apps;
|
|
17
|
+
}
|
|
18
|
+
const needle = query.toLowerCase();
|
|
19
|
+
return apps.filter((item) => JSON.stringify(item).toLowerCase().includes(needle));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async detail(appId, include = []) {
|
|
23
|
+
const apps = await this.list();
|
|
24
|
+
const app = apps.find((item) => getField(item, 'appId', 'app_id') === appId);
|
|
25
|
+
const out = { app };
|
|
26
|
+
if (include.includes('versions') || include.includes('latest')) {
|
|
27
|
+
const versions = await this.versions(appId);
|
|
28
|
+
out.versions = versions;
|
|
29
|
+
if (include.includes('latest')) {
|
|
30
|
+
const latestVersionId = getField(app, 'latestVersionId', 'latest_version_id');
|
|
31
|
+
out.latest = versions.find((item) => getField(item, 'versionId', 'version_id') === latestVersionId);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
create(data) {
|
|
37
|
+
return this.http.post(`${APP_CENTER_BASE}/apps`, data);
|
|
38
|
+
}
|
|
39
|
+
edit(appId, data) {
|
|
40
|
+
return this.http.patch(`${APP_CENTER_BASE}/apps/${encodeURIComponent(appId)}`, data);
|
|
41
|
+
}
|
|
42
|
+
async uploadAppIcon(appId, filePath) {
|
|
43
|
+
const info = await stat(filePath);
|
|
44
|
+
if (!info.isFile()) {
|
|
45
|
+
throw new Error(`${filePath} is not a file`);
|
|
46
|
+
}
|
|
47
|
+
const contentType = lookup(filePath) || 'application/octet-stream';
|
|
48
|
+
const sha256 = await hashFile(filePath);
|
|
49
|
+
const asset = await uploadAdminAsset(this.http, {
|
|
50
|
+
purpose: 'app_icon',
|
|
51
|
+
appId,
|
|
52
|
+
filePath,
|
|
53
|
+
fileName: appIconUploadFileName(contentType, sha256),
|
|
54
|
+
contentType,
|
|
55
|
+
sizeBytes: info.size,
|
|
56
|
+
sha256,
|
|
57
|
+
});
|
|
58
|
+
return asset.assetUrl;
|
|
59
|
+
}
|
|
60
|
+
versions(appId) {
|
|
61
|
+
return this.http
|
|
62
|
+
.get(`${APP_CENTER_BASE}/apps/${encodeURIComponent(appId)}/versions`)
|
|
63
|
+
.then((reply) => reply.versions ?? []);
|
|
64
|
+
}
|
|
65
|
+
publish(appId, version, latest, reason) {
|
|
66
|
+
return this.http
|
|
67
|
+
.post(`${APP_CENTER_BASE}/apps/${encodeURIComponent(appId)}/versions/${encodeURIComponent(version)}/publish`, {
|
|
68
|
+
appId,
|
|
69
|
+
versionId: version,
|
|
70
|
+
reason,
|
|
71
|
+
})
|
|
72
|
+
.then(async (reply) => {
|
|
73
|
+
if (latest) {
|
|
74
|
+
await this.setLatest(appId, version, reason);
|
|
75
|
+
}
|
|
76
|
+
return reply;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
setLatest(appId, version, reason) {
|
|
80
|
+
return this.http.post(`${APP_CENTER_BASE}/apps/${encodeURIComponent(appId)}/versions/${encodeURIComponent(version)}/set-latest`, { appId, versionId: version, reason });
|
|
81
|
+
}
|
|
82
|
+
archive(appId, version, reason) {
|
|
83
|
+
return this.http.post(`${APP_CENTER_BASE}/apps/${encodeURIComponent(appId)}/versions/${encodeURIComponent(version)}/archive`, { appId, versionId: version, reason });
|
|
84
|
+
}
|
|
85
|
+
async release(options) {
|
|
86
|
+
const zip = await inspectZip(options.zip);
|
|
87
|
+
if (zip.appId !== options.appId) {
|
|
88
|
+
throw new Error(`zip appId ${zip.appId} does not match --app-id ${options.appId}`);
|
|
89
|
+
}
|
|
90
|
+
const app = await this.findApp(options.appId);
|
|
91
|
+
if (!app) {
|
|
92
|
+
throw new Error(`app ${options.appId} not found`);
|
|
93
|
+
}
|
|
94
|
+
const versionId = zip.version;
|
|
95
|
+
const versions = await this.versions(options.appId);
|
|
96
|
+
const existingVersion = versions.find((item) => getField(item, 'versionId', 'version_id') === versionId);
|
|
97
|
+
if (existingVersion) {
|
|
98
|
+
throw new Error(`app ${options.appId} version ${versionId} already exists`);
|
|
99
|
+
}
|
|
100
|
+
const contentType = lookup(options.zip) || 'application/zip';
|
|
101
|
+
const artifact = await uploadAppZip(this.http, {
|
|
102
|
+
appId: options.appId,
|
|
103
|
+
versionId,
|
|
104
|
+
filePath: options.zip,
|
|
105
|
+
fileName: basename(options.zip),
|
|
106
|
+
contentType,
|
|
107
|
+
sha256: zip.sha256,
|
|
108
|
+
sizeBytes: zip.sizeBytes,
|
|
109
|
+
});
|
|
110
|
+
const version = await this.http.post(`${APP_CENTER_BASE}/apps/${encodeURIComponent(options.appId)}/versions`, {
|
|
111
|
+
appId: options.appId,
|
|
112
|
+
versionId,
|
|
113
|
+
artifactKind: 'zip',
|
|
114
|
+
artifactUrl: artifact.artifactUrl,
|
|
115
|
+
artifactSha256: zip.sha256,
|
|
116
|
+
artifactSizeBytes: zip.sizeBytes,
|
|
117
|
+
runtimeBootstrap: zip.manifest.runtime?.bootstrap,
|
|
118
|
+
runtimeHealthcheckPath: zip.manifest.runtime?.healthcheckPath,
|
|
119
|
+
reason: options.reason,
|
|
120
|
+
});
|
|
121
|
+
if (options.publish || options.latest) {
|
|
122
|
+
await this.publish(options.appId, versionId, Boolean(options.latest), options.reason);
|
|
123
|
+
}
|
|
124
|
+
return { version, artifact, manifest: zip.manifest };
|
|
125
|
+
}
|
|
126
|
+
async findApp(appId) {
|
|
127
|
+
const apps = await this.list();
|
|
128
|
+
return apps.find((item) => getField(item, 'appId', 'app_id') === appId);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function appIconUploadFileName(contentType, sha256) {
|
|
132
|
+
const ext = contentType === 'image/png'
|
|
133
|
+
? 'png'
|
|
134
|
+
: contentType === 'image/jpeg'
|
|
135
|
+
? 'jpg'
|
|
136
|
+
: contentType === 'image/webp'
|
|
137
|
+
? 'webp'
|
|
138
|
+
: 'bin';
|
|
139
|
+
return `icon-${sha256.slice(0, 12)}.${ext}`;
|
|
140
|
+
}
|
|
141
|
+
function getField(item, ...fields) {
|
|
142
|
+
if (!item || typeof item !== 'object') {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
const record = item;
|
|
146
|
+
for (const field of fields) {
|
|
147
|
+
if (record[field] !== undefined) {
|
|
148
|
+
return record[field];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=appApi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appApi.js","sourceRoot":"","sources":["../src/appApi.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface StoredConfig {
|
|
2
|
+
adminUrl?: string;
|
|
3
|
+
token?: string;
|
|
4
|
+
}
|
|
5
|
+
declare const CONFIG_PATH: string;
|
|
6
|
+
export declare function normalizeApiOrigin(input?: string): string;
|
|
7
|
+
export declare function readConfig(): Promise<StoredConfig>;
|
|
8
|
+
export declare function writeConfig(config: StoredConfig): Promise<void>;
|
|
9
|
+
export declare function resolveRuntimeConfig(options?: {
|
|
10
|
+
adminUrl?: string;
|
|
11
|
+
token?: string;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
adminUrl: string;
|
|
14
|
+
token: string;
|
|
15
|
+
}>;
|
|
16
|
+
export { CONFIG_PATH };
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
const CONFIG_PATH = join(homedir(), '.config', 'tutti-admin', 'config.json');
|
|
5
|
+
export function normalizeApiOrigin(input) {
|
|
6
|
+
const raw = (input || process.env.TUTTI_ADMIN_URL || 'https://nextop.sh').trim();
|
|
7
|
+
const url = new URL(raw);
|
|
8
|
+
if (url.hostname === 'admin.nextop.sh') {
|
|
9
|
+
return 'https://nextop.sh';
|
|
10
|
+
}
|
|
11
|
+
if (url.hostname === 'admin.svenzeng.com') {
|
|
12
|
+
return 'https://svenzeng.com';
|
|
13
|
+
}
|
|
14
|
+
return url.origin;
|
|
15
|
+
}
|
|
16
|
+
export async function readConfig() {
|
|
17
|
+
try {
|
|
18
|
+
const raw = await readFile(CONFIG_PATH, 'utf8');
|
|
19
|
+
return JSON.parse(raw);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function writeConfig(config) {
|
|
26
|
+
await mkdir(dirname(CONFIG_PATH), { recursive: true });
|
|
27
|
+
await writeFile(CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
28
|
+
await chmod(CONFIG_PATH, 0o600);
|
|
29
|
+
}
|
|
30
|
+
export async function resolveRuntimeConfig(options = {}) {
|
|
31
|
+
const stored = await readConfig();
|
|
32
|
+
const adminUrl = normalizeApiOrigin(options.adminUrl || stored.adminUrl);
|
|
33
|
+
const token = (options.token || process.env.TUTTI_ADMIN_TOKEN || stored.token || '').trim();
|
|
34
|
+
return { adminUrl, token };
|
|
35
|
+
}
|
|
36
|
+
export { CONFIG_PATH };
|
|
37
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAO1C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;AAE7E,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,mBAAmB,CAAC,CAAC,IAAI,EAAE,CAAC;IACjF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAEzB,IAAI,GAAG,CAAC,QAAQ,KAAK,iBAAiB,EAAE,CAAC;QACvC,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,oBAAoB,EAAE,CAAC;QAC1C,OAAO,sBAAsB,CAAC;IAChC,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAoB;IACpD,MAAM,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,MAAM,SAAS,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7E,MAAM,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,UAAiD,EAAE;IAC5F,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,OAAO,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC;IACzE,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5F,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare class HttpError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
readonly body: unknown;
|
|
4
|
+
constructor(message: string, status: number, body: unknown);
|
|
5
|
+
}
|
|
6
|
+
export interface HttpClientOptions {
|
|
7
|
+
adminUrl: string;
|
|
8
|
+
token?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare class HttpClient {
|
|
11
|
+
private readonly options;
|
|
12
|
+
constructor(options: HttpClientOptions);
|
|
13
|
+
request<T>(method: string, path: string, body?: unknown): Promise<T>;
|
|
14
|
+
get<T>(path: string): Promise<T>;
|
|
15
|
+
post<T>(path: string, body?: unknown): Promise<T>;
|
|
16
|
+
patch<T>(path: string, body?: unknown): Promise<T>;
|
|
17
|
+
}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export class HttpError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
body;
|
|
4
|
+
constructor(message, status, body) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.body = body;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class HttpClient {
|
|
11
|
+
options;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
}
|
|
15
|
+
async request(method, path, body) {
|
|
16
|
+
const headers = {
|
|
17
|
+
accept: 'application/json',
|
|
18
|
+
};
|
|
19
|
+
if (this.options.token) {
|
|
20
|
+
headers.authorization = `Bearer ${this.options.token}`;
|
|
21
|
+
}
|
|
22
|
+
let payload;
|
|
23
|
+
if (body !== undefined) {
|
|
24
|
+
headers['content-type'] = 'application/json';
|
|
25
|
+
payload = JSON.stringify(body);
|
|
26
|
+
}
|
|
27
|
+
const response = await fetch(`${this.options.adminUrl}${path}`, {
|
|
28
|
+
method,
|
|
29
|
+
headers,
|
|
30
|
+
body: payload,
|
|
31
|
+
});
|
|
32
|
+
const text = await response.text();
|
|
33
|
+
const parsed = parseResponseBody(text);
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new HttpError(`HTTP ${response.status} ${response.statusText}`, response.status, parsed);
|
|
36
|
+
}
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
get(path) {
|
|
40
|
+
return this.request('GET', path);
|
|
41
|
+
}
|
|
42
|
+
post(path, body) {
|
|
43
|
+
return this.request('POST', path, body);
|
|
44
|
+
}
|
|
45
|
+
patch(path, body) {
|
|
46
|
+
return this.request('PATCH', path, body);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function parseResponseBody(text) {
|
|
50
|
+
if (!text.trim()) {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(text);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return text;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,SAAU,SAAQ,KAAK;IAGvB;IACA;IAHX,YACE,OAAe,EACN,MAAc,EACd,IAAa;QAEtB,KAAK,CAAC,OAAO,CAAC,CAAC;QAHN,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAS;IAGxB,CAAC;CACF;AAOD,MAAM,OAAO,UAAU;IACQ;IAA7B,YAA6B,OAA0B;QAA1B,YAAO,GAAP,OAAO,CAAmB;IAAG,CAAC;IAE3D,KAAK,CAAC,OAAO,CAAI,MAAc,EAAE,IAAY,EAAE,IAAc;QAC3D,MAAM,OAAO,GAA2B;YACtC,MAAM,EAAE,kBAAkB;SAC3B,CAAC;QACF,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACvB,OAAO,CAAC,aAAa,GAAG,UAAU,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACzD,CAAC;QACD,IAAI,OAA6B,CAAC;QAClC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;YAC7C,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,GAAG,IAAI,EAAE,EAAE;YAC9D,MAAM;YACN,OAAO;YACP,IAAI,EAAE,OAAO;SACd,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,SAAS,CAAC,QAAQ,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjG,CAAC;QACD,OAAO,MAAW,CAAC;IACrB,CAAC;IAED,GAAG,CAAI,IAAY;QACjB,OAAO,IAAI,CAAC,OAAO,CAAI,KAAK,EAAE,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,CAAI,IAAY,EAAE,IAAc;QAClC,OAAO,IAAI,CAAC,OAAO,CAAI,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAI,IAAY,EAAE,IAAc;QACnC,OAAO,IAAI,CAAC,OAAO,CAAI,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9C,CAAC;CACF;AAED,SAAS,iBAAiB,CAAC,IAAY;IACrC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACjB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from 'node:readline/promises';
|
|
3
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { AppApi } from './appApi.js';
|
|
6
|
+
import { CONFIG_PATH, readConfig, resolveRuntimeConfig, writeConfig } from './config.js';
|
|
7
|
+
import { HttpClient } from './http.js';
|
|
8
|
+
import { inspectZip } from './zip.js';
|
|
9
|
+
const program = new Command();
|
|
10
|
+
program
|
|
11
|
+
.name('tutti-admin')
|
|
12
|
+
.description('Internal Tutti admin CLI')
|
|
13
|
+
.option('--admin-url <url>', 'admin API URL, or admin.nextop.sh page URL')
|
|
14
|
+
.option('--token <token>', 'admin API token')
|
|
15
|
+
.option('--json', 'print JSON output');
|
|
16
|
+
const auth = program.command('auth');
|
|
17
|
+
auth
|
|
18
|
+
.command('login')
|
|
19
|
+
.option('--admin-url <url>', 'admin API URL, or admin.nextop.sh page URL')
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
const rl = createInterface({ input, output });
|
|
22
|
+
try {
|
|
23
|
+
const root = program.opts();
|
|
24
|
+
const adminUrl = (options.adminUrl || root.adminUrl || 'https://admin.nextop.sh').trim();
|
|
25
|
+
const token = (await rl.question('Paste Tutti admin token: ')).trim();
|
|
26
|
+
const runtime = await resolveRuntimeConfig({ adminUrl, token });
|
|
27
|
+
await inspectAdminToken(new HttpClient(runtime), token);
|
|
28
|
+
await writeConfig({ ...(await readConfig()), adminUrl, token });
|
|
29
|
+
console.log(`Logged in. Config saved to ${CONFIG_PATH}`);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
rl.close();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
auth.command('logout').action(async () => {
|
|
36
|
+
const current = await readConfig();
|
|
37
|
+
await writeConfig({ ...current, token: undefined });
|
|
38
|
+
console.log('Logged out.');
|
|
39
|
+
});
|
|
40
|
+
auth.command('whoami').action(run(async (client) => inspectAdminToken(client)));
|
|
41
|
+
auth.command('token').command('inspect').action(run(async (client) => inspectAdminToken(client)));
|
|
42
|
+
program.command('system').command('health').action(run(async (client) => client.get('/health')));
|
|
43
|
+
const app = program.command('app');
|
|
44
|
+
app
|
|
45
|
+
.command('list')
|
|
46
|
+
.requiredOption('--brand <brand>')
|
|
47
|
+
.option('--query <query>')
|
|
48
|
+
.action(runApp(async (api, options) => api.list(options.query)));
|
|
49
|
+
app
|
|
50
|
+
.command('detail')
|
|
51
|
+
.requiredOption('--brand <brand>')
|
|
52
|
+
.requiredOption('--app-id <appId>')
|
|
53
|
+
.option('--include <include>', 'comma separated: latest,versions')
|
|
54
|
+
.action(runApp(async (api, options) => api.detail(options.appId, splitCSV(options.include))));
|
|
55
|
+
app
|
|
56
|
+
.command('create')
|
|
57
|
+
.requiredOption('--brand <brand>')
|
|
58
|
+
.requiredOption('--app-id <appId>')
|
|
59
|
+
.requiredOption('--name <name>')
|
|
60
|
+
.option('--description <description>')
|
|
61
|
+
.option('--icon <path>', 'upload an app icon and use its asset URL')
|
|
62
|
+
.option('--icon-url <iconUrl>')
|
|
63
|
+
.option('--status <status>', 'draft|published|hidden|disabled|archived', 'draft')
|
|
64
|
+
.action(runApp(async (api, options) => api.create({
|
|
65
|
+
appId: options.appId,
|
|
66
|
+
displayNameKey: options.appId,
|
|
67
|
+
descriptionKey: `${options.appId}.description`,
|
|
68
|
+
fallbackDisplayName: options.name,
|
|
69
|
+
fallbackDescription: options.description,
|
|
70
|
+
iconUrl: await resolveIconUrl(api, options),
|
|
71
|
+
status: options.status,
|
|
72
|
+
})));
|
|
73
|
+
app
|
|
74
|
+
.command('edit')
|
|
75
|
+
.requiredOption('--brand <brand>')
|
|
76
|
+
.requiredOption('--app-id <appId>')
|
|
77
|
+
.option('--name <name>')
|
|
78
|
+
.option('--description <description>')
|
|
79
|
+
.option('--icon <path>', 'upload an app icon and use its asset URL')
|
|
80
|
+
.option('--icon-url <iconUrl>')
|
|
81
|
+
.option('--status <status>')
|
|
82
|
+
.action(runApp(async (api, options) => api.edit(options.appId, {
|
|
83
|
+
fallbackDisplayName: options.name,
|
|
84
|
+
fallbackDescription: options.description,
|
|
85
|
+
iconUrl: await resolveIconUrl(api, options),
|
|
86
|
+
status: options.status,
|
|
87
|
+
})));
|
|
88
|
+
app
|
|
89
|
+
.command('validate')
|
|
90
|
+
.requiredOption('--zip <zip>')
|
|
91
|
+
.action(async (options) => print(await inspectZip(options.zip)));
|
|
92
|
+
app
|
|
93
|
+
.command('release')
|
|
94
|
+
.requiredOption('--brand <brand>')
|
|
95
|
+
.requiredOption('--app-id <appId>')
|
|
96
|
+
.requiredOption('--zip <zip>')
|
|
97
|
+
.option('--publish')
|
|
98
|
+
.option('--latest')
|
|
99
|
+
.option('--reason <reason>')
|
|
100
|
+
.action(runApp((api, options) => api.release(options)));
|
|
101
|
+
app
|
|
102
|
+
.command('publish')
|
|
103
|
+
.requiredOption('--brand <brand>')
|
|
104
|
+
.requiredOption('--app-id <appId>')
|
|
105
|
+
.requiredOption('--version <version>')
|
|
106
|
+
.option('--latest')
|
|
107
|
+
.option('--reason <reason>')
|
|
108
|
+
.action(runApp((api, options) => api.publish(options.appId, options.version, Boolean(options.latest), options.reason)));
|
|
109
|
+
app
|
|
110
|
+
.command('rollback')
|
|
111
|
+
.requiredOption('--brand <brand>')
|
|
112
|
+
.requiredOption('--app-id <appId>')
|
|
113
|
+
.requiredOption('--version <version>')
|
|
114
|
+
.option('--reason <reason>')
|
|
115
|
+
.action(runApp((api, options) => api.setLatest(options.appId, options.version, options.reason)));
|
|
116
|
+
app
|
|
117
|
+
.command('archive')
|
|
118
|
+
.requiredOption('--brand <brand>')
|
|
119
|
+
.requiredOption('--app-id <appId>')
|
|
120
|
+
.requiredOption('--version <version>')
|
|
121
|
+
.option('--reason <reason>')
|
|
122
|
+
.action(runApp((api, options) => api.archive(options.appId, options.version, options.reason)));
|
|
123
|
+
program.parseAsync().catch((error) => {
|
|
124
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
125
|
+
process.exitCode = 1;
|
|
126
|
+
});
|
|
127
|
+
function run(handler) {
|
|
128
|
+
return async () => {
|
|
129
|
+
const options = program.opts();
|
|
130
|
+
const runtime = await resolveRuntimeConfig(options);
|
|
131
|
+
if (!runtime.token) {
|
|
132
|
+
throw new Error('TUTTI_ADMIN_TOKEN is required. Run `tutti-admin auth login` first.');
|
|
133
|
+
}
|
|
134
|
+
print(await handler(new HttpClient(runtime)));
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function runApp(handler) {
|
|
138
|
+
return async (options) => {
|
|
139
|
+
const rootOptions = program.opts();
|
|
140
|
+
const runtime = await resolveRuntimeConfig(rootOptions);
|
|
141
|
+
if (!runtime.token) {
|
|
142
|
+
throw new Error('TUTTI_ADMIN_TOKEN is required. Run `tutti-admin auth login` first.');
|
|
143
|
+
}
|
|
144
|
+
print(await handler(new AppApi(new HttpClient(runtime)), options));
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function print(value) {
|
|
148
|
+
console.log(JSON.stringify(value, null, 2));
|
|
149
|
+
}
|
|
150
|
+
function splitCSV(value) {
|
|
151
|
+
return (value ?? '')
|
|
152
|
+
.split(',')
|
|
153
|
+
.map((item) => item.trim())
|
|
154
|
+
.filter(Boolean);
|
|
155
|
+
}
|
|
156
|
+
async function resolveIconUrl(api, options) {
|
|
157
|
+
const icon = String(options.icon ?? '').trim();
|
|
158
|
+
const iconUrl = String(options.iconUrl ?? '').trim();
|
|
159
|
+
if (icon && iconUrl) {
|
|
160
|
+
throw new Error('Use either --icon or --icon-url, not both.');
|
|
161
|
+
}
|
|
162
|
+
if (!icon) {
|
|
163
|
+
return iconUrl || undefined;
|
|
164
|
+
}
|
|
165
|
+
return api.uploadAppIcon(options.appId, icon);
|
|
166
|
+
}
|
|
167
|
+
function inspectAdminToken(client, token) {
|
|
168
|
+
return client.post('/api/v1/admin/auth/token/inspect', token ? { token_value: token } : {});
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/upload.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HttpClient } from './http.js';
|
|
2
|
+
export declare function uploadAdminAsset(http: HttpClient, input: {
|
|
3
|
+
purpose: string;
|
|
4
|
+
appId: string;
|
|
5
|
+
filePath: string;
|
|
6
|
+
fileName: string;
|
|
7
|
+
contentType: string;
|
|
8
|
+
sizeBytes: number;
|
|
9
|
+
sha256: string;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
assetUrl: string;
|
|
12
|
+
objectKey: string | undefined;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function uploadAppZip(http: HttpClient, input: {
|
|
15
|
+
appId: string;
|
|
16
|
+
versionId: string;
|
|
17
|
+
filePath: string;
|
|
18
|
+
fileName: string;
|
|
19
|
+
contentType: string;
|
|
20
|
+
sizeBytes: number;
|
|
21
|
+
sha256: string;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
artifactUrl: string;
|
|
24
|
+
objectKey: string | undefined;
|
|
25
|
+
}>;
|
package/dist/upload.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
3
|
+
import { Upload } from '@aws-sdk/lib-storage';
|
|
4
|
+
const APP_CENTER_BASE = '/api/desktop/v1/admin/app-center';
|
|
5
|
+
export async function uploadAdminAsset(http, input) {
|
|
6
|
+
const session = await http.post(`${APP_CENTER_BASE}/asset-upload-sessions`, {
|
|
7
|
+
purpose: input.purpose,
|
|
8
|
+
appId: input.appId,
|
|
9
|
+
fileName: input.fileName,
|
|
10
|
+
contentType: input.contentType,
|
|
11
|
+
sizeBytes: input.sizeBytes,
|
|
12
|
+
sha256: input.sha256,
|
|
13
|
+
});
|
|
14
|
+
await signedPutUpload(input.filePath, input.contentType, session);
|
|
15
|
+
const assetUrl = (session.assetUrl ?? session.asset_url ?? '').trim();
|
|
16
|
+
if (!assetUrl) {
|
|
17
|
+
throw new Error('asset upload succeeded but assetUrl is empty');
|
|
18
|
+
}
|
|
19
|
+
return { assetUrl, objectKey: session.objectKey ?? session.object_key };
|
|
20
|
+
}
|
|
21
|
+
export async function uploadAppZip(http, input) {
|
|
22
|
+
const session = await http.post(`${APP_CENTER_BASE}/artifact-upload-sessions`, {
|
|
23
|
+
appId: input.appId,
|
|
24
|
+
versionId: input.versionId,
|
|
25
|
+
fileName: input.fileName,
|
|
26
|
+
contentType: input.contentType,
|
|
27
|
+
sizeBytes: input.sizeBytes,
|
|
28
|
+
sha256: input.sha256,
|
|
29
|
+
});
|
|
30
|
+
if (hasMultipartSession(session)) {
|
|
31
|
+
await multipartUpload(input.filePath, input.contentType, session);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
await signedPutUpload(input.filePath, input.contentType, session);
|
|
35
|
+
}
|
|
36
|
+
const artifactUrl = (session.artifactUrl ?? session.artifact_url ?? '').trim();
|
|
37
|
+
if (!artifactUrl) {
|
|
38
|
+
throw new Error('upload succeeded but artifactUrl is empty');
|
|
39
|
+
}
|
|
40
|
+
return { artifactUrl, objectKey: session.objectKey ?? session.object_key };
|
|
41
|
+
}
|
|
42
|
+
function hasMultipartSession(session) {
|
|
43
|
+
const credentials = session.credentials ?? {};
|
|
44
|
+
return Boolean(session.bucket &&
|
|
45
|
+
session.region &&
|
|
46
|
+
(session.objectKey ?? session.object_key) &&
|
|
47
|
+
(credentials.accessKeyId ?? credentials.access_key_id) &&
|
|
48
|
+
(credentials.secretAccessKey ?? credentials.secret_access_key) &&
|
|
49
|
+
(credentials.sessionToken ?? credentials.session_token));
|
|
50
|
+
}
|
|
51
|
+
async function signedPutUpload(filePath, contentType, session) {
|
|
52
|
+
const signature = session.uploadSignature ?? session.upload_signature ?? {};
|
|
53
|
+
const uploadUrl = (signature.uploadUrl ?? signature.upload_url ?? '').trim();
|
|
54
|
+
if (!uploadUrl) {
|
|
55
|
+
throw new Error('upload session missing uploadUrl');
|
|
56
|
+
}
|
|
57
|
+
const headers = new Headers(signature.headers ?? {});
|
|
58
|
+
if (!headers.has('content-type')) {
|
|
59
|
+
headers.set('content-type', contentType);
|
|
60
|
+
}
|
|
61
|
+
const response = await fetch(uploadUrl, {
|
|
62
|
+
method: 'PUT',
|
|
63
|
+
headers,
|
|
64
|
+
body: createReadStream(filePath),
|
|
65
|
+
duplex: 'half',
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
throw new Error(`artifact upload failed: HTTP ${response.status}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function multipartUpload(filePath, contentType, session) {
|
|
72
|
+
const credentials = session.credentials ?? {};
|
|
73
|
+
const multipart = session.multipart ?? {};
|
|
74
|
+
const client = new S3Client({
|
|
75
|
+
region: session.region,
|
|
76
|
+
endpoint: session.endpoint || undefined,
|
|
77
|
+
forcePathStyle: session.forcePathStyle ?? session.force_path_style ?? false,
|
|
78
|
+
requestChecksumCalculation: 'WHEN_REQUIRED',
|
|
79
|
+
credentials: {
|
|
80
|
+
accessKeyId: credentials.accessKeyId ?? credentials.access_key_id ?? '',
|
|
81
|
+
secretAccessKey: credentials.secretAccessKey ?? credentials.secret_access_key ?? '',
|
|
82
|
+
sessionToken: credentials.sessionToken ?? credentials.session_token ?? '',
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
const upload = new Upload({
|
|
86
|
+
client,
|
|
87
|
+
params: {
|
|
88
|
+
Bucket: session.bucket,
|
|
89
|
+
Key: session.objectKey ?? session.object_key,
|
|
90
|
+
Body: createReadStream(filePath),
|
|
91
|
+
ContentType: contentType,
|
|
92
|
+
Metadata: session.uploadConstraints?.metadata ?? session.upload_constraints?.metadata,
|
|
93
|
+
},
|
|
94
|
+
queueSize: Number(multipart.maxConcurrency ?? multipart.max_concurrency ?? 4),
|
|
95
|
+
partSize: Number(multipart.partSizeBytes ?? multipart.part_size_bytes ?? 8 * 1024 * 1024),
|
|
96
|
+
leavePartsOnError: false,
|
|
97
|
+
});
|
|
98
|
+
await upload.done();
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"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"}
|
package/dist/zip.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface TuttiAppManifest {
|
|
2
|
+
appId?: string;
|
|
3
|
+
id?: string;
|
|
4
|
+
version?: string;
|
|
5
|
+
runtime?: {
|
|
6
|
+
bootstrap?: string;
|
|
7
|
+
healthcheckPath?: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export declare function inspectZip(filePath: string): Promise<{
|
|
11
|
+
appId: string;
|
|
12
|
+
sha256: string;
|
|
13
|
+
sizeBytes: number;
|
|
14
|
+
version: string;
|
|
15
|
+
manifest: TuttiAppManifest;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function hashFile(filePath: string): Promise<string>;
|
package/dist/zip.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import extract from 'extract-zip';
|
|
7
|
+
export async function inspectZip(filePath) {
|
|
8
|
+
const info = await stat(filePath);
|
|
9
|
+
if (!info.isFile()) {
|
|
10
|
+
throw new Error(`${filePath} is not a file`);
|
|
11
|
+
}
|
|
12
|
+
if (!filePath.toLowerCase().endsWith('.zip')) {
|
|
13
|
+
throw new Error(`${filePath} must be a .zip file`);
|
|
14
|
+
}
|
|
15
|
+
const [sha256, manifest] = await Promise.all([hashFile(filePath), readManifestFromZip(filePath)]);
|
|
16
|
+
const appId = manifest.appId ?? manifest.id;
|
|
17
|
+
if (!appId) {
|
|
18
|
+
throw new Error('tutti.app.json missing appId');
|
|
19
|
+
}
|
|
20
|
+
if (!manifest.version) {
|
|
21
|
+
throw new Error('tutti.app.json missing version');
|
|
22
|
+
}
|
|
23
|
+
if (!manifest.runtime?.bootstrap) {
|
|
24
|
+
throw new Error('tutti.app.json missing runtime.bootstrap');
|
|
25
|
+
}
|
|
26
|
+
if (!manifest.runtime?.healthcheckPath) {
|
|
27
|
+
throw new Error('tutti.app.json missing runtime.healthcheckPath');
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
appId,
|
|
31
|
+
sha256,
|
|
32
|
+
sizeBytes: info.size,
|
|
33
|
+
version: manifest.version,
|
|
34
|
+
manifest,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export async function hashFile(filePath) {
|
|
38
|
+
const hash = createHash('sha256');
|
|
39
|
+
await new Promise((resolve, reject) => {
|
|
40
|
+
createReadStream(filePath)
|
|
41
|
+
.on('data', (chunk) => hash.update(chunk))
|
|
42
|
+
.on('error', reject)
|
|
43
|
+
.on('end', resolve);
|
|
44
|
+
});
|
|
45
|
+
return hash.digest('hex');
|
|
46
|
+
}
|
|
47
|
+
async function readManifestFromZip(filePath) {
|
|
48
|
+
const dir = await mkdtemp(join(tmpdir(), 'tutti-admin-zip-'));
|
|
49
|
+
try {
|
|
50
|
+
await extract(filePath, { dir });
|
|
51
|
+
const candidates = ['tutti.app.json', 'app/tutti.app.json'];
|
|
52
|
+
for (const candidate of candidates) {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(await readFile(join(dir, candidate), 'utf8'));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Try the next common package layout.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
throw new Error('tutti.app.json not found in zip root');
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await rm(dir, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=zip.js.map
|
package/dist/zip.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zip.js","sourceRoot":"","sources":["../src/zip.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tutti-os/tutti-admin-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Internal CLI for Tutti admin operations.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"tutti-admin": "dist/index.js",
|
|
8
|
+
"tutti-admin-cli": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc -p tsconfig.json",
|
|
15
|
+
"dev": "tsx src/index.ts",
|
|
16
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@aws-sdk/client-s3": "^3.848.0",
|
|
20
|
+
"@aws-sdk/lib-storage": "^3.848.0",
|
|
21
|
+
"commander": "^12.1.0",
|
|
22
|
+
"extract-zip": "^2.0.1",
|
|
23
|
+
"formdata-node": "^6.0.3",
|
|
24
|
+
"mime-types": "^2.1.35"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/mime-types": "^2.1.4",
|
|
28
|
+
"@types/node": "^22.10.2",
|
|
29
|
+
"tsx": "^4.19.2",
|
|
30
|
+
"typescript": "^5.7.2"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20"
|
|
34
|
+
},
|
|
35
|
+
"license": "UNLICENSED",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
}
|
|
39
|
+
}
|