@notis_ai/cli 0.2.1 → 0.2.3
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 +106 -170
- package/config/notis_app_boundary_rules.json +50 -0
- package/dist/scaffolds/notis-database/app/globals.css +44 -0
- package/dist/scaffolds/notis-database/app/layout.tsx +6 -0
- package/dist/scaffolds/notis-database/app/page.tsx +1091 -0
- package/dist/scaffolds/notis-database/components/ui/badge.tsx +28 -0
- package/dist/scaffolds/notis-database/components/ui/button.tsx +53 -0
- package/dist/scaffolds/notis-database/components/ui/card.tsx +56 -0
- package/dist/scaffolds/notis-database/components/ui/table.tsx +120 -0
- package/dist/scaffolds/notis-database/components.json +20 -0
- package/dist/scaffolds/notis-database/index.html +12 -0
- package/dist/scaffolds/notis-database/lib/types.ts +134 -0
- package/dist/scaffolds/notis-database/lib/utils.ts +6 -0
- package/dist/scaffolds/notis-database/metadata/cover.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-1.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-database/notis.config.ts +27 -0
- package/dist/scaffolds/notis-database/package.json +31 -0
- package/dist/scaffolds/notis-database/postcss.config.mjs +8 -0
- package/dist/scaffolds/notis-database/src/dev-main.tsx +23 -0
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +557 -0
- package/dist/scaffolds/notis-database/tailwind.config.ts +59 -0
- package/dist/scaffolds/notis-database/tsconfig.json +23 -0
- package/dist/scaffolds/notis-database/vite.config.ts +22 -0
- package/dist/scaffolds/notis-notes/app/globals.css +3 -0
- package/dist/scaffolds/notis-notes/app/layout.tsx +6 -0
- package/dist/scaffolds/notis-notes/app/page.tsx +2168 -0
- package/dist/scaffolds/notis-notes/components/ui/badge.tsx +28 -0
- package/dist/scaffolds/notis-notes/components/ui/button.tsx +53 -0
- package/dist/scaffolds/notis-notes/components/ui/card.tsx +56 -0
- package/dist/scaffolds/notis-notes/components.json +20 -0
- package/dist/scaffolds/notis-notes/lib/utils.ts +6 -0
- package/dist/scaffolds/notis-notes/metadata/cover.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.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-notes/notis.config.ts +36 -0
- package/dist/scaffolds/notis-notes/package.json +31 -0
- package/dist/scaffolds/notis-notes/postcss.config.mjs +8 -0
- package/dist/scaffolds/notis-notes/tailwind.config.ts +58 -0
- package/dist/scaffolds/notis-notes/tsconfig.json +23 -0
- package/dist/scaffolds/notis-notes/vite.config.ts +10 -0
- package/dist/scaffolds/notis-random/README.md +33 -0
- package/dist/scaffolds/notis-random/app/globals.css +11 -0
- package/dist/scaffolds/notis-random/app/history/page.tsx +66 -0
- package/dist/scaffolds/notis-random/app/layout.tsx +7 -0
- package/dist/scaffolds/notis-random/app/page.tsx +222 -0
- package/dist/scaffolds/notis-random/index.html +12 -0
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +109 -0
- package/dist/scaffolds/notis-random/lib/rng.ts +42 -0
- package/dist/scaffolds/notis-random/lib/roll-record.ts +102 -0
- package/dist/scaffolds/notis-random/lib/utils.ts +25 -0
- package/dist/scaffolds/notis-random/metadata/cover.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/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-random/notis.config.ts +43 -0
- package/dist/scaffolds/notis-random/package.json +32 -0
- package/dist/scaffolds/notis-random/postcss.config.mjs +6 -0
- package/dist/scaffolds/notis-random/src/dev-main.tsx +70 -0
- package/dist/scaffolds/notis-random/src/mock-runtime.ts +128 -0
- package/dist/scaffolds/notis-random/tailwind.config.ts +43 -0
- package/dist/scaffolds/notis-random/tsconfig.json +23 -0
- package/dist/scaffolds/notis-random/vite.config.ts +11 -0
- package/dist/scaffolds.json +36 -0
- package/package.json +8 -3
- package/skills/notis-apps/SKILL.md +162 -0
- package/skills/notis-apps/cli.md +208 -0
- package/skills/notis-cli/SKILL.md +260 -0
- package/skills/notis-query/cli.md +39 -0
- package/src/cli.js +31 -3
- package/src/command-specs/apps.js +248 -56
- package/src/command-specs/helpers.js +72 -104
- package/src/command-specs/index.js +0 -6
- package/src/command-specs/meta.js +14 -13
- package/src/command-specs/tools.js +196 -115
- package/src/runtime/app-boundary-validator.js +65 -14
- package/src/runtime/app-dev-server.js +32 -4
- package/src/runtime/app-platform.js +404 -24
- package/src/runtime/profiles.js +12 -6
- package/src/runtime/transport.js +124 -39
- package/template/.harness/index.html.tmpl +1 -50
- package/template/app/page.tsx +41 -6
- package/template/metadata/cover.png +0 -0
- package/template/metadata/screenshot-1.png +0 -0
- package/template/metadata/screenshot-2.png +0 -0
- package/template/metadata/screenshot-3.png +0 -0
- package/template/notis.config.ts +10 -6
- package/template/package.json +2 -2
- package/template/packages/{notis-sdk → sdk}/package.json +6 -0
- package/template/packages/{notis-sdk → sdk}/src/components/MultiSelectActionBar.tsx +2 -1
- package/template/packages/{notis-sdk → sdk}/src/components/MultiSelectCheckbox.tsx +2 -2
- package/template/packages/{notis-sdk → sdk}/src/components/MultiSelectDragOverlay.tsx +2 -2
- package/template/packages/{notis-sdk → sdk}/src/config.ts +1 -1
- package/template/packages/{notis-sdk → sdk}/src/hooks/useDatabase.ts +1 -13
- package/template/packages/{notis-sdk → sdk}/src/hooks/useMultiSelect.ts +4 -3
- package/template/packages/{notis-sdk → sdk}/src/hooks/useNotis.ts +4 -3
- package/template/packages/{notis-sdk → sdk}/src/hooks/useNotisNavigation.ts +3 -3
- package/template/packages/{notis-sdk → sdk}/src/hooks/useTool.ts +22 -7
- package/template/packages/{notis-sdk → sdk}/src/hooks/useUpsertDocument.ts +0 -8
- package/template/packages/{notis-sdk → sdk}/src/index.ts +2 -15
- package/template/packages/{notis-sdk → sdk}/src/provider.tsx +1 -1
- package/template/packages/{notis-sdk → sdk}/src/runtime.ts +5 -26
- package/src/command-specs/auth.js +0 -178
- package/src/command-specs/db.js +0 -163
- package/template/packages/notis-sdk/src/helpers.ts +0 -131
- package/template/packages/notis-sdk/src/hooks/useAppState.ts +0 -50
- package/template/packages/notis-sdk/src/hooks/useCollectionItem.ts +0 -58
- package/template/packages/notis-sdk/src/hooks/useDocument.ts +0 -61
- /package/template/packages/{notis-sdk → sdk}/src/hooks/useBackend.ts +0 -0
- /package/template/packages/{notis-sdk → sdk}/src/hooks/useTools.ts +0 -0
- /package/template/packages/{notis-sdk → sdk}/src/hooks/useTopBarSearch.ts +0 -0
- /package/template/packages/{notis-sdk → sdk}/src/styles.css +0 -0
- /package/template/packages/{notis-sdk → sdk}/src/ui.ts +0 -0
- /package/template/packages/{notis-sdk → sdk}/src/vite.ts +0 -0
- /package/template/packages/{notis-sdk → sdk}/tsconfig.json +0 -0
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
exportNameFromPath,
|
|
24
24
|
getBundleDir,
|
|
25
25
|
loadAppConfig,
|
|
26
|
+
prepareArtifactBuild,
|
|
26
27
|
readManifest,
|
|
27
28
|
} from './app-platform.js';
|
|
28
29
|
|
|
@@ -77,6 +78,14 @@ function scriptJson(value) {
|
|
|
77
78
|
.replace(/\u2029/g, '\\u2029');
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
function timingMs(startedAt) {
|
|
82
|
+
return Number(process.hrtime.bigint() - startedAt) / 1_000_000;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function formatTimingMs(value) {
|
|
86
|
+
return value.toFixed(value < 10 ? 2 : 1);
|
|
87
|
+
}
|
|
88
|
+
|
|
80
89
|
function readJsonFile(path) {
|
|
81
90
|
if (!existsSync(path)) {
|
|
82
91
|
return null;
|
|
@@ -105,8 +114,8 @@ function reactVersionFromPeer(peerRange) {
|
|
|
105
114
|
function resolveHarnessReactVersion(projectDir) {
|
|
106
115
|
const candidates = [
|
|
107
116
|
join(projectDir, 'node_modules', '@notis', 'sdk', 'package.json'),
|
|
108
|
-
join(REPO_ROOT, 'packages', '
|
|
109
|
-
join(CLI_ROOT, 'template', 'packages', '
|
|
117
|
+
join(REPO_ROOT, 'packages', 'sdk', 'package.json'),
|
|
118
|
+
join(CLI_ROOT, 'template', 'packages', 'sdk', 'package.json'),
|
|
110
119
|
];
|
|
111
120
|
for (const candidate of candidates) {
|
|
112
121
|
const pkg = readJsonFile(candidate);
|
|
@@ -234,7 +243,7 @@ function renderHarnessHtml({ state, manifest, appConfig, route, harnessOptions }
|
|
|
234
243
|
/**
|
|
235
244
|
* Start the dev server for one or more apps.
|
|
236
245
|
*
|
|
237
|
-
* @param {{apps: Array<{slug: string, projectDir: string, appId?: string}>, port: number, watch?: boolean, harness?: { mode?: string, apiBase?: string, jwt?: string }, log?: (m: string) => void, logError?: (m: string) => void}} options
|
|
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
|
|
238
247
|
*/
|
|
239
248
|
export async function startAppDevServer({
|
|
240
249
|
apps,
|
|
@@ -254,6 +263,7 @@ export async function startAppDevServer({
|
|
|
254
263
|
slug: app.slug,
|
|
255
264
|
projectDir: app.projectDir,
|
|
256
265
|
appId: app.appId || null,
|
|
266
|
+
userId: app.userId || null,
|
|
257
267
|
canonicalBundleDir: getBundleDir(app.projectDir),
|
|
258
268
|
bundleDir: null,
|
|
259
269
|
jsPath: null,
|
|
@@ -381,11 +391,21 @@ export async function startAppDevServer({
|
|
|
381
391
|
}
|
|
382
392
|
|
|
383
393
|
if (url.pathname === '/healthz') {
|
|
394
|
+
const now = new Date().toISOString();
|
|
395
|
+
const sessions = Array.from(appState.values()).map((state) => ({
|
|
396
|
+
appId: state.appId,
|
|
397
|
+
userId: state.userId,
|
|
398
|
+
devSlug: state.slug,
|
|
399
|
+
bundleBaseUrl: `http://127.0.0.1:${port}/a/${state.slug}`,
|
|
400
|
+
projectDir: state.projectDir,
|
|
401
|
+
lastHeartbeatAt: now,
|
|
402
|
+
status: 'connected',
|
|
403
|
+
}));
|
|
384
404
|
res.writeHead(200, {
|
|
385
405
|
...headers,
|
|
386
406
|
'Content-Type': 'application/json; charset=utf-8',
|
|
387
407
|
});
|
|
388
|
-
res.end(JSON.stringify({ ok: true, apps: Array.from(appState.keys()) }));
|
|
408
|
+
res.end(JSON.stringify({ ok: true, apps: Array.from(appState.keys()), sessions }));
|
|
389
409
|
return;
|
|
390
410
|
}
|
|
391
411
|
|
|
@@ -454,17 +474,20 @@ export async function startAppDevServer({
|
|
|
454
474
|
}
|
|
455
475
|
|
|
456
476
|
if (routed.rest.startsWith('bundle/')) {
|
|
477
|
+
const startedAt = process.hrtime.bigint();
|
|
457
478
|
updateBundleDir(state, resolveBundleDir(state));
|
|
458
479
|
const rel = routed.rest.slice('bundle/'.length);
|
|
459
480
|
if (!state.bundleDir) {
|
|
460
481
|
res.writeHead(404, headers);
|
|
461
482
|
res.end('not found');
|
|
483
|
+
log(`[notis apps timing] ${state.slug}: GET bundle/${rel} -> 404 in ${formatTimingMs(timingMs(startedAt))}ms`);
|
|
462
484
|
return;
|
|
463
485
|
}
|
|
464
486
|
const full = safeJoin(state.bundleDir, rel);
|
|
465
487
|
if (!full || !existsSync(full)) {
|
|
466
488
|
res.writeHead(404, headers);
|
|
467
489
|
res.end('not found');
|
|
490
|
+
log(`[notis apps timing] ${state.slug}: GET bundle/${rel} -> 404 in ${formatTimingMs(timingMs(startedAt))}ms`);
|
|
468
491
|
return;
|
|
469
492
|
}
|
|
470
493
|
const ext = extFor(rel);
|
|
@@ -480,6 +503,10 @@ export async function startAppDevServer({
|
|
|
480
503
|
} else {
|
|
481
504
|
res.end(content);
|
|
482
505
|
}
|
|
506
|
+
log(
|
|
507
|
+
`[notis apps timing] ${state.slug}: GET bundle/${rel} -> 200 ` +
|
|
508
|
+
`${content.byteLength}B in ${formatTimingMs(timingMs(startedAt))}ms`,
|
|
509
|
+
);
|
|
483
510
|
return;
|
|
484
511
|
}
|
|
485
512
|
|
|
@@ -527,6 +554,7 @@ export async function startAppDevServer({
|
|
|
527
554
|
|
|
528
555
|
for (const state of appState.values()) {
|
|
529
556
|
if (watch) {
|
|
557
|
+
await prepareArtifactBuild(state.projectDir);
|
|
530
558
|
pollForBundleAndWatch(state);
|
|
531
559
|
|
|
532
560
|
state.buildProcess = spawn('npm', ['run', 'build', '--', '--watch'], {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { spawn } from 'node:child_process';
|
|
10
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, rmSync } from 'node:fs';
|
|
10
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
11
11
|
import { createRequire } from 'node:module';
|
|
12
12
|
import { dirname, join, resolve } from 'node:path';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
@@ -21,15 +21,34 @@ const STATE_FILE = join(NOTIS_DIR, 'state.json');
|
|
|
21
21
|
const OUTPUT_DIR = join(NOTIS_DIR, 'output');
|
|
22
22
|
const BUNDLE_DIR = join(OUTPUT_DIR, 'bundle');
|
|
23
23
|
const MANIFEST_FILE = join(OUTPUT_DIR, 'manifest.json');
|
|
24
|
+
const METADATA_DIR = 'metadata';
|
|
25
|
+
const CLI_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
|
26
|
+
const MONOREPO_APPS_DIR = resolve(CLI_ROOT, '../..', 'apps');
|
|
27
|
+
const DIST_DIR = join(CLI_ROOT, 'dist');
|
|
28
|
+
const SCAFFOLD_CATALOG_FILE = join(DIST_DIR, 'scaffolds.json');
|
|
29
|
+
const SCAFFOLD_SOURCE_DIR = join(DIST_DIR, 'scaffolds');
|
|
30
|
+
const TEMPLATE_SDK_DIR = join(CLI_ROOT, 'template', 'packages', 'sdk');
|
|
31
|
+
export const NOTIS_APP_CATEGORIES = [
|
|
32
|
+
'Productivity',
|
|
33
|
+
'Sales & Marketing',
|
|
34
|
+
'Operations',
|
|
35
|
+
'Product & Engineering',
|
|
36
|
+
'Personal',
|
|
37
|
+
];
|
|
24
38
|
const SOURCE_COPY_EXCLUDES = new Set([
|
|
25
39
|
'node_modules',
|
|
26
40
|
'.notis',
|
|
27
41
|
'.git',
|
|
28
42
|
'dist',
|
|
29
|
-
'package-lock.json',
|
|
30
43
|
'tsconfig.tsbuildinfo',
|
|
31
44
|
'.DS_Store',
|
|
32
45
|
]);
|
|
46
|
+
const SCAFFOLD_COPY_EXCLUDES = new Set([
|
|
47
|
+
...SOURCE_COPY_EXCLUDES,
|
|
48
|
+
'coverage',
|
|
49
|
+
'.next',
|
|
50
|
+
'.turbo',
|
|
51
|
+
]);
|
|
33
52
|
let appConfigImportNonce = 0;
|
|
34
53
|
|
|
35
54
|
// ---------------------------------------------------------------------------
|
|
@@ -214,9 +233,160 @@ export function detectProjectWarnings(projectDir, appConfig = null) {
|
|
|
214
233
|
warnings.push('No database references declared in notis.config.ts.');
|
|
215
234
|
}
|
|
216
235
|
|
|
236
|
+
if (appConfig) {
|
|
237
|
+
try {
|
|
238
|
+
const listing = inspectListingReadiness(projectDir, appConfig);
|
|
239
|
+
warnings.push(...listing.warnings);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
warnings.push(error instanceof Error ? error.message : String(error));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
217
245
|
return warnings;
|
|
218
246
|
}
|
|
219
247
|
|
|
248
|
+
function safeKebab(value) {
|
|
249
|
+
return String(value || '')
|
|
250
|
+
.trim()
|
|
251
|
+
.toLowerCase()
|
|
252
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
253
|
+
.replace(/(^-|-$)+/g, '');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function jsStringLiteral(value) {
|
|
257
|
+
return `'${String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function normalizeCategories(categories = []) {
|
|
261
|
+
if (!Array.isArray(categories)) {
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
const allowed = new Set(NOTIS_APP_CATEGORIES);
|
|
265
|
+
const normalized = [];
|
|
266
|
+
for (const category of categories) {
|
|
267
|
+
if (typeof category !== 'string' || !category.trim()) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const trimmed = category.trim();
|
|
271
|
+
if (!allowed.has(trimmed)) {
|
|
272
|
+
throw usageError(
|
|
273
|
+
`Invalid Notis app category "${trimmed}". Use one of: ${NOTIS_APP_CATEGORIES.join(', ')}.`,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
if (!normalized.includes(trimmed)) {
|
|
277
|
+
normalized.push(trimmed);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return normalized;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function pngDimensions(buffer) {
|
|
284
|
+
const signature = '89504e470d0a1a0a';
|
|
285
|
+
if (!Buffer.isBuffer(buffer) || buffer.length < 24 || buffer.subarray(0, 8).toString('hex') !== signature) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
width: buffer.readUInt32BE(16),
|
|
290
|
+
height: buffer.readUInt32BE(20),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function imageAssetMeta(projectDir, relPath, { maxBytes, recommendedRatio = 1.6 }) {
|
|
295
|
+
const fullPath = join(projectDir, relPath);
|
|
296
|
+
const content = readFileSync(fullPath);
|
|
297
|
+
const dimensions = pngDimensions(content);
|
|
298
|
+
const ratio = dimensions?.height ? dimensions.width / dimensions.height : null;
|
|
299
|
+
const errors = [];
|
|
300
|
+
const warnings = [];
|
|
301
|
+
|
|
302
|
+
if (!relPath.toLowerCase().endsWith('.png')) {
|
|
303
|
+
errors.push(`${relPath} must be a PNG file.`);
|
|
304
|
+
}
|
|
305
|
+
if (content.length > maxBytes) {
|
|
306
|
+
errors.push(`${relPath} must be ${Math.round(maxBytes / 1024 / 1024)} MB or smaller.`);
|
|
307
|
+
}
|
|
308
|
+
if (ratio !== null && (ratio < 1.3 || ratio > 1.9)) {
|
|
309
|
+
errors.push(`${relPath} should be close to a 16:10 aspect ratio.`);
|
|
310
|
+
} else if (ratio !== null && Math.abs(ratio - recommendedRatio) > 0.08) {
|
|
311
|
+
warnings.push(`${relPath} is not 2000x1250; 16:10 PNG is recommended.`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
path: relPath,
|
|
316
|
+
content_type: 'image/png',
|
|
317
|
+
bytes: content.length,
|
|
318
|
+
width: dimensions?.width ?? null,
|
|
319
|
+
height: dimensions?.height ?? null,
|
|
320
|
+
errors,
|
|
321
|
+
warnings,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function discoverMetadataAssets(projectDir) {
|
|
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
|
+
const screenshots = [];
|
|
332
|
+
|
|
333
|
+
if (existsSync(metadataDir)) {
|
|
334
|
+
for (const entry of readdirSync(metadataDir, { withFileTypes: true })) {
|
|
335
|
+
if (!entry.isFile()) continue;
|
|
336
|
+
const match = /^screenshot-(\d+)\.png$/i.exec(entry.name);
|
|
337
|
+
if (!match) continue;
|
|
338
|
+
screenshots.push({
|
|
339
|
+
index: Number.parseInt(match[1], 10),
|
|
340
|
+
...imageAssetMeta(projectDir, join(METADATA_DIR, entry.name), {
|
|
341
|
+
maxBytes: 2 * 1024 * 1024,
|
|
342
|
+
}),
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
screenshots.sort((a, b) => a.index - b.index);
|
|
348
|
+
return {
|
|
349
|
+
cover,
|
|
350
|
+
screenshots: screenshots.slice(0, 6),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function inspectListingReadiness(projectDir, appConfig = null) {
|
|
355
|
+
const config = appConfig || {};
|
|
356
|
+
const warnings = [];
|
|
357
|
+
const errors = [];
|
|
358
|
+
const metadata = discoverMetadataAssets(projectDir);
|
|
359
|
+
const categories = normalizeCategories(config.categories || []);
|
|
360
|
+
|
|
361
|
+
if (!String(config.tagline || '').trim()) {
|
|
362
|
+
warnings.push('Listing tagline missing in notis.config.ts.');
|
|
363
|
+
}
|
|
364
|
+
if (categories.length === 0) {
|
|
365
|
+
warnings.push(`Listing category missing in notis.config.ts. Use one of: ${NOTIS_APP_CATEGORIES.join(', ')}.`);
|
|
366
|
+
}
|
|
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
|
+
if (metadata.screenshots.length < 3) {
|
|
374
|
+
warnings.push(`Listing screenshots: ${metadata.screenshots.length}/3 minimum in metadata/screenshot-N.png.`);
|
|
375
|
+
}
|
|
376
|
+
for (const screenshot of metadata.screenshots) {
|
|
377
|
+
errors.push(...screenshot.errors);
|
|
378
|
+
warnings.push(...screenshot.warnings);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
ready: errors.length === 0 && Boolean(config.tagline) && categories.length > 0 && Boolean(metadata.cover) && metadata.screenshots.length >= 1,
|
|
383
|
+
warnings,
|
|
384
|
+
errors,
|
|
385
|
+
metadata,
|
|
386
|
+
categories,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
220
390
|
// ---------------------------------------------------------------------------
|
|
221
391
|
// Build
|
|
222
392
|
// ---------------------------------------------------------------------------
|
|
@@ -409,6 +579,21 @@ function generateEntryFile(projectDir, routes) {
|
|
|
409
579
|
return entryPath;
|
|
410
580
|
}
|
|
411
581
|
|
|
582
|
+
export async function prepareArtifactBuild(projectDir) {
|
|
583
|
+
validateProjectBoundary(projectDir);
|
|
584
|
+
const appConfig = await loadAppConfig(projectDir);
|
|
585
|
+
const detectedRoutes = resolveConfiguredRoutes(appConfig, projectDir);
|
|
586
|
+
|
|
587
|
+
generateEntryFile(projectDir, detectedRoutes);
|
|
588
|
+
|
|
589
|
+
const manifest = generateManifest(appConfig, projectDir);
|
|
590
|
+
const manifestPath = join(projectDir, MANIFEST_FILE);
|
|
591
|
+
mkdirSync(dirname(manifestPath), { recursive: true });
|
|
592
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
593
|
+
|
|
594
|
+
return { appConfig, manifest, routes: detectedRoutes };
|
|
595
|
+
}
|
|
596
|
+
|
|
412
597
|
/**
|
|
413
598
|
* Generate the manifest from app config and build output.
|
|
414
599
|
*/
|
|
@@ -431,15 +616,52 @@ export function generateManifest(appConfig, projectDir) {
|
|
|
431
616
|
});
|
|
432
617
|
|
|
433
618
|
const databases = appConfig.databases || [];
|
|
619
|
+
const categories = normalizeCategories(appConfig.categories || []);
|
|
620
|
+
const metadata = discoverMetadataAssets(projectDir);
|
|
621
|
+
const appSlug = safeKebab(appConfig.name);
|
|
622
|
+
const displayTitle = appConfig.title || appConfig.displayName || appConfig.name;
|
|
623
|
+
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
|
+
screenshots: metadata.screenshots.map((screenshot) => ({
|
|
634
|
+
path: screenshot.path,
|
|
635
|
+
content_type: screenshot.content_type,
|
|
636
|
+
width: screenshot.width,
|
|
637
|
+
height: screenshot.height,
|
|
638
|
+
bytes: screenshot.bytes,
|
|
639
|
+
})),
|
|
640
|
+
};
|
|
434
641
|
|
|
435
642
|
return {
|
|
436
643
|
version: 1,
|
|
437
|
-
spec_version:
|
|
644
|
+
spec_version: 4,
|
|
438
645
|
app: {
|
|
439
|
-
name:
|
|
646
|
+
name: displayTitle,
|
|
647
|
+
slug: appSlug || null,
|
|
440
648
|
description: appConfig.description || null,
|
|
441
649
|
icon: appConfig.icon || null,
|
|
650
|
+
title: displayTitle,
|
|
651
|
+
tagline: appConfig.tagline || null,
|
|
652
|
+
categories,
|
|
653
|
+
author: appConfig.author || null,
|
|
654
|
+
version_notes: appConfig.versionNotes || appConfig.version_notes || null,
|
|
442
655
|
},
|
|
656
|
+
listing: {
|
|
657
|
+
title: displayTitle,
|
|
658
|
+
tagline: appConfig.tagline || null,
|
|
659
|
+
categories,
|
|
660
|
+
author: appConfig.author || null,
|
|
661
|
+
version_notes: appConfig.versionNotes || appConfig.version_notes || null,
|
|
662
|
+
media: listingMedia,
|
|
663
|
+
},
|
|
664
|
+
metadata: listingMedia,
|
|
443
665
|
routes,
|
|
444
666
|
bundle: {
|
|
445
667
|
js: 'bundle/app.js',
|
|
@@ -454,14 +676,7 @@ export function generateManifest(appConfig, projectDir) {
|
|
|
454
676
|
* Build the app bundle: generate entry file, run `vite build`, package into .notis/output/.
|
|
455
677
|
*/
|
|
456
678
|
export async function buildArtifact(projectDir) {
|
|
457
|
-
|
|
458
|
-
const appConfig = await loadAppConfig(projectDir);
|
|
459
|
-
|
|
460
|
-
// Auto-detect or use configured routes
|
|
461
|
-
const detectedRoutes = resolveConfiguredRoutes(appConfig, projectDir);
|
|
462
|
-
|
|
463
|
-
// Generate the entry file that re-exports all route page components
|
|
464
|
-
generateEntryFile(projectDir, detectedRoutes);
|
|
679
|
+
await prepareArtifactBuild(projectDir);
|
|
465
680
|
|
|
466
681
|
// Run Vite build
|
|
467
682
|
await runProjectScript({
|
|
@@ -477,14 +692,11 @@ export async function buildArtifact(projectDir) {
|
|
|
477
692
|
);
|
|
478
693
|
}
|
|
479
694
|
normalizeBundleStylesheets(projectDir);
|
|
695
|
+
copyMetadataAssets(projectDir);
|
|
480
696
|
|
|
481
697
|
validateArtifactBoundary(readArtifactFiles(projectDir));
|
|
482
698
|
|
|
483
|
-
|
|
484
|
-
const manifest = generateManifest(appConfig, projectDir);
|
|
485
|
-
const manifestPath = join(projectDir, MANIFEST_FILE);
|
|
486
|
-
mkdirSync(dirname(manifestPath), { recursive: true });
|
|
487
|
-
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
699
|
+
const manifest = readManifest(projectDir);
|
|
488
700
|
|
|
489
701
|
return { manifest, outputDir: join(projectDir, OUTPUT_DIR) };
|
|
490
702
|
}
|
|
@@ -500,6 +712,22 @@ export function readManifest(projectDir) {
|
|
|
500
712
|
return JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
501
713
|
}
|
|
502
714
|
|
|
715
|
+
function copyMetadataAssets(projectDir) {
|
|
716
|
+
const metadataDir = join(projectDir, METADATA_DIR);
|
|
717
|
+
if (!existsSync(metadataDir)) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const outputMetadataDir = join(projectDir, OUTPUT_DIR, METADATA_DIR);
|
|
721
|
+
mkdirSync(outputMetadataDir, { recursive: true });
|
|
722
|
+
for (const entry of readdirSync(metadataDir, { withFileTypes: true })) {
|
|
723
|
+
if (!entry.isFile()) continue;
|
|
724
|
+
if (entry.name !== 'cover.png' && !/^screenshot-\d+\.png$/i.test(entry.name)) {
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
cpSync(join(metadataDir, entry.name), join(outputMetadataDir, entry.name));
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
503
731
|
// ---------------------------------------------------------------------------
|
|
504
732
|
// Linking
|
|
505
733
|
// ---------------------------------------------------------------------------
|
|
@@ -530,24 +758,37 @@ export function requireLinkedAppId(projectDir, explicitAppId) {
|
|
|
530
758
|
/**
|
|
531
759
|
* Scaffold a new Notis app project from the SDK template.
|
|
532
760
|
*/
|
|
533
|
-
export function
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
761
|
+
export function loadScaffoldCatalog() {
|
|
762
|
+
if (existsSync(SCAFFOLD_CATALOG_FILE)) {
|
|
763
|
+
const parsed = JSON.parse(readFileSync(SCAFFOLD_CATALOG_FILE, 'utf-8'));
|
|
764
|
+
const scaffolds = Array.isArray(parsed?.scaffolds) ? parsed.scaffolds : parsed;
|
|
765
|
+
return Array.isArray(scaffolds)
|
|
766
|
+
? scaffolds.filter((entry) => entry && typeof entry.slug === 'string')
|
|
767
|
+
: [];
|
|
768
|
+
}
|
|
769
|
+
return loadMonorepoScaffoldCatalog();
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
export function scaffoldProject({ projectDir, appName, fromSlug = null }) {
|
|
773
|
+
const templateDir = fromSlug ? resolveScaffoldSourceDir(fromSlug) : join(CLI_ROOT, 'template');
|
|
538
774
|
|
|
539
775
|
if (!existsSync(templateDir)) {
|
|
776
|
+
if (fromSlug) {
|
|
777
|
+
const known = loadScaffoldCatalog().map((entry) => entry.slug).join(', ') || 'none';
|
|
778
|
+
throw usageError(`Unknown scaffold "${fromSlug}". Available scaffolds: ${known}.`);
|
|
779
|
+
}
|
|
540
780
|
throw usageError(`SDK template not found at ${templateDir}. Ensure @notis_ai/cli is installed correctly.`);
|
|
541
781
|
}
|
|
542
782
|
|
|
543
783
|
mkdirSync(projectDir, { recursive: true });
|
|
544
|
-
|
|
784
|
+
copyScaffoldSource(templateDir, projectDir);
|
|
545
785
|
|
|
546
786
|
// Update package.json with the app name
|
|
547
787
|
const pkgPath = join(projectDir, 'package.json');
|
|
548
788
|
if (existsSync(pkgPath)) {
|
|
549
789
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
550
790
|
pkg.name = appName.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
|
|
791
|
+
ensureScaffoldLocalSdk(projectDir, pkg);
|
|
551
792
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
552
793
|
}
|
|
553
794
|
|
|
@@ -555,13 +796,141 @@ export function scaffoldProject({ projectDir, appName }) {
|
|
|
555
796
|
const configPath = join(projectDir, 'notis.config.ts');
|
|
556
797
|
if (existsSync(configPath)) {
|
|
557
798
|
let config = readFileSync(configPath, 'utf-8');
|
|
558
|
-
|
|
799
|
+
const displayName = jsStringLiteral(appName);
|
|
800
|
+
const slugName = jsStringLiteral(safeKebab(appName) || 'my-notis-app');
|
|
801
|
+
if (fromSlug) {
|
|
802
|
+
if (/name\s*:/.test(config)) {
|
|
803
|
+
config = config.replace(/name\s*:\s*(['"`])[\s\S]*?\1/, `name: ${slugName}`);
|
|
804
|
+
}
|
|
805
|
+
if (/title\s*:/.test(config)) {
|
|
806
|
+
config = config.replace(/title\s*:\s*(['"`])[\s\S]*?\1/, `title: ${displayName}`);
|
|
807
|
+
} else {
|
|
808
|
+
config = config.replace(/name\s*:\s*(['"`])[\s\S]*?\1/, `name: ${slugName},\n title: ${displayName}`);
|
|
809
|
+
}
|
|
810
|
+
} else {
|
|
811
|
+
if (/name\s*:/.test(config)) {
|
|
812
|
+
config = config.replace(/name\s*:\s*(['"`])[\s\S]*?\1/, `name: ${slugName}`);
|
|
813
|
+
}
|
|
814
|
+
if (/title\s*:/.test(config)) {
|
|
815
|
+
config = config.replace(/title\s*:\s*(['"`])[\s\S]*?\1/, `title: ${displayName}`);
|
|
816
|
+
} else {
|
|
817
|
+
config = config.replace(/'My Notis App'/, displayName);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
559
820
|
writeFileSync(configPath, config);
|
|
560
821
|
}
|
|
561
822
|
|
|
562
823
|
return { projectDir };
|
|
563
824
|
}
|
|
564
825
|
|
|
826
|
+
function ensureScaffoldLocalSdk(projectDir, pkg) {
|
|
827
|
+
let shouldInstallLocalSdk = false;
|
|
828
|
+
for (const dependencyGroup of ['dependencies', 'devDependencies']) {
|
|
829
|
+
const dependencyValue = pkg[dependencyGroup]?.['@notis/sdk'];
|
|
830
|
+
if (typeof dependencyValue === 'string' && dependencyValue.startsWith('file:')) {
|
|
831
|
+
pkg[dependencyGroup]['@notis/sdk'] = 'file:./packages/sdk';
|
|
832
|
+
shouldInstallLocalSdk = true;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (!shouldInstallLocalSdk) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const localSdkDir = join(projectDir, 'packages', 'sdk');
|
|
841
|
+
if (existsSync(join(localSdkDir, 'package.json'))) {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (!existsSync(join(TEMPLATE_SDK_DIR, 'package.json'))) {
|
|
845
|
+
throw usageError(`SDK template not found at ${TEMPLATE_SDK_DIR}. Ensure @notis_ai/cli is installed correctly.`);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
mkdirSync(dirname(localSdkDir), { recursive: true });
|
|
849
|
+
cpSync(TEMPLATE_SDK_DIR, localSdkDir, { recursive: true, dereference: true });
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function resolveScaffoldSourceDir(fromSlug) {
|
|
853
|
+
const bundledDir = join(SCAFFOLD_SOURCE_DIR, fromSlug);
|
|
854
|
+
if (existsSync(bundledDir)) {
|
|
855
|
+
return bundledDir;
|
|
856
|
+
}
|
|
857
|
+
const monorepoDir = join(MONOREPO_APPS_DIR, fromSlug);
|
|
858
|
+
if (existsSync(join(monorepoDir, 'notis.config.ts'))) {
|
|
859
|
+
return monorepoDir;
|
|
860
|
+
}
|
|
861
|
+
return bundledDir;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function loadMonorepoScaffoldCatalog() {
|
|
865
|
+
if (!existsSync(MONOREPO_APPS_DIR)) {
|
|
866
|
+
return [];
|
|
867
|
+
}
|
|
868
|
+
const scaffolds = [];
|
|
869
|
+
for (const entry of readdirSync(MONOREPO_APPS_DIR, { withFileTypes: true })) {
|
|
870
|
+
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name.startsWith('_')) {
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
const configPath = join(MONOREPO_APPS_DIR, entry.name, 'notis.config.ts');
|
|
874
|
+
if (!existsSync(configPath)) {
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
const configSource = readFileSync(configPath, 'utf-8');
|
|
878
|
+
const description = readTsStringProperty(configSource, 'description') || '';
|
|
879
|
+
scaffolds.push({
|
|
880
|
+
slug: entry.name,
|
|
881
|
+
name: readTsStringProperty(configSource, 'title') || readTsStringProperty(configSource, 'name') || entry.name,
|
|
882
|
+
description,
|
|
883
|
+
icon: readTsStringProperty(configSource, 'icon') || 'phosphor:squares-four',
|
|
884
|
+
categories: readTsStringArrayProperty(configSource, 'categories'),
|
|
885
|
+
tagline: readTsStringProperty(configSource, 'tagline') || description,
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
return scaffolds.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function readTsStringProperty(source, propertyName) {
|
|
892
|
+
const match = source.match(new RegExp(`\\b${propertyName}\\s*:\\s*(['"\`])([\\s\\S]*?)\\1`));
|
|
893
|
+
return match ? match[2] : null;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
function readTsStringArrayProperty(source, propertyName) {
|
|
897
|
+
const match = source.match(new RegExp(`\\b${propertyName}\\s*:\\s*\\[([\\s\\S]*?)\\]`));
|
|
898
|
+
if (!match) {
|
|
899
|
+
return [];
|
|
900
|
+
}
|
|
901
|
+
return Array.from(match[1].matchAll(/(['"`])([\s\S]*?)\1/g), (entry) => entry[2].trim()).filter(Boolean);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function copyScaffoldSource(sourceDir, targetDir) {
|
|
905
|
+
function shouldCopy(path) {
|
|
906
|
+
const name = path.split(/[\\/]/).pop();
|
|
907
|
+
if (!name) return true;
|
|
908
|
+
return !SCAFFOLD_COPY_EXCLUDES.has(name)
|
|
909
|
+
&& !name.startsWith('.env')
|
|
910
|
+
&& !/\.(test|spec)\.[cm]?[jt]sx?$/i.test(name);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function walk(src, dest) {
|
|
914
|
+
if (!shouldCopy(src)) {
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
const stat = statSync(src);
|
|
918
|
+
if (stat.isDirectory()) {
|
|
919
|
+
mkdirSync(dest, { recursive: true });
|
|
920
|
+
for (const entry of readdirSync(src)) {
|
|
921
|
+
walk(join(src, entry), join(dest, entry));
|
|
922
|
+
}
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
if (stat.isFile()) {
|
|
926
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
927
|
+
cpSync(src, dest);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
walk(sourceDir, targetDir);
|
|
932
|
+
}
|
|
933
|
+
|
|
565
934
|
// ---------------------------------------------------------------------------
|
|
566
935
|
// Deploy helpers
|
|
567
936
|
// ---------------------------------------------------------------------------
|
|
@@ -700,8 +1069,14 @@ export async function pullAppSource({
|
|
|
700
1069
|
const versionMatch = /-v(\d+)\.tar\.gz/i.exec(contentDisposition);
|
|
701
1070
|
const pulledVersion = versionMatch ? Number.parseInt(versionMatch[1], 10) : null;
|
|
702
1071
|
extractTarGz(Buffer.from(await response.arrayBuffer()), targetDir);
|
|
1072
|
+
const linkedVersion = pulledVersion || (
|
|
1073
|
+
String(version || 'latest') === 'latest'
|
|
1074
|
+
? undefined
|
|
1075
|
+
: Number.parseInt(String(version), 10)
|
|
1076
|
+
);
|
|
703
1077
|
writeLinkedState(targetDir, {
|
|
704
1078
|
app_id: appId,
|
|
1079
|
+
...(Number.isFinite(linkedVersion) ? { version: linkedVersion } : {}),
|
|
705
1080
|
linked_at: new Date().toISOString(),
|
|
706
1081
|
});
|
|
707
1082
|
|
|
@@ -898,6 +1273,9 @@ const CONTENT_TYPE_MAP = {
|
|
|
898
1273
|
'.css': 'text/css',
|
|
899
1274
|
'.json': 'application/json',
|
|
900
1275
|
'.html': 'text/html',
|
|
1276
|
+
'.png': 'image/png',
|
|
1277
|
+
'.jpg': 'image/jpeg',
|
|
1278
|
+
'.jpeg': 'image/jpeg',
|
|
901
1279
|
};
|
|
902
1280
|
|
|
903
1281
|
/**
|
|
@@ -952,6 +1330,8 @@ export async function directDeploy(projectDir, appId) {
|
|
|
952
1330
|
version: newVersion,
|
|
953
1331
|
storage_bucket: bucket,
|
|
954
1332
|
storage_prefix: storagePrefix,
|
|
1333
|
+
source_storage_bucket: 'app-source',
|
|
1334
|
+
source_storage_prefix: storagePrefix,
|
|
955
1335
|
};
|
|
956
1336
|
await updateAppVersion(supabaseUrl, supabaseKey, appId, newVersion, deployManifest);
|
|
957
1337
|
|
package/src/runtime/profiles.js
CHANGED
|
@@ -205,22 +205,28 @@ export function resolveRuntimeProfile(globalOptions = {}, { requireAuth = true }
|
|
|
205
205
|
exitCode: EXIT_CODES.auth,
|
|
206
206
|
hints: [
|
|
207
207
|
{
|
|
208
|
-
command: '
|
|
209
|
-
reason: '
|
|
208
|
+
command: 'Open the Notis desktop app and sign in',
|
|
209
|
+
reason: 'Set NOTIS_JWT or sign in through the Notis desktop app',
|
|
210
210
|
},
|
|
211
211
|
],
|
|
212
212
|
});
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
// An explicit NOTIS_JWT is a complete credential override: use it verbatim and do
|
|
216
|
+
// NOT attempt a stored-profile dev_portal token refresh (whose refresh_token may be
|
|
217
|
+
// stale or belong to a different session). Without this, a long-lived shell with an
|
|
218
|
+
// expired profile refresh_token fails every command with invalid_grant even though
|
|
219
|
+
// the supplied NOTIS_JWT is valid.
|
|
220
|
+
const usingEnvJwt = Boolean(process.env.NOTIS_JWT);
|
|
215
221
|
return {
|
|
216
222
|
config,
|
|
217
223
|
profileName,
|
|
218
224
|
apiBase,
|
|
219
225
|
jwt,
|
|
220
|
-
authMode: profile.auth_mode,
|
|
221
|
-
refreshToken: profile.refresh_token,
|
|
222
|
-
accessExpiresAt: profile.access_expires_at,
|
|
223
|
-
refreshExpiresAt: profile.refresh_expires_at,
|
|
226
|
+
authMode: usingEnvJwt ? 'jwt' : profile.auth_mode,
|
|
227
|
+
refreshToken: usingEnvJwt ? undefined : profile.refresh_token,
|
|
228
|
+
accessExpiresAt: usingEnvJwt ? undefined : profile.access_expires_at,
|
|
229
|
+
refreshExpiresAt: usingEnvJwt ? undefined : profile.refresh_expires_at,
|
|
224
230
|
agentMode,
|
|
225
231
|
nonInteractive,
|
|
226
232
|
outputMode,
|