@agent-foundry/studio 1.0.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 +96 -0
- package/dist/db/client.d.ts +59 -0
- package/dist/db/client.d.ts.map +1 -0
- package/dist/db/client.js +51 -0
- package/dist/db/client.js.map +1 -0
- package/dist/db/deployments.d.ts +65 -0
- package/dist/db/deployments.d.ts.map +1 -0
- package/dist/db/deployments.js +249 -0
- package/dist/db/deployments.js.map +1 -0
- package/dist/db/index.d.ts +7 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +7 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/projects.d.ts +48 -0
- package/dist/db/projects.d.ts.map +1 -0
- package/dist/db/projects.js +192 -0
- package/dist/db/projects.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/oss/client.d.ts +65 -0
- package/dist/oss/client.d.ts.map +1 -0
- package/dist/oss/client.js +146 -0
- package/dist/oss/client.js.map +1 -0
- package/dist/oss/index.d.ts +7 -0
- package/dist/oss/index.d.ts.map +1 -0
- package/dist/oss/index.js +7 -0
- package/dist/oss/index.js.map +1 -0
- package/dist/oss/types.d.ts +96 -0
- package/dist/oss/types.d.ts.map +1 -0
- package/dist/oss/types.js +5 -0
- package/dist/oss/types.js.map +1 -0
- package/dist/oss/uploader.d.ts +72 -0
- package/dist/oss/uploader.d.ts.map +1 -0
- package/dist/oss/uploader.js +185 -0
- package/dist/oss/uploader.js.map +1 -0
- package/dist/types/deployment.d.ts +112 -0
- package/dist/types/deployment.d.ts.map +1 -0
- package/dist/types/deployment.js +7 -0
- package/dist/types/deployment.js.map +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/project.d.ts +90 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/project.js +8 -0
- package/dist/types/project.js.map +1 -0
- package/dist/types/user.d.ts +71 -0
- package/dist/types/user.d.ts.map +1 -0
- package/dist/types/user.js +8 -0
- package/dist/types/user.js.map +1 -0
- package/dist/types/workspace.d.ts +88 -0
- package/dist/types/workspace.d.ts.map +1 -0
- package/dist/types/workspace.js +27 -0
- package/dist/types/workspace.js.map +1 -0
- package/dist/utils/build.d.ts +78 -0
- package/dist/utils/build.d.ts.map +1 -0
- package/dist/utils/build.js +148 -0
- package/dist/utils/build.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/manifest.d.ts +106 -0
- package/dist/utils/manifest.d.ts.map +1 -0
- package/dist/utils/manifest.js +109 -0
- package/dist/utils/manifest.js.map +1 -0
- package/package.json +62 -0
- package/src/db/client.ts +92 -0
- package/src/db/deployments.ts +316 -0
- package/src/db/index.ts +7 -0
- package/src/db/projects.ts +246 -0
- package/src/db/schema.sql +156 -0
- package/src/index.ts +18 -0
- package/src/oss/client.ts +183 -0
- package/src/oss/index.ts +7 -0
- package/src/oss/types.ts +126 -0
- package/src/oss/uploader.ts +254 -0
- package/src/types/deployment.ts +147 -0
- package/src/types/index.ts +8 -0
- package/src/types/project.ts +114 -0
- package/src/types/user.ts +91 -0
- package/src/types/workspace.ts +124 -0
- package/src/utils/build.ts +199 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/manifest.ts +224 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace types
|
|
3
|
+
*
|
|
4
|
+
* Based on AF-studio-SPEC.md - Workspace is the runtime representation
|
|
5
|
+
* of a project being actively worked on in the Studio.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Dev server status
|
|
10
|
+
*/
|
|
11
|
+
export type DevServerStatus = 'stopped' | 'starting' | 'running' | 'error';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Dev server runtime state
|
|
15
|
+
*/
|
|
16
|
+
export interface DevServerState {
|
|
17
|
+
/** Current server status */
|
|
18
|
+
status: DevServerStatus;
|
|
19
|
+
|
|
20
|
+
/** Port number if running */
|
|
21
|
+
port?: number;
|
|
22
|
+
|
|
23
|
+
/** Full URL to access dev server */
|
|
24
|
+
url?: string;
|
|
25
|
+
|
|
26
|
+
/** Process ID of dev server */
|
|
27
|
+
pid?: number;
|
|
28
|
+
|
|
29
|
+
/** Last error message if status is 'error' */
|
|
30
|
+
lastError?: string;
|
|
31
|
+
|
|
32
|
+
/** Timestamp when server started */
|
|
33
|
+
startedAt?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Workspace - runtime state of an active project in Studio
|
|
38
|
+
*
|
|
39
|
+
* This extends StudioProject with runtime-specific fields that are
|
|
40
|
+
* not persisted to the database.
|
|
41
|
+
*/
|
|
42
|
+
export interface Workspace {
|
|
43
|
+
/** Unique identifier (ULID recommended) */
|
|
44
|
+
id: string;
|
|
45
|
+
|
|
46
|
+
/** Human-readable workspace name */
|
|
47
|
+
name: string;
|
|
48
|
+
|
|
49
|
+
/** Local filesystem root path */
|
|
50
|
+
rootPath: string;
|
|
51
|
+
|
|
52
|
+
/** ISO timestamp of creation */
|
|
53
|
+
createdAt: string;
|
|
54
|
+
|
|
55
|
+
/** ISO timestamp of last update */
|
|
56
|
+
updatedAt: string;
|
|
57
|
+
|
|
58
|
+
/** Parent workspace ID if this is a fork */
|
|
59
|
+
parentWorkspaceId?: string;
|
|
60
|
+
|
|
61
|
+
/** Associated project ID in database (optional - may be local-only) */
|
|
62
|
+
projectId?: string;
|
|
63
|
+
|
|
64
|
+
/** Dev server runtime state */
|
|
65
|
+
devServer?: DevServerState;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Session - an agent interaction session within a workspace
|
|
70
|
+
* Based on OpenCode session model
|
|
71
|
+
*/
|
|
72
|
+
export interface Session {
|
|
73
|
+
/** Session ID */
|
|
74
|
+
id: string;
|
|
75
|
+
|
|
76
|
+
/** Associated workspace ID */
|
|
77
|
+
workspaceId: string;
|
|
78
|
+
|
|
79
|
+
/** Active agent (plan or build) */
|
|
80
|
+
agentId: 'plan' | 'build' | string;
|
|
81
|
+
|
|
82
|
+
/** Parent session ID if forked */
|
|
83
|
+
parentId?: string;
|
|
84
|
+
|
|
85
|
+
/** ISO timestamp of creation */
|
|
86
|
+
createdAt: string;
|
|
87
|
+
|
|
88
|
+
/** ISO timestamp of last activity */
|
|
89
|
+
lastActivityAt: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Viewport preset for responsive preview
|
|
94
|
+
*/
|
|
95
|
+
export interface ViewportPreset {
|
|
96
|
+
name: string;
|
|
97
|
+
width: number;
|
|
98
|
+
height: number;
|
|
99
|
+
deviceScaleFactor?: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Built-in viewport presets from SPEC
|
|
104
|
+
*/
|
|
105
|
+
export const VIEWPORT_PRESETS: ViewportPreset[] = [
|
|
106
|
+
{ name: 'Desktop', width: 1280, height: 800 },
|
|
107
|
+
{ name: 'iPhone SE', width: 375, height: 667, deviceScaleFactor: 2 },
|
|
108
|
+
{ name: 'iPhone 14 Pro', width: 390, height: 844, deviceScaleFactor: 3 },
|
|
109
|
+
{ name: 'iPad', width: 768, height: 1024, deviceScaleFactor: 2 },
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Workspace runner command whitelist (for security)
|
|
114
|
+
*/
|
|
115
|
+
export const ALLOWED_SCRIPTS = [
|
|
116
|
+
'install',
|
|
117
|
+
'dev',
|
|
118
|
+
'build',
|
|
119
|
+
'test',
|
|
120
|
+
'lint',
|
|
121
|
+
'preview',
|
|
122
|
+
] as const;
|
|
123
|
+
|
|
124
|
+
export type AllowedScript = typeof ALLOWED_SCRIPTS[number];
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build utilities for Vite projects
|
|
3
|
+
*
|
|
4
|
+
* Provides helpers for running builds and collecting output files.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { UploadFile } from '../oss/types';
|
|
8
|
+
import type { ProjectConfig } from '../types/project';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* MIME type mapping for common file extensions
|
|
12
|
+
*/
|
|
13
|
+
const MIME_TYPES: Record<string, string> = {
|
|
14
|
+
'.html': 'text/html',
|
|
15
|
+
'.css': 'text/css',
|
|
16
|
+
'.js': 'application/javascript',
|
|
17
|
+
'.mjs': 'application/javascript',
|
|
18
|
+
'.json': 'application/json',
|
|
19
|
+
'.svg': 'image/svg+xml',
|
|
20
|
+
'.png': 'image/png',
|
|
21
|
+
'.jpg': 'image/jpeg',
|
|
22
|
+
'.jpeg': 'image/jpeg',
|
|
23
|
+
'.gif': 'image/gif',
|
|
24
|
+
'.webp': 'image/webp',
|
|
25
|
+
'.ico': 'image/x-icon',
|
|
26
|
+
'.woff': 'font/woff',
|
|
27
|
+
'.woff2': 'font/woff2',
|
|
28
|
+
'.ttf': 'font/ttf',
|
|
29
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
30
|
+
'.otf': 'font/otf',
|
|
31
|
+
'.map': 'application/json',
|
|
32
|
+
'.txt': 'text/plain',
|
|
33
|
+
'.xml': 'application/xml',
|
|
34
|
+
'.webmanifest': 'application/manifest+json',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get MIME type for a file based on extension
|
|
39
|
+
*/
|
|
40
|
+
export function getMimeType(filename: string): string {
|
|
41
|
+
const ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();
|
|
42
|
+
return MIME_TYPES[ext] || 'application/octet-stream';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Build command configuration
|
|
47
|
+
*/
|
|
48
|
+
export interface BuildConfig {
|
|
49
|
+
/** Working directory (project root) */
|
|
50
|
+
cwd: string;
|
|
51
|
+
|
|
52
|
+
/** Build command (default: "pnpm build") */
|
|
53
|
+
command?: string;
|
|
54
|
+
|
|
55
|
+
/** Environment variables to set */
|
|
56
|
+
env?: Record<string, string>;
|
|
57
|
+
|
|
58
|
+
/** Timeout in milliseconds (default: 5 minutes) */
|
|
59
|
+
timeout?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Build result
|
|
64
|
+
*/
|
|
65
|
+
export interface BuildResult {
|
|
66
|
+
/** Whether build was successful */
|
|
67
|
+
success: boolean;
|
|
68
|
+
|
|
69
|
+
/** Output directory path */
|
|
70
|
+
outputDir: string;
|
|
71
|
+
|
|
72
|
+
/** Build output log */
|
|
73
|
+
log: string;
|
|
74
|
+
|
|
75
|
+
/** Error message if failed */
|
|
76
|
+
error?: string;
|
|
77
|
+
|
|
78
|
+
/** Build duration in milliseconds */
|
|
79
|
+
durationMs: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get the default build command based on project config
|
|
84
|
+
*/
|
|
85
|
+
export function getDefaultBuildCommand(config: ProjectConfig): string {
|
|
86
|
+
return config.buildCommand || 'pnpm build';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get the output directory based on project config
|
|
91
|
+
*/
|
|
92
|
+
export function getOutputDir(config: ProjectConfig): string {
|
|
93
|
+
return config.outputDir || 'dist';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Convert a File object to UploadFile format
|
|
98
|
+
*
|
|
99
|
+
* @param file - File object from file input or FileList
|
|
100
|
+
* @param relativePath - Relative path within the bundle
|
|
101
|
+
*/
|
|
102
|
+
export function fileToUploadFile(file: File, relativePath: string): UploadFile {
|
|
103
|
+
return {
|
|
104
|
+
path: relativePath,
|
|
105
|
+
content: file,
|
|
106
|
+
contentType: file.type || getMimeType(file.name),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Calculate total size of files
|
|
112
|
+
*/
|
|
113
|
+
export function calculateTotalSize(files: UploadFile[]): number {
|
|
114
|
+
let total = 0;
|
|
115
|
+
for (const file of files) {
|
|
116
|
+
if (file.content instanceof Blob) {
|
|
117
|
+
total += file.content.size;
|
|
118
|
+
} else if (file.content instanceof ArrayBuffer) {
|
|
119
|
+
total += file.content.byteLength;
|
|
120
|
+
} else if (typeof file.content === 'string') {
|
|
121
|
+
total += new Blob([file.content]).size;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return total;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Format file size for display
|
|
129
|
+
*/
|
|
130
|
+
export function formatFileSize(bytes: number): string {
|
|
131
|
+
if (bytes === 0) return '0 B';
|
|
132
|
+
|
|
133
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
134
|
+
const k = 1024;
|
|
135
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
136
|
+
|
|
137
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${units[i]}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Validate that required files exist in the bundle
|
|
142
|
+
*/
|
|
143
|
+
export function validateBundle(files: UploadFile[]): { valid: boolean; errors: string[] } {
|
|
144
|
+
const errors: string[] = [];
|
|
145
|
+
const paths = new Set(files.map(f => f.path));
|
|
146
|
+
|
|
147
|
+
// Check for index.html
|
|
148
|
+
if (!paths.has('index.html')) {
|
|
149
|
+
errors.push('Missing index.html - required for web deployment');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Warn about large files
|
|
153
|
+
for (const file of files) {
|
|
154
|
+
let size = 0;
|
|
155
|
+
if (file.content instanceof Blob) {
|
|
156
|
+
size = file.content.size;
|
|
157
|
+
} else if (file.content instanceof ArrayBuffer) {
|
|
158
|
+
size = file.content.byteLength;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Warn if file is larger than 10MB
|
|
162
|
+
if (size > 10 * 1024 * 1024) {
|
|
163
|
+
errors.push(`Large file detected: ${file.path} (${formatFileSize(size)})`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
valid: errors.length === 0 || !errors.some(e => e.includes('Missing')),
|
|
169
|
+
errors,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Files to exclude from upload (common patterns)
|
|
175
|
+
*/
|
|
176
|
+
export const EXCLUDE_PATTERNS = [
|
|
177
|
+
/^\.git\//,
|
|
178
|
+
/^node_modules\//,
|
|
179
|
+
/^\.DS_Store$/,
|
|
180
|
+
/^Thumbs\.db$/,
|
|
181
|
+
/\.map$/, // Source maps (optional)
|
|
182
|
+
/^\.env/,
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check if a file should be excluded from upload
|
|
187
|
+
*/
|
|
188
|
+
export function shouldExclude(path: string, includeSourceMaps = false): boolean {
|
|
189
|
+
for (const pattern of EXCLUDE_PATTERNS) {
|
|
190
|
+
if (pattern.test(path)) {
|
|
191
|
+
// Allow source maps if requested
|
|
192
|
+
if (pattern.source === '\\.map$' && includeSourceMaps) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebApp Manifest utilities
|
|
3
|
+
*
|
|
4
|
+
* Generates manifest files for deployed web applications,
|
|
5
|
+
* compatible with Agent Foundry's app registry.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { StudioProject } from '../types/project';
|
|
9
|
+
import type { Deployment } from '../types/deployment';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* WebApp manifest structure for Agent Foundry
|
|
13
|
+
*/
|
|
14
|
+
export interface WebAppManifest {
|
|
15
|
+
/** Schema version */
|
|
16
|
+
schema: string;
|
|
17
|
+
|
|
18
|
+
/** App ID (e.g., "com.agentfoundry.myapp") */
|
|
19
|
+
id: string;
|
|
20
|
+
|
|
21
|
+
/** Display name */
|
|
22
|
+
name: string;
|
|
23
|
+
|
|
24
|
+
/** Short name for icons */
|
|
25
|
+
shortName?: string;
|
|
26
|
+
|
|
27
|
+
/** App description */
|
|
28
|
+
description?: string;
|
|
29
|
+
|
|
30
|
+
/** App version */
|
|
31
|
+
version: string;
|
|
32
|
+
|
|
33
|
+
/** Entry URL (index.html) */
|
|
34
|
+
entryUrl: string;
|
|
35
|
+
|
|
36
|
+
/** Icon URLs */
|
|
37
|
+
icons?: {
|
|
38
|
+
src: string;
|
|
39
|
+
sizes: string;
|
|
40
|
+
type?: string;
|
|
41
|
+
}[];
|
|
42
|
+
|
|
43
|
+
/** Theme color */
|
|
44
|
+
themeColor?: string;
|
|
45
|
+
|
|
46
|
+
/** Background color */
|
|
47
|
+
backgroundColor?: string;
|
|
48
|
+
|
|
49
|
+
/** Display mode */
|
|
50
|
+
display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';
|
|
51
|
+
|
|
52
|
+
/** Orientation preference */
|
|
53
|
+
orientation?: 'any' | 'portrait' | 'landscape';
|
|
54
|
+
|
|
55
|
+
/** Categories */
|
|
56
|
+
categories?: string[];
|
|
57
|
+
|
|
58
|
+
/** Agent Foundry specific metadata */
|
|
59
|
+
agentFoundry?: {
|
|
60
|
+
/** Creator user ID */
|
|
61
|
+
createdBy: string;
|
|
62
|
+
|
|
63
|
+
/** Deployment ID */
|
|
64
|
+
deploymentId: string;
|
|
65
|
+
|
|
66
|
+
/** Project ID */
|
|
67
|
+
projectId: string;
|
|
68
|
+
|
|
69
|
+
/** Framework used */
|
|
70
|
+
framework: string;
|
|
71
|
+
|
|
72
|
+
/** Build timestamp */
|
|
73
|
+
builtAt: string;
|
|
74
|
+
|
|
75
|
+
/** Bundle size in bytes */
|
|
76
|
+
bundleSize?: number;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Options for generating a manifest
|
|
82
|
+
*/
|
|
83
|
+
export interface ManifestOptions {
|
|
84
|
+
/** App ID prefix (default: "com.agentfoundry.studio") */
|
|
85
|
+
idPrefix?: string;
|
|
86
|
+
|
|
87
|
+
/** Theme color */
|
|
88
|
+
themeColor?: string;
|
|
89
|
+
|
|
90
|
+
/** Background color */
|
|
91
|
+
backgroundColor?: string;
|
|
92
|
+
|
|
93
|
+
/** Icon URL */
|
|
94
|
+
iconUrl?: string;
|
|
95
|
+
|
|
96
|
+
/** Categories */
|
|
97
|
+
categories?: string[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Generate a WebApp manifest for a deployed project
|
|
102
|
+
*
|
|
103
|
+
* @param project - The Studio project
|
|
104
|
+
* @param deployment - The deployment record
|
|
105
|
+
* @param options - Optional manifest configuration
|
|
106
|
+
*/
|
|
107
|
+
export function generateManifest(
|
|
108
|
+
project: StudioProject,
|
|
109
|
+
deployment: Deployment,
|
|
110
|
+
options: ManifestOptions = {}
|
|
111
|
+
): WebAppManifest {
|
|
112
|
+
const idPrefix = options.idPrefix ?? 'com.agentfoundry.studio';
|
|
113
|
+
const appId = `${idPrefix}.${project.slug}`;
|
|
114
|
+
|
|
115
|
+
const manifest: WebAppManifest = {
|
|
116
|
+
schema: 'agent-foundry.webapp.v1',
|
|
117
|
+
id: appId,
|
|
118
|
+
name: project.name,
|
|
119
|
+
shortName: project.slug,
|
|
120
|
+
description: project.description,
|
|
121
|
+
version: deployment.version,
|
|
122
|
+
entryUrl: deployment.ossUrl ?? `https://placeholder/${deployment.id}/index.html`,
|
|
123
|
+
display: 'standalone',
|
|
124
|
+
orientation: 'any',
|
|
125
|
+
themeColor: options.themeColor ?? '#000000',
|
|
126
|
+
backgroundColor: options.backgroundColor ?? '#ffffff',
|
|
127
|
+
categories: options.categories ?? ['tools', 'productivity'],
|
|
128
|
+
agentFoundry: {
|
|
129
|
+
createdBy: project.userId,
|
|
130
|
+
deploymentId: deployment.id,
|
|
131
|
+
projectId: project.id,
|
|
132
|
+
framework: project.framework,
|
|
133
|
+
builtAt: deployment.publishedAt ?? deployment.createdAt,
|
|
134
|
+
bundleSize: deployment.bundleSizeBytes,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
if (options.iconUrl) {
|
|
139
|
+
manifest.icons = [
|
|
140
|
+
{ src: options.iconUrl, sizes: '192x192', type: 'image/png' },
|
|
141
|
+
{ src: options.iconUrl, sizes: '512x512', type: 'image/png' },
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return manifest;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Generate a standard web app manifest (PWA compatible)
|
|
150
|
+
*/
|
|
151
|
+
export function generateWebManifest(
|
|
152
|
+
project: StudioProject,
|
|
153
|
+
options: ManifestOptions = {}
|
|
154
|
+
): Record<string, unknown> {
|
|
155
|
+
return {
|
|
156
|
+
name: project.name,
|
|
157
|
+
short_name: project.slug,
|
|
158
|
+
description: project.description,
|
|
159
|
+
start_url: '/',
|
|
160
|
+
display: 'standalone',
|
|
161
|
+
background_color: options.backgroundColor ?? '#ffffff',
|
|
162
|
+
theme_color: options.themeColor ?? '#000000',
|
|
163
|
+
icons: options.iconUrl
|
|
164
|
+
? [
|
|
165
|
+
{ src: options.iconUrl, sizes: '192x192', type: 'image/png' },
|
|
166
|
+
{ src: options.iconUrl, sizes: '512x512', type: 'image/png' },
|
|
167
|
+
]
|
|
168
|
+
: [],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Serialize manifest to JSON string
|
|
174
|
+
*/
|
|
175
|
+
export function serializeManifest(manifest: WebAppManifest): string {
|
|
176
|
+
return JSON.stringify(manifest, null, 2);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Parse manifest from JSON string
|
|
181
|
+
*/
|
|
182
|
+
export function parseManifest(json: string): WebAppManifest {
|
|
183
|
+
const parsed = JSON.parse(json);
|
|
184
|
+
|
|
185
|
+
// Validate schema
|
|
186
|
+
if (!parsed.schema || !parsed.schema.startsWith('agent-foundry.webapp.')) {
|
|
187
|
+
throw new Error('Invalid manifest: missing or invalid schema');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!parsed.id || !parsed.name || !parsed.version) {
|
|
191
|
+
throw new Error('Invalid manifest: missing required fields (id, name, version)');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return parsed as WebAppManifest;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Generate deployment URL path
|
|
199
|
+
*
|
|
200
|
+
* @param userId - User ID
|
|
201
|
+
* @param projectSlug - Project slug
|
|
202
|
+
* @param version - Deployment version
|
|
203
|
+
*/
|
|
204
|
+
export function generateDeploymentPath(
|
|
205
|
+
userId: string,
|
|
206
|
+
projectSlug: string,
|
|
207
|
+
version: string
|
|
208
|
+
): string {
|
|
209
|
+
return `${userId}/${projectSlug}/${version}`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Generate a unique deployment version based on timestamp
|
|
214
|
+
*/
|
|
215
|
+
export function generateVersionFromTimestamp(date: Date = new Date()): string {
|
|
216
|
+
const year = date.getFullYear();
|
|
217
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
218
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
219
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
220
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
221
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
222
|
+
|
|
223
|
+
return `${year}.${month}.${day}-${hours}${minutes}${seconds}`;
|
|
224
|
+
}
|