@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.
Files changed (115) hide show
  1. package/README.md +103 -163
  2. package/dist/scaffolds/notis-database/app/globals.css +44 -0
  3. package/dist/scaffolds/notis-database/app/layout.tsx +6 -0
  4. package/dist/scaffolds/notis-database/app/page.tsx +1091 -0
  5. package/dist/scaffolds/notis-database/components/ui/badge.tsx +28 -0
  6. package/dist/scaffolds/notis-database/components/ui/button.tsx +53 -0
  7. package/dist/scaffolds/notis-database/components/ui/card.tsx +56 -0
  8. package/dist/scaffolds/notis-database/components/ui/table.tsx +120 -0
  9. package/dist/scaffolds/notis-database/components.json +20 -0
  10. package/dist/scaffolds/notis-database/index.html +12 -0
  11. package/dist/scaffolds/notis-database/lib/types.ts +134 -0
  12. package/dist/scaffolds/notis-database/lib/utils.ts +6 -0
  13. package/dist/scaffolds/notis-database/metadata/cover.png +0 -0
  14. package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
  15. package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
  16. package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
  17. package/dist/scaffolds/notis-database/notis.config.ts +27 -0
  18. package/dist/scaffolds/notis-database/package.json +31 -0
  19. package/dist/scaffolds/notis-database/postcss.config.mjs +8 -0
  20. package/dist/scaffolds/notis-database/src/dev-main.tsx +23 -0
  21. package/dist/scaffolds/notis-database/src/mock-runtime.ts +557 -0
  22. package/dist/scaffolds/notis-database/tailwind.config.ts +59 -0
  23. package/dist/scaffolds/notis-database/tsconfig.json +23 -0
  24. package/dist/scaffolds/notis-database/vite.config.ts +22 -0
  25. package/dist/scaffolds/notis-notes/app/globals.css +3 -0
  26. package/dist/scaffolds/notis-notes/app/layout.tsx +6 -0
  27. package/dist/scaffolds/notis-notes/app/page.tsx +2168 -0
  28. package/dist/scaffolds/notis-notes/components/ui/badge.tsx +28 -0
  29. package/dist/scaffolds/notis-notes/components/ui/button.tsx +53 -0
  30. package/dist/scaffolds/notis-notes/components/ui/card.tsx +56 -0
  31. package/dist/scaffolds/notis-notes/components.json +20 -0
  32. package/dist/scaffolds/notis-notes/lib/utils.ts +6 -0
  33. package/dist/scaffolds/notis-notes/metadata/cover.png +0 -0
  34. package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
  35. package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
  36. package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
  37. package/dist/scaffolds/notis-notes/notis.config.ts +36 -0
  38. package/dist/scaffolds/notis-notes/package.json +31 -0
  39. package/dist/scaffolds/notis-notes/postcss.config.mjs +8 -0
  40. package/dist/scaffolds/notis-notes/tailwind.config.ts +58 -0
  41. package/dist/scaffolds/notis-notes/tsconfig.json +23 -0
  42. package/dist/scaffolds/notis-notes/vite.config.ts +10 -0
  43. package/dist/scaffolds/notis-random/README.md +33 -0
  44. package/dist/scaffolds/notis-random/app/globals.css +11 -0
  45. package/dist/scaffolds/notis-random/app/history/page.tsx +66 -0
  46. package/dist/scaffolds/notis-random/app/layout.tsx +7 -0
  47. package/dist/scaffolds/notis-random/app/page.tsx +222 -0
  48. package/dist/scaffolds/notis-random/index.html +12 -0
  49. package/dist/scaffolds/notis-random/lib/notis-tools.ts +109 -0
  50. package/dist/scaffolds/notis-random/lib/rng.ts +42 -0
  51. package/dist/scaffolds/notis-random/lib/roll-record.ts +102 -0
  52. package/dist/scaffolds/notis-random/lib/utils.ts +25 -0
  53. package/dist/scaffolds/notis-random/metadata/cover.png +0 -0
  54. package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
  55. package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
  56. package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
  57. package/dist/scaffolds/notis-random/notis.config.ts +43 -0
  58. package/dist/scaffolds/notis-random/package.json +32 -0
  59. package/dist/scaffolds/notis-random/postcss.config.mjs +6 -0
  60. package/dist/scaffolds/notis-random/src/dev-main.tsx +70 -0
  61. package/dist/scaffolds/notis-random/src/mock-runtime.ts +128 -0
  62. package/dist/scaffolds/notis-random/tailwind.config.ts +43 -0
  63. package/dist/scaffolds/notis-random/tsconfig.json +23 -0
  64. package/dist/scaffolds/notis-random/vite.config.ts +11 -0
  65. package/dist/scaffolds.json +36 -0
  66. package/package.json +7 -3
  67. package/skills/notis-apps/SKILL.md +162 -0
  68. package/skills/notis-apps/cli.md +208 -0
  69. package/skills/notis-cli/SKILL.md +258 -0
  70. package/skills/notis-query/cli.md +40 -0
  71. package/src/cli.js +11 -2
  72. package/src/command-specs/apps.js +211 -46
  73. package/src/command-specs/helpers.js +2 -60
  74. package/src/command-specs/index.js +0 -6
  75. package/src/command-specs/meta.js +7 -6
  76. package/src/command-specs/tools.js +101 -35
  77. package/src/runtime/app-dev-server.js +32 -4
  78. package/src/runtime/app-platform.js +404 -24
  79. package/src/runtime/profiles.js +2 -2
  80. package/src/runtime/transport.js +131 -19
  81. package/template/.harness/index.html.tmpl +1 -50
  82. package/template/app/page.tsx +41 -6
  83. package/template/metadata/cover.png +0 -0
  84. package/template/metadata/screenshot-1.png +0 -0
  85. package/template/metadata/screenshot-2.png +0 -0
  86. package/template/metadata/screenshot-3.png +0 -0
  87. package/template/notis.config.ts +10 -6
  88. package/template/package.json +2 -2
  89. package/template/packages/{notis-sdk → sdk}/package.json +6 -0
  90. package/template/packages/{notis-sdk → sdk}/src/components/MultiSelectActionBar.tsx +2 -1
  91. package/template/packages/{notis-sdk → sdk}/src/components/MultiSelectCheckbox.tsx +2 -2
  92. package/template/packages/{notis-sdk → sdk}/src/components/MultiSelectDragOverlay.tsx +2 -2
  93. package/template/packages/{notis-sdk → sdk}/src/config.ts +1 -1
  94. package/template/packages/{notis-sdk → sdk}/src/hooks/useDatabase.ts +1 -13
  95. package/template/packages/{notis-sdk → sdk}/src/hooks/useMultiSelect.ts +4 -3
  96. package/template/packages/{notis-sdk → sdk}/src/hooks/useNotis.ts +4 -3
  97. package/template/packages/{notis-sdk → sdk}/src/hooks/useNotisNavigation.ts +3 -3
  98. package/template/packages/{notis-sdk → sdk}/src/hooks/useTool.ts +22 -7
  99. package/template/packages/{notis-sdk → sdk}/src/hooks/useUpsertDocument.ts +0 -8
  100. package/template/packages/{notis-sdk → sdk}/src/index.ts +2 -15
  101. package/template/packages/{notis-sdk → sdk}/src/provider.tsx +1 -1
  102. package/template/packages/{notis-sdk → sdk}/src/runtime.ts +5 -26
  103. package/src/command-specs/auth.js +0 -178
  104. package/src/command-specs/db.js +0 -163
  105. package/template/packages/notis-sdk/src/helpers.ts +0 -131
  106. package/template/packages/notis-sdk/src/hooks/useAppState.ts +0 -50
  107. package/template/packages/notis-sdk/src/hooks/useCollectionItem.ts +0 -58
  108. package/template/packages/notis-sdk/src/hooks/useDocument.ts +0 -61
  109. /package/template/packages/{notis-sdk → sdk}/src/hooks/useBackend.ts +0 -0
  110. /package/template/packages/{notis-sdk → sdk}/src/hooks/useTools.ts +0 -0
  111. /package/template/packages/{notis-sdk → sdk}/src/hooks/useTopBarSearch.ts +0 -0
  112. /package/template/packages/{notis-sdk → sdk}/src/styles.css +0 -0
  113. /package/template/packages/{notis-sdk → sdk}/src/ui.ts +0 -0
  114. /package/template/packages/{notis-sdk → sdk}/src/vite.ts +0 -0
  115. /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 notis-default-query --watch 10 --arguments \'{"database_slug":"tasks","query":{}}\'',
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', 'notis-sdk', 'package.json'),
109
- join(CLI_ROOT, 'template', 'packages', 'notis-sdk', 'package.json'),
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'], {