@notis_ai/cli 0.2.0-beta.32.1 → 0.2.0-beta.41.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 +103 -163
- 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 +7 -3
- package/skills/notis-apps/SKILL.md +162 -0
- package/skills/notis-apps/cli.md +208 -0
- package/skills/notis-cli/SKILL.md +258 -0
- package/skills/notis-query/cli.md +40 -0
- package/src/cli.js +11 -2
- package/src/command-specs/apps.js +211 -46
- package/src/command-specs/helpers.js +2 -60
- package/src/command-specs/index.js +0 -6
- package/src/command-specs/meta.js +7 -6
- package/src/command-specs/tools.js +101 -35
- package/src/runtime/app-dev-server.js +32 -4
- package/src/runtime/app-platform.js +404 -24
- package/src/runtime/profiles.js +2 -2
- package/src/runtime/transport.js +131 -19
- 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
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { accessSync, constants as fsConstants, createReadStream, existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { basename } from 'node:path';
|
|
2
4
|
import { usageError } from '../runtime/errors.js';
|
|
3
5
|
import {
|
|
4
6
|
fetchToolkits,
|
|
@@ -42,6 +44,89 @@ const LONG_RUNNING_TOOL_NAMES = new Set([
|
|
|
42
44
|
'notis-default-edit_image_gemini',
|
|
43
45
|
]);
|
|
44
46
|
|
|
47
|
+
const EXTENSION_CONTENT_TYPES = new Map([
|
|
48
|
+
['.pdf', 'application/pdf'],
|
|
49
|
+
['.png', 'image/png'],
|
|
50
|
+
['.jpg', 'image/jpeg'],
|
|
51
|
+
['.jpeg', 'image/jpeg'],
|
|
52
|
+
['.gif', 'image/gif'],
|
|
53
|
+
['.webp', 'image/webp'],
|
|
54
|
+
['.txt', 'text/plain'],
|
|
55
|
+
['.csv', 'text/csv'],
|
|
56
|
+
['.json', 'application/json'],
|
|
57
|
+
['.md', 'text/markdown'],
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
function guessContentType(filePath) {
|
|
61
|
+
const lower = filePath.toLowerCase();
|
|
62
|
+
const dotIndex = lower.lastIndexOf('.');
|
|
63
|
+
if (dotIndex === -1) return 'application/octet-stream';
|
|
64
|
+
return EXTENSION_CONTENT_TYPES.get(lower.slice(dotIndex)) || 'application/octet-stream';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function hashFileSha256(localPath) {
|
|
68
|
+
const hash = createHash('sha256');
|
|
69
|
+
for await (const chunk of createReadStream(localPath)) {
|
|
70
|
+
hash.update(chunk);
|
|
71
|
+
}
|
|
72
|
+
return hash.digest('hex');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function parseFileBindingSpec(spec, index) {
|
|
76
|
+
if (typeof spec !== 'string' || !spec.trim()) {
|
|
77
|
+
throw usageError('--file must be formatted as <argument-path>=<local-path>');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const equalsIndex = spec.indexOf('=');
|
|
81
|
+
if (equalsIndex <= 0 || equalsIndex === spec.length - 1) {
|
|
82
|
+
throw usageError('--file must be formatted as <argument-path>=<local-path>');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const argumentPath = spec.slice(0, equalsIndex).trim();
|
|
86
|
+
const localPath = spec.slice(equalsIndex + 1).trim();
|
|
87
|
+
if (!argumentPath) {
|
|
88
|
+
throw usageError('--file argument path is required');
|
|
89
|
+
}
|
|
90
|
+
if (!localPath) {
|
|
91
|
+
throw usageError('--file local path is required');
|
|
92
|
+
}
|
|
93
|
+
if (!existsSync(localPath)) {
|
|
94
|
+
throw usageError(`File not found: ${localPath}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let stats;
|
|
98
|
+
try {
|
|
99
|
+
stats = statSync(localPath);
|
|
100
|
+
accessSync(localPath, fsConstants.R_OK);
|
|
101
|
+
} catch {
|
|
102
|
+
throw usageError(`File is not readable: ${localPath}`);
|
|
103
|
+
}
|
|
104
|
+
if (!stats.isFile()) {
|
|
105
|
+
throw usageError(`File is not a regular file: ${localPath}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const sha256 = await hashFileSha256(localPath);
|
|
109
|
+
const fileBasename = basename(localPath);
|
|
110
|
+
const fieldName = `file_${index}`;
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
argument_path: argumentPath,
|
|
114
|
+
field_name: fieldName,
|
|
115
|
+
basename: fileBasename,
|
|
116
|
+
size: stats.size,
|
|
117
|
+
sha256,
|
|
118
|
+
contentType: guessContentType(fileBasename),
|
|
119
|
+
localPath,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function parseFileBindings(rawFileOptions) {
|
|
124
|
+
const values = Array.isArray(rawFileOptions)
|
|
125
|
+
? rawFileOptions
|
|
126
|
+
: (rawFileOptions ? [rawFileOptions] : []);
|
|
127
|
+
return Promise.all(values.map((value, index) => parseFileBindingSpec(value, index)));
|
|
128
|
+
}
|
|
129
|
+
|
|
45
130
|
function ensureLongRunningToolTimeout(ctx, toolNames) {
|
|
46
131
|
const names = Array.isArray(toolNames) ? toolNames : [toolNames];
|
|
47
132
|
if (!names.some((name) => LONG_RUNNING_TOOL_NAMES.has(name))) {
|
|
@@ -124,6 +209,7 @@ async function toolsDescribeHandler(ctx) {
|
|
|
124
209
|
async function toolsExecHandler(ctx) {
|
|
125
210
|
ensureLongRunningToolTimeout(ctx, ctx.args.toolName);
|
|
126
211
|
const toolkits = normalizeToolkits(ctx.options.toolkits);
|
|
212
|
+
const fileBindings = await parseFileBindings(ctx.options.file);
|
|
127
213
|
|
|
128
214
|
if (ctx.options.getSchema) {
|
|
129
215
|
const tool = await fetchToolSchema(ctx.runtime, ctx.args.toolName, ctx.options.toolkits);
|
|
@@ -144,6 +230,9 @@ async function toolsExecHandler(ctx) {
|
|
|
144
230
|
: parseMaybeJson(rawArguments, 'arguments') || {};
|
|
145
231
|
|
|
146
232
|
if (ctx.options.dryRun) {
|
|
233
|
+
if (fileBindings.length) {
|
|
234
|
+
throw usageError('--file is only supported when executing a tool, not with --dry-run');
|
|
235
|
+
}
|
|
147
236
|
const tool = await fetchToolSchema(ctx.runtime, ctx.args.toolName, ctx.options.toolkits);
|
|
148
237
|
const errors = validateArguments(tool.parameters, args);
|
|
149
238
|
|
|
@@ -171,37 +260,6 @@ async function toolsExecHandler(ctx) {
|
|
|
171
260
|
|
|
172
261
|
const idempotencyKey = nextIdempotencyKey(ctx.globalOptions);
|
|
173
262
|
|
|
174
|
-
if (ctx.options.watch) {
|
|
175
|
-
const intervalSec = Math.max(1, parseInt(ctx.options.watch, 10));
|
|
176
|
-
const intervalMs = intervalSec * 1000;
|
|
177
|
-
process.on('SIGINT', () => process.exit(0));
|
|
178
|
-
|
|
179
|
-
while (true) {
|
|
180
|
-
try {
|
|
181
|
-
const result = await runToolCommand({
|
|
182
|
-
runtime: ctx.runtime,
|
|
183
|
-
toolName: 'notis_execute_tool',
|
|
184
|
-
arguments_: {
|
|
185
|
-
tool_name: ctx.args.toolName,
|
|
186
|
-
arguments: args,
|
|
187
|
-
...(toolkits.length ? { toolkits } : {}),
|
|
188
|
-
},
|
|
189
|
-
mutating: true,
|
|
190
|
-
idempotencyKey: nextIdempotencyKey(ctx.globalOptions),
|
|
191
|
-
});
|
|
192
|
-
ctx.output.emitSuccess({
|
|
193
|
-
command: ctx.spec.command_path.join(' '),
|
|
194
|
-
data: result.payload,
|
|
195
|
-
humanSummary: `[${new Date().toISOString()}] ${ctx.args.toolName}`,
|
|
196
|
-
renderHuman: () => JSON.stringify(result.payload, null, 2),
|
|
197
|
-
});
|
|
198
|
-
} catch (error) {
|
|
199
|
-
process.stderr.write(`[${new Date().toISOString()}] Error: ${error.message}\n`);
|
|
200
|
-
}
|
|
201
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
263
|
const result = await runToolCommand({
|
|
206
264
|
runtime: ctx.runtime,
|
|
207
265
|
toolName: 'notis_execute_tool',
|
|
@@ -212,6 +270,7 @@ async function toolsExecHandler(ctx) {
|
|
|
212
270
|
},
|
|
213
271
|
mutating: true,
|
|
214
272
|
idempotencyKey,
|
|
273
|
+
fileBindings,
|
|
215
274
|
});
|
|
216
275
|
|
|
217
276
|
return ctx.output.emitSuccess({
|
|
@@ -224,6 +283,10 @@ async function toolsExecHandler(ctx) {
|
|
|
224
283
|
}
|
|
225
284
|
|
|
226
285
|
async function toolsExecParallelHandler(ctx) {
|
|
286
|
+
const parallelFileBindings = await parseFileBindings(ctx.options.file);
|
|
287
|
+
if (parallelFileBindings.length) {
|
|
288
|
+
throw usageError('--file is supported by `notis tools exec` only; exec-parallel accepts JSON calls without file uploads.');
|
|
289
|
+
}
|
|
227
290
|
const calls = parseMaybeJson(ctx.args.calls, 'calls');
|
|
228
291
|
if (!Array.isArray(calls) || !calls.length) {
|
|
229
292
|
throw usageError('calls must be a non-empty JSON array of {tool_name, arguments} objects');
|
|
@@ -357,19 +420,20 @@ export const toolsCommandSpecs = [
|
|
|
357
420
|
arguments: [{ token: '<tool-name>', description: 'Tool name returned by `notis tools search`.' }],
|
|
358
421
|
options: [
|
|
359
422
|
{ flags: '--arguments <json>', description: 'JSON object, @file path, or - for stdin.' },
|
|
423
|
+
{ flags: '--file <argument-path=local-path>', description: 'Upload a local file into a file-uploadable tool argument. Repeatable.', collect: true },
|
|
360
424
|
{ flags: '--get-schema', description: 'Display the tool parameter schema without executing.' },
|
|
361
425
|
{ flags: '--dry-run', description: 'Validate arguments against the tool schema without executing.' },
|
|
362
|
-
{ flags: '--watch <seconds>', description: 'Re-execute on an interval and stream results.' },
|
|
363
426
|
{ flags: '--toolkits <csv-or-json>', description: 'Optional toolkit namespace(s) to use when resolving the tool.' },
|
|
364
427
|
],
|
|
365
428
|
},
|
|
366
429
|
examples: [
|
|
367
430
|
'notis tools exec notis-default-query --arguments \'{"database_slug":"tasks","query":{}}\'',
|
|
431
|
+
'notis tools exec notis-default-get_database --arguments \'{"database_slug":"tasks"}\'',
|
|
368
432
|
'notis tools exec notis-default-query --get-schema',
|
|
369
433
|
'notis tools exec notis-default-query --dry-run --arguments \'{"database_slug":"tasks","query":{}}\'',
|
|
370
434
|
'notis tools exec notis-default-query --arguments @query.json',
|
|
371
435
|
'notis tools exec notis-default-query --arguments - < query.json',
|
|
372
|
-
'notis tools exec
|
|
436
|
+
'notis tools exec composio-dropbox-default-upload_file --arguments \'{"path":"/target/in/dropbox.pdf"}\' --file content=./Invoice.pdf',
|
|
373
437
|
],
|
|
374
438
|
output_schema: 'Returns the raw tool execution payload.',
|
|
375
439
|
mutates: true,
|
|
@@ -386,7 +450,9 @@ export const toolsCommandSpecs = [
|
|
|
386
450
|
arguments: [
|
|
387
451
|
{ token: '<calls>', description: 'JSON array of {tool_name, arguments} objects.' },
|
|
388
452
|
],
|
|
389
|
-
options: [
|
|
453
|
+
options: [
|
|
454
|
+
{ flags: '--file <argument-path=local-path>', description: 'Unsupported for exec-parallel; use tools exec for file uploads.', collect: true },
|
|
455
|
+
],
|
|
390
456
|
},
|
|
391
457
|
examples: [
|
|
392
458
|
'notis tools exec-parallel \'[{"tool_name":"notis-default-query","arguments":{"database_slug":"tasks","query":{}}},{"tool_name":"notis-default-list_databases","arguments":{}}]\'',
|
|
@@ -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'], {
|