@igoruehara/canvas-flow 0.1.11 → 0.1.12

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 (2) hide show
  1. package/bin/canvas-flow.js +120 -41
  2. package/package.json +1 -1
@@ -835,6 +835,70 @@ function sleep(ms) {
835
835
  return new Promise((resolve) => setTimeout(resolve, ms));
836
836
  }
837
837
 
838
+ function createStartupProgress() {
839
+ const frames = ['-', '\\', '|', '/'];
840
+ const useTty = Boolean(process.stdout.isTTY && !process.env.CI);
841
+ let frameIndex = 0;
842
+ let lastLength = 0;
843
+ let interval;
844
+ let percent = 0;
845
+ let message = 'starting';
846
+
847
+ const line = () => `${frames[frameIndex]} Canvas Flow startup ${String(percent).padStart(3, ' ')}% - ${message}`;
848
+ const clearLine = () => {
849
+ if (!useTty || !lastLength) return;
850
+ process.stdout.write(`\r${' '.repeat(lastLength)}\r`);
851
+ lastLength = 0;
852
+ };
853
+ const render = () => {
854
+ if (!useTty) return;
855
+ const text = line();
856
+ const padded = text.padEnd(lastLength, ' ');
857
+ lastLength = Math.max(lastLength, text.length);
858
+ process.stdout.write(`\r${padded}`);
859
+ };
860
+ const ensureInterval = () => {
861
+ if (!useTty || interval) return;
862
+ interval = setInterval(() => {
863
+ frameIndex = (frameIndex + 1) % frames.length;
864
+ render();
865
+ }, 140);
866
+ if (typeof interval.unref === 'function') interval.unref();
867
+ };
868
+ const stopInterval = () => {
869
+ if (!interval) return;
870
+ clearInterval(interval);
871
+ interval = undefined;
872
+ };
873
+
874
+ return {
875
+ update(nextPercent, nextMessage) {
876
+ percent = Math.max(percent, Math.min(99, Number(nextPercent) || percent));
877
+ message = nextMessage || message;
878
+ ensureInterval();
879
+ if (useTty) render();
880
+ else console.log(`Canvas Flow startup ${percent}% - ${message}`);
881
+ },
882
+ log(text) {
883
+ clearLine();
884
+ console.log(text);
885
+ render();
886
+ },
887
+ done(text) {
888
+ percent = 100;
889
+ message = 'ready';
890
+ stopInterval();
891
+ clearLine();
892
+ console.log(text || 'Canvas Flow ready (100%)');
893
+ },
894
+ fail(text) {
895
+ stopInterval();
896
+ clearLine();
897
+ if (text) console.log(text);
898
+ },
899
+ };
900
+ }
901
+
838
902
  async function checkHttpOk(url, timeoutMs = 1200) {
839
903
  const controller = new AbortController();
840
904
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -851,37 +915,37 @@ async function checkHttpOk(url, timeoutMs = 1200) {
851
915
  function startStartupStatus(publicUrl, options = {}) {
852
916
  const healthUrl = `${String(publicUrl || '').replace(/\/$/, '')}/health`;
853
917
  const startedAt = Date.now();
918
+ const progress = options.progress || createStartupProgress();
854
919
  let stopped = false;
855
-
856
- console.log('Canvas Flow startup: loading application bundle...');
857
- console.log(`Canvas Flow startup: waiting for backend health at ${healthUrl}`);
858
-
859
- const interval = setInterval(() => {
860
- const elapsedSeconds = Math.max(1, Math.round((Date.now() - startedAt) / 1000));
861
- console.log(`Canvas Flow startup: still starting (${elapsedSeconds}s elapsed)...`);
862
- }, 2500);
920
+ let nextHealthLogAt = 0;
863
921
 
864
922
  const stop = () => {
865
923
  if (stopped) return;
866
924
  stopped = true;
867
- clearInterval(interval);
868
925
  };
869
926
 
870
927
  void (async () => {
871
928
  const deadline = Date.now() + 90000;
929
+ progress.update(90, `waiting for backend health at ${healthUrl}`);
872
930
  while (!stopped && Date.now() < deadline) {
873
931
  if (await checkHttpOk(healthUrl)) {
874
932
  stop();
875
- console.log(`Canvas Flow ready: ${publicUrl}`);
933
+ progress.done(`Canvas Flow ready (100%): ${publicUrl}`);
876
934
  if (options.openBrowser) openBrowser(publicUrl);
877
935
  return;
878
936
  }
937
+ const elapsedSeconds = Math.max(1, Math.round((Date.now() - startedAt) / 1000));
938
+ if (elapsedSeconds >= nextHealthLogAt) {
939
+ const nextPercent = Math.min(98, 90 + Math.floor(elapsedSeconds / 15));
940
+ progress.update(nextPercent, `waiting for backend health (${elapsedSeconds}s elapsed)`);
941
+ nextHealthLogAt = elapsedSeconds + 3;
942
+ }
879
943
  await sleep(500);
880
944
  }
881
945
 
882
946
  if (!stopped) {
883
947
  stop();
884
- console.log('Canvas Flow startup: health check is still pending. Keep this terminal open and watch the backend logs above.');
948
+ progress.fail('Canvas Flow startup: health check is still pending. Keep this terminal open and watch the backend logs above.');
885
949
  }
886
950
  })();
887
951
 
@@ -1161,8 +1225,11 @@ async function doctor(flags) {
1161
1225
  reporter.finish();
1162
1226
  }
1163
1227
 
1164
- async function waitForMongo(config, flags, paths) {
1165
- if (flags['skip-mongo-check'] === true) return;
1228
+ async function waitForMongo(config, flags, paths, progress) {
1229
+ if (flags['skip-mongo-check'] === true) {
1230
+ if (progress) progress.update(45, 'MongoDB preflight skipped');
1231
+ return;
1232
+ }
1166
1233
  if (!config.database.mongoUrl) {
1167
1234
  throw new Error(`database.mongoUrl is required. Edit the config with: canvas-flow config --edit`);
1168
1235
  }
@@ -1174,15 +1241,18 @@ async function waitForMongo(config, flags, paths) {
1174
1241
  let lastMessage = '';
1175
1242
 
1176
1243
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
1244
+ if (progress) progress.update(30, `checking MongoDB (${attempt}/${attempts})`);
1177
1245
  const result = await checkMongoConnection(config.database.mongoUrl, options);
1178
1246
  if (result.ok) {
1179
- console.log('MongoDB preflight: connected');
1247
+ if (progress) progress.update(55, 'MongoDB connected');
1248
+ else console.log('MongoDB preflight: connected');
1180
1249
  return;
1181
1250
  }
1182
1251
 
1183
1252
  lastMessage = result.message;
1184
1253
  if (attempt < attempts) {
1185
- console.log(`MongoDB preflight waiting (${attempt}/${attempts}): ${result.message}`);
1254
+ if (progress) progress.update(35, `waiting for MongoDB (${attempt}/${attempts})`);
1255
+ else console.log(`MongoDB preflight waiting (${attempt}/${attempts}): ${result.message}`);
1186
1256
  await sleep(1500);
1187
1257
  }
1188
1258
  }
@@ -1196,35 +1266,44 @@ async function waitForMongo(config, flags, paths) {
1196
1266
  }
1197
1267
 
1198
1268
  async function start(flags) {
1199
- assertBundleExists();
1200
- addSourceDependencyFallback();
1201
- if (flags['with-docker'] === true || flags.infra === true) {
1202
- infra('up', flags);
1203
- }
1204
- const paths = resolvePaths(flags);
1205
- ensureDir(paths.homeDir);
1206
- const configExisted = fs.existsSync(paths.configPath);
1207
- const config = loadConfig(paths.configPath);
1208
- const runtime = applyEnvironment(config, paths, flags);
1209
- await waitForMongo(config, flags, paths);
1210
-
1211
- process.chdir(paths.homeDir);
1212
-
1213
- console.log(`Canvas Flow config: ${paths.configPath}`);
1214
- console.log(`Canvas Flow home: ${paths.homeDir}`);
1215
- console.log(`Canvas Flow URL: ${runtime.publicUrl}`);
1216
- if (!configExisted) {
1217
- console.log('Created the default config.json.');
1218
- console.log('Edit it with: canvas-flow config --edit');
1219
- console.log('Show it with: canvas-flow config --show');
1220
- }
1221
-
1222
- const shouldOpen = flags.open === true || (flags.open !== false && config.server.openBrowser === true);
1223
- const startupStatus = startStartupStatus(runtime.publicUrl, { openBrowser: shouldOpen });
1269
+ const progress = createStartupProgress();
1270
+ let startupStatus;
1224
1271
  try {
1272
+ progress.update(5, 'checking package bundle');
1273
+ assertBundleExists();
1274
+ progress.update(10, 'loading runtime dependencies');
1275
+ addSourceDependencyFallback();
1276
+ if (flags['with-docker'] === true || flags.infra === true) {
1277
+ progress.log('Canvas Flow startup: starting Docker infrastructure...');
1278
+ infra('up', flags);
1279
+ }
1280
+ progress.update(15, 'loading config');
1281
+ const paths = resolvePaths(flags);
1282
+ ensureDir(paths.homeDir);
1283
+ const configExisted = fs.existsSync(paths.configPath);
1284
+ const config = loadConfig(paths.configPath);
1285
+ progress.update(25, 'applying environment');
1286
+ const runtime = applyEnvironment(config, paths, flags);
1287
+ await waitForMongo(config, flags, paths, progress);
1288
+
1289
+ process.chdir(paths.homeDir);
1290
+
1291
+ progress.log(`Canvas Flow config: ${paths.configPath}`);
1292
+ progress.log(`Canvas Flow home: ${paths.homeDir}`);
1293
+ progress.log(`Canvas Flow URL: ${runtime.publicUrl}`);
1294
+ if (!configExisted) {
1295
+ progress.log('Created the default config.json.');
1296
+ progress.log('Edit it with: canvas-flow config --edit');
1297
+ progress.log('Show it with: canvas-flow config --show');
1298
+ }
1299
+
1300
+ const shouldOpen = flags.open === true || (flags.open !== false && config.server.openBrowser === true);
1301
+ progress.update(75, 'starting Canvas Flow API');
1302
+ startupStatus = startStartupStatus(runtime.publicUrl, { openBrowser: shouldOpen, progress });
1225
1303
  require(SERVER_ENTRY);
1226
1304
  } catch (error) {
1227
- startupStatus.stop();
1305
+ if (startupStatus) startupStatus.stop();
1306
+ progress.fail('Canvas Flow startup failed.');
1228
1307
  throw error;
1229
1308
  }
1230
1309
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@igoruehara/canvas-flow",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Standalone npm launcher for Canvas Flow multi-agent GenAI workflows.",
5
5
  "homepage": "https://github.com/igoruehara/canvas-flow#readme",
6
6
  "repository": {