@notis_ai/cli 0.2.0-beta.76.1 → 0.2.0-beta.95.1
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 +20 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-notes/app/page.tsx +313 -175
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
- package/package.json +2 -1
- package/skills/notis-apps/SKILL.md +25 -5
- package/skills/notis-apps/cli.md +20 -0
- package/src/cli.js +20 -1
- package/src/command-specs/apps.js +249 -7
- package/src/runtime/agent-browser.js +89 -0
- package/src/runtime/app-boundary-validator.js +41 -10
- package/src/runtime/app-dev-server.js +92 -2
- package/src/runtime/app-dev-sessions.js +77 -11
- package/src/runtime/app-platform.js +5 -23
- package/dist/scaffolds/notis-database/metadata/cover.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/cover.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-random/metadata/cover.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
- package/template/metadata/cover.png +0 -0
|
@@ -40,20 +40,50 @@ const IGNORED_SOURCE_DIRS = new Set([
|
|
|
40
40
|
'coverage',
|
|
41
41
|
]);
|
|
42
42
|
|
|
43
|
-
function loadBoundaryRules() {
|
|
44
|
-
return JSON.parse(readFileSync(resolveRulesPath(), 'utf-8'));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
43
|
function compileRules(entries) {
|
|
48
|
-
|
|
44
|
+
// Tolerate a rules object whose javascript/css field is a non-array (a corrupt
|
|
45
|
+
// file can parse to {"javascript":"x"}); coerce anything non-array to [] so
|
|
46
|
+
// getCompiledRules degrades to an empty rule set instead of throwing on .map.
|
|
47
|
+
return (Array.isArray(entries) ? entries : []).map((entry) => ({
|
|
49
48
|
regex: new RegExp(entry.pattern, 'm'),
|
|
50
49
|
message: entry.message,
|
|
51
50
|
}));
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
// Loaded lazily on first validation rather than at module import time: a
|
|
54
|
+
// missing rules file must not prevent the whole CLI from booting (every
|
|
55
|
+
// command imports this module transitively). The server re-validates on
|
|
56
|
+
// save/deploy, so an empty client-side rule set degrades gracefully.
|
|
57
|
+
let compiledRules = null;
|
|
58
|
+
|
|
59
|
+
function getCompiledRules() {
|
|
60
|
+
if (compiledRules) {
|
|
61
|
+
return compiledRules;
|
|
62
|
+
}
|
|
63
|
+
let boundaryRules = { javascript: [], css: [] };
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(readFileSync(resolveRulesPath(), 'utf-8'));
|
|
66
|
+
// Keep the safe default unless the file is a real rules object: a valid but
|
|
67
|
+
// non-object payload (null, a number, an array) would otherwise throw on the
|
|
68
|
+
// `.javascript`/`.css` deref below, reintroducing the boot crash this guards.
|
|
69
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
70
|
+
boundaryRules = parsed;
|
|
71
|
+
} else {
|
|
72
|
+
process.stderr.write(
|
|
73
|
+
'Warning: Notis app boundary rules are not a rules object; skipping local boundary checks.\n',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
process.stderr.write(
|
|
78
|
+
`Warning: could not load Notis app boundary rules (${error.message}); skipping local boundary checks.\n`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
compiledRules = {
|
|
82
|
+
javascript: compileRules(boundaryRules.javascript),
|
|
83
|
+
css: compileRules(boundaryRules.css),
|
|
84
|
+
};
|
|
85
|
+
return compiledRules;
|
|
86
|
+
}
|
|
57
87
|
|
|
58
88
|
function collectProjectFiles(projectDir, dir, results) {
|
|
59
89
|
if (!existsSync(dir)) {
|
|
@@ -101,11 +131,12 @@ function applyRules(content, rules, relPath) {
|
|
|
101
131
|
|
|
102
132
|
function validateTextFile(relPath, content) {
|
|
103
133
|
const extension = extname(relPath);
|
|
134
|
+
const rules = getCompiledRules();
|
|
104
135
|
if (extension === '.css' || extension === '.scss' || extension === '.sass' || extension === '.pcss') {
|
|
105
|
-
return applyRules(content,
|
|
136
|
+
return applyRules(content, rules.css, relPath);
|
|
106
137
|
}
|
|
107
138
|
if (extension === '.js' || extension === '.jsx' || extension === '.ts' || extension === '.tsx') {
|
|
108
|
-
return applyRules(content,
|
|
139
|
+
return applyRules(content, rules.javascript, relPath);
|
|
109
140
|
}
|
|
110
141
|
return [];
|
|
111
142
|
}
|
|
@@ -25,13 +25,17 @@ import {
|
|
|
25
25
|
loadAppConfig,
|
|
26
26
|
prepareArtifactBuild,
|
|
27
27
|
readManifest,
|
|
28
|
+
readLinkedState,
|
|
29
|
+
writeLinkedState,
|
|
28
30
|
} from './app-platform.js';
|
|
31
|
+
import { linkAppDevSessionTarget } from './app-dev-sessions.js';
|
|
29
32
|
|
|
30
33
|
const CONTENT_TYPES = {
|
|
31
34
|
'.js': 'application/javascript; charset=utf-8',
|
|
32
35
|
'.css': 'text/css; charset=utf-8',
|
|
33
36
|
'.map': 'application/json; charset=utf-8',
|
|
34
37
|
};
|
|
38
|
+
const MAX_JSON_BODY_BYTES = 64 * 1024;
|
|
35
39
|
const RUNTIME_DIR = dirname(fileURLToPath(import.meta.url));
|
|
36
40
|
const CLI_ROOT = resolve(RUNTIME_DIR, '../..');
|
|
37
41
|
const REPO_ROOT = resolve(RUNTIME_DIR, '../../../..');
|
|
@@ -59,7 +63,7 @@ function corsHeaders(origin) {
|
|
|
59
63
|
const allowOrigin = origin && isAllowedOrigin(origin) ? origin : '*';
|
|
60
64
|
return {
|
|
61
65
|
'Access-Control-Allow-Origin': allowOrigin,
|
|
62
|
-
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
|
66
|
+
'Access-Control-Allow-Methods': 'GET, HEAD, POST, OPTIONS',
|
|
63
67
|
'Access-Control-Allow-Headers': 'Content-Type, Cache-Control',
|
|
64
68
|
'Cache-Control': 'no-store',
|
|
65
69
|
};
|
|
@@ -97,6 +101,32 @@ function readJsonFile(path) {
|
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
103
|
|
|
104
|
+
function readRequestJson(req) {
|
|
105
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
106
|
+
let body = '';
|
|
107
|
+
req.setEncoding('utf8');
|
|
108
|
+
req.on('data', (chunk) => {
|
|
109
|
+
body += chunk;
|
|
110
|
+
if (Buffer.byteLength(body, 'utf8') > MAX_JSON_BODY_BYTES) {
|
|
111
|
+
rejectPromise(new Error('request body too large'));
|
|
112
|
+
req.destroy();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
req.on('end', () => {
|
|
116
|
+
if (!body.trim()) {
|
|
117
|
+
resolvePromise({});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
resolvePromise(JSON.parse(body));
|
|
122
|
+
} catch {
|
|
123
|
+
rejectPromise(new Error('invalid JSON body'));
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
req.on('error', rejectPromise);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
100
130
|
function reactVersionFromPeer(peerRange) {
|
|
101
131
|
if (typeof peerRange !== 'string' || !peerRange) {
|
|
102
132
|
return FALLBACK_REACT_VERSION;
|
|
@@ -243,7 +273,7 @@ function renderHarnessHtml({ state, manifest, appConfig, route, harnessOptions }
|
|
|
243
273
|
/**
|
|
244
274
|
* Start the dev server for one or more apps.
|
|
245
275
|
*
|
|
246
|
-
* @param {{apps: Array<{slug: string, projectDir: string, appId?: string, userId?: string}>, port: number, watch?: boolean, harness?: { mode?: string, apiBase?: string, jwt?: string }, log?: (m: string) => void, logError?: (m: string) => void}} options
|
|
276
|
+
* @param {{apps: Array<{slug: string, projectDir: string, appId?: string, targetAppId?: string, userId?: string}>, port: number, watch?: boolean, harness?: { mode?: string, apiBase?: string, jwt?: string }, log?: (m: string) => void, logError?: (m: string) => void}} options
|
|
247
277
|
*/
|
|
248
278
|
export async function startAppDevServer({
|
|
249
279
|
apps,
|
|
@@ -263,6 +293,7 @@ export async function startAppDevServer({
|
|
|
263
293
|
slug: app.slug,
|
|
264
294
|
projectDir: app.projectDir,
|
|
265
295
|
appId: app.appId || null,
|
|
296
|
+
targetAppId: app.targetAppId || null,
|
|
266
297
|
userId: app.userId || null,
|
|
267
298
|
canonicalBundleDir: getBundleDir(app.projectDir),
|
|
268
299
|
bundleDir: null,
|
|
@@ -385,6 +416,63 @@ export async function startAppDevServer({
|
|
|
385
416
|
}
|
|
386
417
|
|
|
387
418
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
419
|
+
if (req.method === 'POST') {
|
|
420
|
+
const routed = matchAppRoute(url.pathname);
|
|
421
|
+
const state = routed ? appState.get(routed.slug) : null;
|
|
422
|
+
if (state && routed.rest === 'link') {
|
|
423
|
+
void (async () => {
|
|
424
|
+
try {
|
|
425
|
+
const body = await readRequestJson(req);
|
|
426
|
+
const appId = typeof body.app_id === 'string' ? body.app_id.trim() : '';
|
|
427
|
+
if (!appId) {
|
|
428
|
+
res.writeHead(400, {
|
|
429
|
+
...headers,
|
|
430
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
431
|
+
});
|
|
432
|
+
res.end(JSON.stringify({ error: 'app_id is required' }));
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const currentState = readLinkedState(state.projectDir) || {};
|
|
437
|
+
const now = new Date().toISOString();
|
|
438
|
+
writeLinkedState(state.projectDir, {
|
|
439
|
+
...currentState,
|
|
440
|
+
app_id: appId,
|
|
441
|
+
dev_app_id: state.appId || currentState.dev_app_id,
|
|
442
|
+
linked_at: currentState.linked_at || now,
|
|
443
|
+
dev_linked_at: currentState.dev_linked_at || now,
|
|
444
|
+
updated_at: now,
|
|
445
|
+
});
|
|
446
|
+
state.targetAppId = appId;
|
|
447
|
+
linkAppDevSessionTarget({
|
|
448
|
+
appId: state.appId,
|
|
449
|
+
devSlug: state.slug,
|
|
450
|
+
targetAppId: appId,
|
|
451
|
+
lastHeartbeatAt: now,
|
|
452
|
+
});
|
|
453
|
+
const response = {
|
|
454
|
+
ok: true,
|
|
455
|
+
app_id: appId,
|
|
456
|
+
dev_app_id: state.appId || null,
|
|
457
|
+
target_app_id: appId,
|
|
458
|
+
};
|
|
459
|
+
res.writeHead(200, {
|
|
460
|
+
...headers,
|
|
461
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
462
|
+
});
|
|
463
|
+
res.end(JSON.stringify(response));
|
|
464
|
+
} catch (error) {
|
|
465
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
466
|
+
res.writeHead(400, {
|
|
467
|
+
...headers,
|
|
468
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
469
|
+
});
|
|
470
|
+
res.end(JSON.stringify({ error: message }));
|
|
471
|
+
}
|
|
472
|
+
})();
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
388
476
|
res.writeHead(405, headers);
|
|
389
477
|
res.end('method not allowed');
|
|
390
478
|
return;
|
|
@@ -394,6 +482,7 @@ export async function startAppDevServer({
|
|
|
394
482
|
const now = new Date().toISOString();
|
|
395
483
|
const sessions = Array.from(appState.values()).map((state) => ({
|
|
396
484
|
appId: state.appId,
|
|
485
|
+
targetAppId: state.targetAppId || undefined,
|
|
397
486
|
userId: state.userId,
|
|
398
487
|
devSlug: state.slug,
|
|
399
488
|
bundleBaseUrl: `http://127.0.0.1:${port}/a/${state.slug}`,
|
|
@@ -446,6 +535,7 @@ export async function startAppDevServer({
|
|
|
446
535
|
const manifest = readManifest(state.projectDir);
|
|
447
536
|
const payload = {
|
|
448
537
|
app_id: state.appId,
|
|
538
|
+
target_app_id: state.targetAppId || null,
|
|
449
539
|
dev_slug: state.slug,
|
|
450
540
|
manifest,
|
|
451
541
|
files: collectArtifactFiles(state.projectDir),
|
|
@@ -1,33 +1,78 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import {
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { dirname, join, parse as parsePath } from 'node:path';
|
|
4
5
|
import { CONFIG_DIR } from './profiles.js';
|
|
5
6
|
|
|
6
7
|
export const DEFAULT_APP_DEV_SESSIONS_FILE = join(CONFIG_DIR, 'app-dev-sessions.json');
|
|
7
8
|
export const APP_DEV_SESSIONS_FILE = DEFAULT_APP_DEV_SESSIONS_FILE;
|
|
8
9
|
export const APP_DEV_SESSIONS_VERSION = 1;
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Walk up from `startDir` looking for a workspace-scoped dev-sessions registry.
|
|
13
|
+
*
|
|
14
|
+
* The Notis desktop (and Conductor) reads a PER-WORKSPACE registry at
|
|
15
|
+
* `<workspace>/.context/app-dev-sessions.json` (it points its own `apps dev`
|
|
16
|
+
* at it via `NOTIS_APP_DEV_SESSIONS_FILE`). When a coding agent runs
|
|
17
|
+
* `apps dev` manually it does not inherit that env var, so its session would
|
|
18
|
+
* otherwise land in the global `~/.notis` registry and never appear in the
|
|
19
|
+
* desktop's Local development sidebar. Resolving the nearest `.context`
|
|
20
|
+
* registry keeps agent-run and desktop-run sessions in the same file.
|
|
21
|
+
*
|
|
22
|
+
* Returns the registry path if a `.context/` directory is found in `startDir`
|
|
23
|
+
* or an ancestor (stopping at the home directory / filesystem root), else null.
|
|
24
|
+
*/
|
|
25
|
+
export function findWorkspaceAppDevSessionsFile(startDir = process.cwd()) {
|
|
26
|
+
let dir = startDir;
|
|
27
|
+
const home = homedir();
|
|
28
|
+
const { root } = parsePath(dir);
|
|
29
|
+
// Bounded walk; `.context` lives at the workspace root, never above $HOME.
|
|
30
|
+
for (let depth = 0; depth < 64; depth += 1) {
|
|
31
|
+
if (existsSync(join(dir, '.context'))) {
|
|
32
|
+
return join(dir, '.context', 'app-dev-sessions.json');
|
|
33
|
+
}
|
|
34
|
+
if (dir === home || dir === root) break;
|
|
35
|
+
const parent = dirname(dir);
|
|
36
|
+
if (parent === dir) break;
|
|
37
|
+
dir = parent;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
10
42
|
export function getAppDevSessionsFile(filePath) {
|
|
11
43
|
if (filePath) {
|
|
12
44
|
return filePath;
|
|
13
45
|
}
|
|
14
46
|
const envPath = process.env.NOTIS_APP_DEV_SESSIONS_FILE;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
47
|
+
if (typeof envPath === 'string' && envPath.trim()) {
|
|
48
|
+
return envPath.trim();
|
|
49
|
+
}
|
|
50
|
+
const workspaceFile = findWorkspaceAppDevSessionsFile();
|
|
51
|
+
if (workspaceFile) {
|
|
52
|
+
return workspaceFile;
|
|
53
|
+
}
|
|
54
|
+
return DEFAULT_APP_DEV_SESSIONS_FILE;
|
|
18
55
|
}
|
|
19
56
|
|
|
20
57
|
function normalizeSessions(raw) {
|
|
21
58
|
if (!raw || typeof raw !== 'object' || !Array.isArray(raw.sessions)) {
|
|
22
59
|
return [];
|
|
23
60
|
}
|
|
24
|
-
return raw.sessions
|
|
25
|
-
session
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
61
|
+
return raw.sessions
|
|
62
|
+
.filter((session) =>
|
|
63
|
+
session &&
|
|
64
|
+
typeof session === 'object' &&
|
|
65
|
+
typeof session.sessionId === 'string' &&
|
|
66
|
+
typeof session.appId === 'string' &&
|
|
67
|
+
typeof session.bundleBaseUrl === 'string',
|
|
68
|
+
)
|
|
69
|
+
.map((session) => {
|
|
70
|
+
if (typeof session.targetAppId === 'string' && session.targetAppId.trim()) {
|
|
71
|
+
return { ...session, targetAppId: session.targetAppId.trim() };
|
|
72
|
+
}
|
|
73
|
+
const { targetAppId: _targetAppId, ...rest } = session;
|
|
74
|
+
return rest;
|
|
75
|
+
});
|
|
31
76
|
}
|
|
32
77
|
|
|
33
78
|
export function readAppDevSessions(filePath) {
|
|
@@ -80,6 +125,27 @@ export function heartbeatAppDevSession(sessionId, lastHeartbeatAt, filePath) {
|
|
|
80
125
|
return writeAppDevSessions(registry, filePath);
|
|
81
126
|
}
|
|
82
127
|
|
|
128
|
+
export function linkAppDevSessionTarget({ appId, devSlug, targetAppId, lastHeartbeatAt = new Date().toISOString() }, filePath) {
|
|
129
|
+
const target = typeof targetAppId === 'string' ? targetAppId.trim() : '';
|
|
130
|
+
if (!target) {
|
|
131
|
+
return readAppDevSessions(filePath);
|
|
132
|
+
}
|
|
133
|
+
const registry = readAppDevSessions(filePath);
|
|
134
|
+
registry.sessions = registry.sessions.map((session) => {
|
|
135
|
+
const matchesApp = appId && session.appId === appId;
|
|
136
|
+
const matchesSlug = devSlug && session.devSlug === devSlug;
|
|
137
|
+
if (!matchesApp || !matchesSlug) {
|
|
138
|
+
return session;
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
...session,
|
|
142
|
+
targetAppId: target,
|
|
143
|
+
lastHeartbeatAt,
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
return writeAppDevSessions(registry, filePath);
|
|
147
|
+
}
|
|
148
|
+
|
|
83
149
|
export function removeAppDevSession(sessionId, filePath) {
|
|
84
150
|
const registry = readAppDevSessions(filePath);
|
|
85
151
|
registry.sessions = registry.sessions.filter((session) => session.sessionId !== sessionId);
|
|
@@ -324,10 +324,6 @@ function imageAssetMeta(projectDir, relPath, { maxBytes, recommendedRatio = 1.6
|
|
|
324
324
|
|
|
325
325
|
export function discoverMetadataAssets(projectDir) {
|
|
326
326
|
const metadataDir = join(projectDir, METADATA_DIR);
|
|
327
|
-
const coverPath = join(METADATA_DIR, 'cover.png');
|
|
328
|
-
const cover = existsSync(join(projectDir, coverPath))
|
|
329
|
-
? imageAssetMeta(projectDir, coverPath, { maxBytes: 5 * 1024 * 1024 })
|
|
330
|
-
: null;
|
|
331
327
|
const screenshots = [];
|
|
332
328
|
|
|
333
329
|
if (existsSync(metadataDir)) {
|
|
@@ -346,7 +342,6 @@ export function discoverMetadataAssets(projectDir) {
|
|
|
346
342
|
|
|
347
343
|
screenshots.sort((a, b) => a.index - b.index);
|
|
348
344
|
return {
|
|
349
|
-
cover,
|
|
350
345
|
screenshots: screenshots.slice(0, 6),
|
|
351
346
|
};
|
|
352
347
|
}
|
|
@@ -364,14 +359,8 @@ export function inspectListingReadiness(projectDir, appConfig = null) {
|
|
|
364
359
|
if (categories.length === 0) {
|
|
365
360
|
warnings.push(`Listing category missing in notis.config.ts. Use one of: ${NOTIS_APP_CATEGORIES.join(', ')}.`);
|
|
366
361
|
}
|
|
367
|
-
if (!metadata.cover) {
|
|
368
|
-
warnings.push('Listing cover missing at metadata/cover.png.');
|
|
369
|
-
} else {
|
|
370
|
-
errors.push(...metadata.cover.errors);
|
|
371
|
-
warnings.push(...metadata.cover.warnings);
|
|
372
|
-
}
|
|
373
362
|
if (metadata.screenshots.length < 3) {
|
|
374
|
-
warnings.push(`Listing screenshots: ${metadata.screenshots.length}/3 minimum in metadata/screenshot-N.png.`);
|
|
363
|
+
warnings.push(`Listing screenshots: ${metadata.screenshots.length}/3 minimum in metadata/screenshot-N.png. Run \`notis apps screenshot\` to generate them.`);
|
|
375
364
|
}
|
|
376
365
|
for (const screenshot of metadata.screenshots) {
|
|
377
366
|
errors.push(...screenshot.errors);
|
|
@@ -379,7 +368,7 @@ export function inspectListingReadiness(projectDir, appConfig = null) {
|
|
|
379
368
|
}
|
|
380
369
|
|
|
381
370
|
return {
|
|
382
|
-
ready: errors.length === 0 && Boolean(config.tagline) && categories.length > 0 &&
|
|
371
|
+
ready: errors.length === 0 && Boolean(config.tagline) && categories.length > 0 && metadata.screenshots.length >= 1,
|
|
383
372
|
warnings,
|
|
384
373
|
errors,
|
|
385
374
|
metadata,
|
|
@@ -621,15 +610,6 @@ export function generateManifest(appConfig, projectDir) {
|
|
|
621
610
|
const appSlug = safeKebab(appConfig.name);
|
|
622
611
|
const displayTitle = appConfig.title || appConfig.displayName || appConfig.name;
|
|
623
612
|
const listingMedia = {
|
|
624
|
-
cover: metadata.cover
|
|
625
|
-
? {
|
|
626
|
-
path: metadata.cover.path,
|
|
627
|
-
content_type: metadata.cover.content_type,
|
|
628
|
-
width: metadata.cover.width,
|
|
629
|
-
height: metadata.cover.height,
|
|
630
|
-
bytes: metadata.cover.bytes,
|
|
631
|
-
}
|
|
632
|
-
: null,
|
|
633
613
|
screenshots: metadata.screenshots.map((screenshot) => ({
|
|
634
614
|
path: screenshot.path,
|
|
635
615
|
content_type: screenshot.content_type,
|
|
@@ -647,6 +627,7 @@ export function generateManifest(appConfig, projectDir) {
|
|
|
647
627
|
slug: appSlug || null,
|
|
648
628
|
description: appConfig.description || null,
|
|
649
629
|
icon: appConfig.icon || null,
|
|
630
|
+
accent: appConfig.accent || null,
|
|
650
631
|
title: displayTitle,
|
|
651
632
|
tagline: appConfig.tagline || null,
|
|
652
633
|
categories,
|
|
@@ -721,7 +702,7 @@ function copyMetadataAssets(projectDir) {
|
|
|
721
702
|
mkdirSync(outputMetadataDir, { recursive: true });
|
|
722
703
|
for (const entry of readdirSync(metadataDir, { withFileTypes: true })) {
|
|
723
704
|
if (!entry.isFile()) continue;
|
|
724
|
-
if (
|
|
705
|
+
if (!/^screenshot-\d+\.png$/i.test(entry.name)) {
|
|
725
706
|
continue;
|
|
726
707
|
}
|
|
727
708
|
cpSync(join(metadataDir, entry.name), join(outputMetadataDir, entry.name));
|
|
@@ -1259,6 +1240,7 @@ async function updateAppVersion(supabaseUrl, supabaseKey, appId, newVersion, man
|
|
|
1259
1240
|
},
|
|
1260
1241
|
body: JSON.stringify({
|
|
1261
1242
|
manifest: { ...manifest, version: newVersion },
|
|
1243
|
+
accent: manifest?.app?.accent ?? null,
|
|
1262
1244
|
updated_at: new Date().toISOString(),
|
|
1263
1245
|
}),
|
|
1264
1246
|
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|