tungsten 0.4.9 → 0.4.10

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 (93) hide show
  1. package/dist/.build-complete +1 -1
  2. package/dist/Framework.d.ts +1 -0
  3. package/dist/Framework.d.ts.map +1 -1
  4. package/dist/Framework.js +30 -20
  5. package/dist/FrameworkConfig.d.ts +4 -0
  6. package/dist/FrameworkConfig.d.ts.map +1 -1
  7. package/dist/Servers/DevServer.d.ts +0 -1
  8. package/dist/Servers/DevServer.d.ts.map +1 -1
  9. package/dist/Servers/DevServer.js +2 -1
  10. package/dist/Servers/hono/TungstenServerHono.d.ts +1 -1
  11. package/dist/Servers/hono/TungstenServerHono.d.ts.map +1 -1
  12. package/dist/Servers/loadEnvsDev.d.ts.map +1 -1
  13. package/dist/Servers/loadEnvsDev.js +13 -20
  14. package/dist/Servers/tungstenCorePrivateEnvSchema.d.ts +6 -6
  15. package/dist/Servers/tungstenCorePrivateEnvSchema.d.ts.map +1 -1
  16. package/dist/Servers/tungstenCorePrivateEnvSchema.js +12 -22
  17. package/dist/Servers/tungstenCorePublicEnvSchema.d.ts +7 -7
  18. package/dist/Servers/tungstenCorePublicEnvSchema.d.ts.map +1 -1
  19. package/dist/Servers/tungstenCorePublicEnvSchema.js +6 -7
  20. package/dist/api/getApiRoutesHono.d.ts +1 -1
  21. package/dist/api/getApiRoutesHono.d.ts.map +1 -1
  22. package/dist/babelPlugins/BabelAddDevCacheBust.d.ts +1 -0
  23. package/dist/babelPlugins/BabelAddDevCacheBust.d.ts.map +1 -1
  24. package/dist/babelPlugins/BabelAddDevCacheBust.js +1 -0
  25. package/dist/babelPlugins/BabelTransformComponentStyles.d.ts.map +1 -1
  26. package/dist/babelPlugins/BabelTransformComponentStyles.js +4 -2
  27. package/dist/babelPlugins/BabelTransformCssImports.d.ts +1 -0
  28. package/dist/babelPlugins/BabelTransformCssImports.d.ts.map +1 -1
  29. package/dist/babelPlugins/BabelTransformCssImports.js +1 -0
  30. package/dist/babelPlugins/BabelTransformJsxExtensionsToJs.d.ts +1 -0
  31. package/dist/babelPlugins/BabelTransformJsxExtensionsToJs.d.ts.map +1 -1
  32. package/dist/babelPlugins/BabelTransformJsxExtensionsToJs.js +1 -0
  33. package/dist/babelPlugins/BabelTransformPackageImports.d.ts +1 -0
  34. package/dist/babelPlugins/BabelTransformPackageImports.d.ts.map +1 -1
  35. package/dist/babelPlugins/BabelTransformPackageImports.js +75 -15
  36. package/dist/bin/args.d.ts +11 -0
  37. package/dist/bin/args.d.ts.map +1 -0
  38. package/dist/bin/args.js +77 -0
  39. package/dist/bin/commands/build.d.ts.map +1 -1
  40. package/dist/bin/commands/build.js +15 -4
  41. package/dist/bin/commands/buildHelp.js +4 -1
  42. package/dist/bin/commands/clean.d.ts.map +1 -1
  43. package/dist/bin/commands/clean.js +2 -0
  44. package/dist/bin/commands/compile.d.ts.map +1 -1
  45. package/dist/bin/commands/compile.js +73 -81
  46. package/dist/bin/commands/dev.d.ts.map +1 -1
  47. package/dist/bin/commands/dev.js +23 -8
  48. package/dist/bin/commands/devmon.d.ts.map +1 -1
  49. package/dist/bin/commands/devmon.js +26 -30
  50. package/dist/bin/commands/help.js +2 -2
  51. package/dist/bin/commands/start.d.ts.map +1 -1
  52. package/dist/bin/commands/start.js +2 -1
  53. package/dist/bin/commands/static.d.ts.map +1 -1
  54. package/dist/bin/commands/static.js +2 -0
  55. package/dist/bin/index.d.ts.map +1 -1
  56. package/dist/bin/index.js +35 -1
  57. package/dist/bin/moduleLoadAnalysis.d.ts +2 -0
  58. package/dist/bin/moduleLoadAnalysis.d.ts.map +1 -0
  59. package/dist/bin/moduleLoadAnalysis.js +243 -0
  60. package/dist/build/compileJavascript.d.ts +1 -0
  61. package/dist/build/compileJavascript.d.ts.map +1 -1
  62. package/dist/build/compileJavascript.js +67 -11
  63. package/dist/build/compileUserApp.d.ts +1 -1
  64. package/dist/build/compileUserApp.d.ts.map +1 -1
  65. package/dist/build/compileUserApp.js +4 -2
  66. package/dist/build/sassImportParser.d.ts.map +1 -1
  67. package/dist/build/sassImportParser.js +5 -2
  68. package/dist/build/tallyBabelPlugins.d.ts +3 -0
  69. package/dist/build/tallyBabelPlugins.d.ts.map +1 -0
  70. package/dist/build/tallyBabelPlugins.js +68 -0
  71. package/dist/componentCss/tungsten.css +2 -2
  72. package/dist/env/getEnvSchemas.d.ts +3 -3
  73. package/dist/env/getEnvSchemas.d.ts.map +1 -1
  74. package/dist/env/getEnvSchemas.js +9 -15
  75. package/dist/env/setPrivateEnvValidator.d.ts +2 -2
  76. package/dist/env/setPrivateEnvValidator.d.ts.map +1 -1
  77. package/dist/env/setPublicEnvValidator.d.ts +2 -2
  78. package/dist/env/setPublicEnvValidator.d.ts.map +1 -1
  79. package/dist/frameworkConfigSchema.d.ts +61 -192
  80. package/dist/frameworkConfigSchema.d.ts.map +1 -1
  81. package/dist/frameworkConfigSchema.js +49 -62
  82. package/dist/getUserConfig.d.ts.map +1 -1
  83. package/dist/getUserConfig.js +5 -5
  84. package/dist/moduleReloader.d.ts +1 -3
  85. package/dist/moduleReloader.d.ts.map +1 -1
  86. package/dist/moduleReloader.js +17 -15
  87. package/dist/standardSchema.d.ts +37 -0
  88. package/dist/standardSchema.d.ts.map +1 -0
  89. package/dist/standardSchema.js +47 -0
  90. package/dist/timeProfiler.d.ts +6 -0
  91. package/dist/timeProfiler.d.ts.map +1 -1
  92. package/dist/timeProfiler.js +253 -10
  93. package/package.json +3 -4
@@ -1,52 +1,48 @@
1
- // Master process for `tungsten dev`: runs it as a child process and watches the same
2
- // build-notification marker file as warnOnFrameworkChange.ts (dist/.build-complete in the
3
- // framework package). When a framework rebuild touches it, kills and respawns the dev child
4
- // instead of just warning that a restart is needed.
1
+ // Supervisor for `tungsten dev`: runs it as a child and watches the same build-notification marker
2
+ // file as warnOnFrameworkChange.ts (dist/.build-complete in the framework package). Where that just
3
+ // warns you to restart by hand, this kills the dev child on a framework rebuild and respawns it.
5
4
  import { spawn } from 'node:child_process';
6
5
  import { join } from 'node:path';
7
6
  import { fileURLToPath } from 'node:url';
8
7
  import chokidar from 'chokidar';
9
8
  import { cliError } from "../../cliOutput.js";
10
9
  import { getFrameworkDir } from "../../getFrameworkDir.js";
11
- const binIndexPath = fileURLToPath(new URL('../index.js', import.meta.url));
10
+ import { cliArgs } from "../args.js";
11
+ // devmon reads none of its arguments itself - it passes them straight through to the dev child
12
+ cliArgs({}, {
13
+ handledElsewhere: ['--no-boot', '--profile', '--profile-modules']
14
+ });
12
15
  const initialArgs = process.argv.slice(3);
13
- // Restarts always force --no-boot (no reopening the browser tab each time), but otherwise
14
- // keep whatever else devmon itself was passed - e.g. --profile.
15
- const restartArgs = [...initialArgs.filter(arg => arg !== '--no-boot'), '--no-boot'];
16
+ // a restart must not reopen the browser tab, so it always carries --no-boot
17
+ const restartArgs = initialArgs.includes('--no-boot') ? initialArgs : [...initialArgs, '--no-boot'];
18
+ const binIndexPath = fileURLToPath(new URL('../index.js', import.meta.url));
16
19
  const buildMarkerPath = join(getFrameworkDir(), 'dist', '.build-complete');
17
20
  let child = null;
18
- let restarting = false;
19
- function startDev(args) {
21
+ // tells the child's exit handler that we asked for it, rather than the run being over
22
+ let restartWanted = false;
23
+ function startDev(devArgs) {
20
24
  // __TUNGSTEN_DEVMON tells dev.ts to skip warnOnFrameworkChange() - devmon is already
21
25
  // restarting it, so that warning would just be confusing noise here.
22
- child = spawn(process.execPath, [binIndexPath, 'dev', ...args], {
26
+ child = spawn(process.execPath, [binIndexPath, 'dev', ...devArgs], {
23
27
  stdio: 'inherit',
24
28
  env: {
25
29
  ...process.env,
26
30
  __TUNGSTEN_DEVMON: 'true'
27
31
  }
28
32
  });
29
- child.on('exit', code => {
33
+ child.once('exit', code => {
30
34
  child = null;
31
- // not one of our own restarts, so the dev process ended on its own (crash, or the
32
- // user quit it directly) - nothing left for devmon to supervise
33
- if (!restarting) process.exit(code ?? 0);
35
+ // nobody asked for this, so dev ended on its own - it crashed, or you quit it
36
+ if (!restartWanted) process.exit(code ?? 0);
37
+ restartWanted = false;
38
+ startDev(restartArgs);
34
39
  });
35
40
  }
36
- function restartDev() {
37
- if (restarting) return;
38
- restarting = true;
41
+ function restartOnRebuild() {
42
+ if (restartWanted) return; // one is already on its way down
43
+ restartWanted = true;
39
44
  cliError('tungsten framework rebuilt - restarting tungsten dev');
40
- if (child === null) {
41
- restarting = false;
42
- startDev(restartArgs);
43
- return;
44
- }
45
- child.once('exit', () => {
46
- restarting = false;
47
- startDev(restartArgs);
48
- });
49
- child.kill();
45
+ child?.kill();
50
46
  }
51
47
  startDev(initialArgs);
52
48
  const watcher = chokidar.watch(buildMarkerPath, {
@@ -56,10 +52,10 @@ const watcher = chokidar.watch(buildMarkerPath, {
56
52
  pollInterval: 20
57
53
  }
58
54
  });
59
- watcher.on('all', restartDev);
55
+ watcher.on('all', restartOnRebuild);
60
56
  function shutdown() {
61
57
  void watcher.close();
62
- if (child !== null) child.kill();
58
+ child?.kill();
63
59
  process.exit(0);
64
60
  }
65
61
  process.on('SIGINT', shutdown);
@@ -3,9 +3,9 @@ const command = process.argv[2];
3
3
  cliOut(`Usage: tungsten <command> [options]
4
4
 
5
5
  Commands:
6
- dev Start dev mode [--no-boot] [--profile]
6
+ dev Start dev mode [--no-boot] [--profile] [--profile-modules]
7
7
  devmon Dev mode, auto-restarts on framework rebuilds (same options as dev)
8
- build Production build [--profile] [--quiet]
8
+ build Production build [--profile] [--profile-modules] [--quiet]
9
9
  start Start production server
10
10
  static Preview a static site build
11
11
  clean Delete build assets
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/start.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/start.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,CAAA"}
@@ -1,7 +1,8 @@
1
1
  import { Framework } from "../../Framework.js";
2
+ import { cliArgs } from "../args.js";
2
3
  //import dotenv from 'dotenv'
3
4
  //dotenv.config({ path: '.private.env' })
4
- console.log('tungsten start');
5
+ cliArgs({}); // accepts nothing but --help
5
6
  //// prep the framework
6
7
  const framework = new Framework();
7
8
  await framework.init();
@@ -1 +1 @@
1
- {"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/static.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/static.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,CAAA"}
@@ -1,6 +1,8 @@
1
1
  import open from 'open';
2
2
  import { getUserConfig } from "../../getUserConfig.js";
3
3
  import { StaticSiteServer } from "../../Servers/StaticSiteServer.js";
4
+ import { cliArgs } from "../args.js";
5
+ cliArgs({}); // accepts nothing but --help
4
6
  const staticServer = new StaticSiteServer(await getUserConfig());
5
7
  const staticServerLocation = await staticServer.start();
6
8
  open(staticServerLocation);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bin/index.ts"],"names":[],"mappings":"AAuEA,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bin/index.ts"],"names":[],"mappings":"AA6GA,OAAO,EAAE,CAAA"}
package/dist/bin/index.js CHANGED
@@ -1,7 +1,29 @@
1
+ import { enableCompileCache, flushCompileCache } from 'node:module';
1
2
  import { cliOut, cliWarn } from "../cliOutput.js";
3
+ import { beginStartupProfiling } from "../timeProfiler.js";
2
4
  // get command line args
3
5
  const args = process.argv.slice(2);
4
6
  const command = args[0];
7
+ const profile = args.includes('--profile');
8
+ // V8 bytecode cache for the command's import tree, which is compiled on the dynamic imports below.
9
+ // Measured at ~14% off importing Framework.js once warm; the first run after a miss is ~8% slower
10
+ // while it populates, so a single run straight after a rebuild looks like a regression.
11
+ // NODE_DISABLE_COMPILE_CACHE=1 turns it off (NODE_COMPILE_CACHE does not - it names the cache
12
+ // directory, so a value of 0 makes a folder called 0).
13
+ const compileCache = enableCompileCache();
14
+ // reported under --profile because "is it even on?" is otherwise only answerable by inspecting
15
+ // the cache directory's timestamps - status 1 is enabled, 2 already enabled, 3 disabled
16
+ if (profile) {
17
+ cliOut(`compile cache: status ${compileCache.status}` + ` ${compileCache.directory ?? compileCache.message ?? ''}`);
18
+ }
19
+ // hooks have to be registered before the command module - and so its import tree - is loaded
20
+ const profileModulesArg = args.find(arg => arg === '--profile-modules' || arg.startsWith('--profile-modules='));
21
+ if (profileModulesArg !== undefined) {
22
+ const {
23
+ registerModuleLoadAnalysis
24
+ } = await import("./moduleLoadAnalysis.js");
25
+ registerModuleLoadAnalysis(profileModulesArg.split('=')[1]);
26
+ }
5
27
  switch (command) {
6
28
  case undefined:
7
29
  case '':
@@ -15,6 +37,7 @@ switch (command) {
15
37
  case 'dev':
16
38
  {
17
39
  cliOut(`Starting Tungsten dev mode`);
40
+ beginStartupProfiling(profile);
18
41
  // set env before loading the next file, to set the env before file imports
19
42
  process.env.__TUNGSTEN_ENV = 'development';
20
43
  await import("./commands/dev.js");
@@ -35,6 +58,7 @@ switch (command) {
35
58
  }
36
59
  // announce before the import: resolving the build's dependencies is a long silent wait
37
60
  if (!args.includes('--quiet')) cliOut(`Starting Tungsten production build`);
61
+ beginStartupProfiling(profile);
38
62
  process.env.__TUNGSTEN_ENV = 'production';
39
63
  process.env.__TUNGSTEN_DEBUG = 'true';
40
64
  await import("./commands/build.js");
@@ -61,6 +85,7 @@ switch (command) {
61
85
  }
62
86
  case 'compile':
63
87
  {
88
+ beginStartupProfiling(profile);
64
89
  process.env.__TUNGSTEN_ENV = 'development';
65
90
  await import("./commands/compile.js");
66
91
  break;
@@ -75,4 +100,13 @@ switch (command) {
75
100
  cliWarn(`tungsten: unknown command "${command}" (run "tungsten --help" for usage)`);
76
101
  process.exit(1);
77
102
  }
78
- }
103
+ }
104
+ // Node only writes the compile cache when the process exits, and dev/start are killed rather than
105
+ // exited - signals skip exit handling - so they would populate the cache and then throw it away,
106
+ // paying the cold cost on every run forever. The import above has returned by here, which for a
107
+ // server command means it has finished starting, so this persists what that startup compiled.
108
+ flushCompileCache();
109
+ // A profiled dev run has measured everything it will measure once the server is up and the report
110
+ // has printed. Exit rather than leave a server to be killed - after the flush above, which a kill
111
+ // would have skipped. (dev.ts itself must not exit: its import returning is what reaches here.)
112
+ if (profile && command === 'dev') process.exit(0);
@@ -0,0 +1,2 @@
1
+ export declare function registerModuleLoadAnalysis(pivotHint: string | undefined): void;
2
+ //# sourceMappingURL=moduleLoadAnalysis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moduleLoadAnalysis.d.ts","sourceRoot":"","sources":["../../src/bin/moduleLoadAnalysis.ts"],"names":[],"mappings":"AAmCA,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CA0B9E"}
@@ -0,0 +1,243 @@
1
+ // --profile-modules: records the real import graph and per-module cost through node's module
2
+ // hooks. Timing a subsystem on its own overstates it - it pays for everything it pulls in,
3
+ // including what a sibling would have pulled in anyway - so this splits each subsystem's cost
4
+ // into what only it needs and what it shares, and names who it shares with.
5
+ import { registerHooks } from 'node:module';
6
+ import { performance } from 'node:perf_hooks';
7
+ import chalk from 'chalk-extensions';
8
+ import { cliOut } from "../cliOutput.js";
9
+ import { addEndOfRunReport } from "../timeProfiler.js";
10
+ const records = new Map();
11
+ // The user's app enters the graph through an iterationQueryString() import, so its first module
12
+ // arrives as an explicitly tagged specifier - and recording stops there. Past that point the cost
13
+ // is the app's own dependencies, which tungsten cannot do anything about. Matching the specifier
14
+ // rather than the resolved url makes this independent of the order the registered hooks run in.
15
+ const appModulePattern = /[?&]t-(?:instance|reload)\b/;
16
+ let stoppedAtApp = false;
17
+ function recordFor(url) {
18
+ const existing = records.get(url);
19
+ if (existing !== undefined) return existing;
20
+ const created = {
21
+ url,
22
+ importers: new Set(),
23
+ ms: 0
24
+ };
25
+ records.set(url, created);
26
+ return created;
27
+ }
28
+ export function registerModuleLoadAnalysis(pivotHint) {
29
+ registerHooks({
30
+ resolve(specifier, context, next) {
31
+ if (appModulePattern.test(specifier)) stoppedAtApp = true;
32
+ if (stoppedAtApp) return next(specifier, context);
33
+ const start = performance.now();
34
+ const result = next(specifier, context);
35
+ const entry = recordFor(result.url);
36
+ entry.ms += performance.now() - start;
37
+ if (context.parentURL !== undefined) entry.importers.add(context.parentURL);
38
+ return result;
39
+ },
40
+ load(url, context, next) {
41
+ if (stoppedAtApp) return next(url, context);
42
+ const start = performance.now();
43
+ const result = next(url, context);
44
+ recordFor(url).ms += performance.now() - start;
45
+ return result;
46
+ }
47
+ });
48
+ // Printed where each command reports its run total - by then everything is loaded, and dev
49
+ // has its server up rather than waiting to be killed. process.on('exit') cannot do this: a
50
+ // watcher ended with ctrl-c dies to the signal and never runs exit handlers. Kept as a
51
+ // fallback anyway, for a command that ends before it reports a total.
52
+ addEndOfRunReport(() => reportOnce(pivotHint));
53
+ process.on('exit', () => reportOnce(pivotHint));
54
+ }
55
+ let reported = false;
56
+ function reportOnce(pivotHint) {
57
+ if (reported) return;
58
+ reported = true;
59
+ reportModuleLoadAnalysis(pivotHint);
60
+ }
61
+ function ms(value) {
62
+ return `${Math.round(value).toLocaleString()}ms`;
63
+ }
64
+ function shorten(url) {
65
+ const path = url.startsWith('file:///') ? url.slice('file:///'.length) : url;
66
+ const marker = path.lastIndexOf('node_modules/');
67
+ const name = marker !== -1 ? path.slice(marker + 'node_modules/'.length) : path.split('/').slice(-3).join('/');
68
+ return name.length > 46 ? `…${name.slice(-45)}` : name;
69
+ }
70
+ // the npm package a module belongs to - tungsten's own files and node builtins have none
71
+ function packageOf(url) {
72
+ const marker = url.lastIndexOf('node_modules/');
73
+ if (marker === -1) return undefined;
74
+ const [scope, scoped] = url.slice(marker + 'node_modules/'.length).split('/');
75
+ if (scope === undefined) return undefined;
76
+ return scope.startsWith('@') ? `${scope}/${scoped ?? ''}` : scope;
77
+ }
78
+ // What dropping a package would actually save: everything reachable from it that nothing else
79
+ // reaches - its own modules plus the transitive dependencies it alone pulls in. A package's total
80
+ // size is the wrong number for that, because most of a small package can be dependencies it
81
+ // shares with something you are keeping.
82
+ function reportPackageCosts(loaded) {
83
+ const children = childGraph();
84
+ // Only packages entered from outside node_modules - tungsten's own code - are candidates.
85
+ // Counting nested packages too would leave every one of them at zero, since its modules are
86
+ // always reachable from whichever package depends on it as well as from itself.
87
+ const entries = new Map();
88
+ for (const entry of loaded) {
89
+ const name = packageOf(entry.url);
90
+ if (name === undefined) continue;
91
+ if (![...entry.importers].some(importer => packageOf(importer) === undefined)) continue;
92
+ entries.set(name, [...(entries.get(name) ?? []), entry.url]);
93
+ }
94
+ const owners = new Map();
95
+ for (const [name, urls] of entries) {
96
+ const reached = new Set();
97
+ for (const url of urls) for (const found of reachableFrom(url, children)) reached.add(found);
98
+ for (const url of reached) owners.set(url, [...(owners.get(url) ?? []), name]);
99
+ }
100
+ const totals = new Map();
101
+ for (const [url, names] of owners) {
102
+ const cost = costOf(url);
103
+ for (const name of names) {
104
+ const stat = totals.get(name) ?? {
105
+ exclusive: 0,
106
+ shared: 0,
107
+ modules: 0
108
+ };
109
+ if (names.length === 1) {
110
+ stat.exclusive += cost;
111
+ stat.modules += 1;
112
+ } else stat.shared += cost;
113
+ totals.set(name, stat);
114
+ }
115
+ }
116
+ cliOut('');
117
+ cliOut(chalk.mediumgrey`packages imported directly` + chalk.mediumgrey` - "only it" is what dropping the package would save, its own` + chalk.mediumgrey` modules plus the dependencies nothing else pulls in`);
118
+ cliOut(' ' + chalk.mediumgrey`${'package'.padEnd(30)}` + ' ' + chalk.mediumgrey`${'only it'.padStart(9)}` + ' ' + chalk.mediumgrey`${'shared'.padStart(9)}`);
119
+ const ranked = [...totals].sort((a, b) => b[1].exclusive - a[1].exclusive).slice(0, 15);
120
+ for (const [name, stat] of ranked) {
121
+ cliOut(' ' + chalk.yellow`${name.padEnd(30)}` + ' ' + chalk.orange`${ms(stat.exclusive).padStart(9)}` + ' ' + chalk.mediumgrey`${ms(stat.shared).padStart(9)}` + ' ' + chalk.mediumgrey`${stat.modules} modules only it needs`);
122
+ }
123
+ }
124
+ function childGraph() {
125
+ const children = new Map();
126
+ for (const entry of records.values()) {
127
+ for (const importer of entry.importers) {
128
+ const existing = children.get(importer);
129
+ if (existing === undefined) children.set(importer, new Set([entry.url]));else existing.add(entry.url);
130
+ }
131
+ }
132
+ return children;
133
+ }
134
+ function reachableFrom(root, children) {
135
+ const seen = new Set();
136
+ const stack = [root];
137
+ for (let url = stack.pop(); url !== undefined; url = stack.pop()) {
138
+ if (seen.has(url)) continue;
139
+ seen.add(url);
140
+ for (const child of children.get(url) ?? []) stack.push(child);
141
+ }
142
+ return seen;
143
+ }
144
+ // Framework.js is the interesting fork for dev/build; compile has no Framework, so fall back to
145
+ // the command module. --profile-modules=<substring> overrides both.
146
+ function choosePivot(pivotHint) {
147
+ const urls = [...records.keys()];
148
+ if (pivotHint !== undefined && pivotHint !== '') return urls.find(url => url.includes(pivotHint));
149
+ return urls.find(url => url.endsWith('/Framework.js')) ?? urls.find(url => url.includes('/commands/'));
150
+ }
151
+ function costOf(url) {
152
+ return records.get(url)?.ms ?? 0;
153
+ }
154
+ function reportModuleLoadAnalysis(pivotHint) {
155
+ // snapshot first: printing loads chalk's template module, which would otherwise land in the
156
+ // listing below but not in the count above it
157
+ const loaded = [...records.values()];
158
+ if (loaded.length === 0) return;
159
+ const recorded = loaded.reduce((total, entry) => total + entry.ms, 0);
160
+ cliOut('');
161
+ const scope = stoppedAtApp ? `, stopping where the user's app starts loading` : ``;
162
+ const summary = ` - ${loaded.length.toLocaleString()} modules, ${ms(recorded)} in resolve + read` + `${scope} (V8 compile and module evaluation are not covered)`;
163
+ cliOut(chalk.yellow`module load analysis` + chalk.mediumgrey`${summary}`);
164
+ // what bundling tungsten's own dist would collapse, as against what lives in node_modules
165
+ const own = loaded.filter(entry => !entry.url.startsWith('node:') && packageOf(entry.url) === undefined);
166
+ const ownMs = own.reduce((total, entry) => total + entry.ms, 0);
167
+ const dependencies = loaded.length - own.length;
168
+ cliOut(chalk.mediumgrey` tungsten's own files: ${own.length.toLocaleString()} modules, ${ms(ownMs)}` + ` - dependencies: ${dependencies.toLocaleString()} modules, ${ms(recorded - ownMs)}`);
169
+ cliOut('');
170
+ cliOut(chalk.mediumgrey`heaviest modules`);
171
+ const heaviest = [...loaded].sort((a, b) => b.ms - a.ms).slice(0, 12);
172
+ for (const entry of heaviest) {
173
+ cliOut(' ' + chalk.yellow`${shorten(entry.url).padEnd(46)}` + ' ' + chalk.orange`${ms(entry.ms).padStart(9)}` + ' ' + chalk.mediumgrey`${`${entry.importers.size} importer${entry.importers.size === 1 ? '' : 's'}`}`);
174
+ }
175
+ reportPackageCosts(loaded);
176
+ const pivot = choosePivot(pivotHint);
177
+ if (pivot === undefined) return;
178
+ const children = childGraph();
179
+ const roots = [...(children.get(pivot) ?? [])].filter(url => !url.startsWith('node:'));
180
+ if (roots.length === 0) return;
181
+ // which roots can reach each module - one root means the cost is that root's alone
182
+ const owners = new Map();
183
+ for (const root of roots) {
184
+ for (const url of reachableFrom(root, children)) {
185
+ if (url === pivot) continue;
186
+ const existing = owners.get(url);
187
+ if (existing === undefined) owners.set(url, [root]);else existing.push(root);
188
+ }
189
+ }
190
+ const stats = new Map(roots.map(root => [root, {
191
+ exclusive: 0,
192
+ shared: 0
193
+ }]));
194
+ const groups = new Map();
195
+ let attributed = 0;
196
+ for (const [url, ownerList] of owners) {
197
+ const cost = costOf(url);
198
+ attributed += cost;
199
+ for (const root of ownerList) {
200
+ const stat = stats.get(root);
201
+ if (stat === undefined) continue;
202
+ if (ownerList.length === 1) stat.exclusive += cost;else stat.shared += cost;
203
+ }
204
+ if (ownerList.length === 1) continue;
205
+ const key = [...ownerList].sort().join('\n');
206
+ const group = groups.get(key);
207
+ if (group === undefined) groups.set(key, {
208
+ roots: [...ownerList].sort(),
209
+ ms: cost,
210
+ modules: 1
211
+ });else {
212
+ group.ms += cost;
213
+ group.modules += 1;
214
+ }
215
+ }
216
+ cliOut('');
217
+ cliOut(chalk.mediumgrey`direct imports of ${shorten(pivot)}` + chalk.mediumgrey` - "alone" is what that import would cost with no siblings loaded`);
218
+ cliOut(' ' + chalk.mediumgrey`${'import'.padEnd(46)}` + ' ' + chalk.mediumgrey`${'only it'.padStart(9)}` + ' ' + chalk.mediumgrey`${'shared'.padStart(9)}` + ' ' + chalk.mediumgrey`${'alone'.padStart(9)}`);
219
+ const ranked = roots.map(root => ({
220
+ root,
221
+ ...(stats.get(root) ?? {
222
+ exclusive: 0,
223
+ shared: 0
224
+ })
225
+ })).sort((a, b) => b.exclusive + b.shared - (a.exclusive + a.shared));
226
+ for (const {
227
+ root,
228
+ exclusive,
229
+ shared
230
+ } of ranked.slice(0, 15)) {
231
+ cliOut(' ' + chalk.yellow`${shorten(root).padEnd(46)}` + ' ' + chalk.orange`${ms(exclusive).padStart(9)}` + ' ' + chalk.mediumgrey`${ms(shared).padStart(9)}` + ' ' + chalk.mediumgrey`${ms(exclusive + shared).padStart(9)}`);
232
+ }
233
+ if (ranked.length > 15) cliOut(' ' + chalk.mediumgrey`+ ${ranked.length - 15} more`);
234
+ const outside = `${ms(recorded - attributed)} outside ${shorten(pivot)} (the cli entry and its own imports)`;
235
+ cliOut(' ' + chalk.mediumgrey`${outside}`);
236
+ const sharedGroups = [...groups.values()].sort((a, b) => b.ms - a.ms).slice(0, 8);
237
+ if (sharedGroups.length === 0) return;
238
+ cliOut('');
239
+ cliOut(chalk.mediumgrey`shared subtrees - counted once here, but in every sharer's "alone" above`);
240
+ for (const group of sharedGroups) {
241
+ cliOut(' ' + chalk.orange`${ms(group.ms).padStart(9)}` + ' ' + chalk.mediumgrey`${`${group.modules} modules`.padEnd(14)}` + ' ' + chalk.yellow`${group.roots.map(shorten).join(' + ')}`);
242
+ }
243
+ }
@@ -18,6 +18,7 @@ export type CompileJavascriptOptions = {
18
18
  packages?: boolean;
19
19
  hmr?: boolean;
20
20
  onCycleComplete?: ((cycle: JavascriptCompilationCycle) => void | Promise<void>) | undefined;
21
+ tests?: boolean;
21
22
  };
22
23
  export declare function compileJavascript(projectDir: string, appConfig: FrameworkConfigComplete, codeLocation: string, sourceLocation: string, styleImportBase: string, buildLocation: string, buildCodeLocation: string, mode: Mode, watch?: boolean, options?: CompileJavascriptOptions): Promise<CompileJavascriptResult>;
23
24
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"compileJavascript.d.ts","sourceRoot":"","sources":["../../src/build/compileJavascript.ts"],"names":[],"mappings":"AAEA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAO/B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAetC,KAAK,uBAAuB,GAAG;IAC3B,OAAO,EAAE,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAA;IAClC,WAAW,EAAE,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAA;IACtC,aAAa,EAAE,aAAa,CAAA;IAC5B,cAAc,EAAE,OAAO,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACnC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAE9B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,eAAe,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAA;CAC9F,CAAA;AAKD,wBAAsB,iBAAiB,CACnC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,uBAAuB,EACtD,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EACrE,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,UAAO,EAC1E,OAAO,GAAE,wBAA6B,GACvC,OAAO,CAAC,uBAAuB,CAAC,CAkZlC"}
1
+ {"version":3,"file":"compileJavascript.d.ts","sourceRoot":"","sources":["../../src/build/compileJavascript.ts"],"names":[],"mappings":"AAGA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAO/B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAiBtC,KAAK,uBAAuB,GAAG;IAC3B,OAAO,EAAE,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAA;IAClC,WAAW,EAAE,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAA;IACtC,aAAa,EAAE,aAAa,CAAA;IAC5B,cAAc,EAAE,OAAO,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACnC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAE9B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,eAAe,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAA;IAE3F,KAAK,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAoBD,wBAAsB,iBAAiB,CACnC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,uBAAuB,EACtD,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EACrE,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,UAAO,EAC1E,OAAO,GAAE,wBAA6B,GACvC,OAAO,CAAC,uBAAuB,CAAC,CA+blC"}
@@ -1,6 +1,7 @@
1
1
  import { join, dirname } from 'path';
2
+ import { pathToFileURL } from 'url';
2
3
  import chokidar from 'chokidar';
3
- import { transformFileAsync as babeltransformFileAsync } from '@babel/core';
4
+ import { transformFileSync as babelTransformFileSync } from '@babel/core';
4
5
  import fs, { promises as fsPromises } from 'fs';
5
6
  import slash from 'slash';
6
7
  import serializeJavascript from 'serialize-javascript';
@@ -8,9 +9,25 @@ import { globby } from 'globby';
8
9
  import { packagesCacheFolder } from "../settings.js";
9
10
  import { makeBabelUserDevConfig } from "./makeBabelUserDevConfig.js";
10
11
  import { makeBabelUserProdConfig } from "./makeBabelUserProdConfig.js";
11
- import { cliOut, cliLog, cliError } from "../cliOutput.js"; // eslint-disable-line @typescript-eslint/no-unused-vars
12
+ import { cliOut, cliLog, cliError, cliWarn } from "../cliOutput.js"; // eslint-disable-line @typescript-eslint/no-unused-vars
13
+ import { createTimeProfiler, isProfiling, tally } from "../timeProfiler.js";
14
+ import { tallyBabelPlugins } from "./tallyBabelPlugins.js";
12
15
  import { TungstenError } from "../TungstenError.js";
13
16
  import { getFrameworkDir } from "../getFrameworkDir.js";
17
+ // The graph writeDependencyGraph() left behind on the previous run, naming a compiled copy of every
18
+ // package file in the packages cache. Missing or unreadable just means everything is compiled from
19
+ // source, which is what happened on every run before this was read back.
20
+ async function loadDependencyGraph(location) {
21
+ if (!fs.existsSync(location)) return {};
22
+ try {
23
+ const graphModule = await import(pathToFileURL(location).href);
24
+ return graphModule.graph ?? {};
25
+ } catch (error) {
26
+ cliWarn(`Could not read the package cache at ${location}, compiling packages from source`);
27
+ cliLog(error);
28
+ return {};
29
+ }
30
+ }
14
31
  ///// needs work to work in prod mode
15
32
  ///// see babel config
16
33
  export async function compileJavascript(projectDir, appConfig, codeLocation, sourceLocation, styleImportBase, buildLocation, buildCodeLocation, mode, watch = true, options = {}) {
@@ -37,7 +54,7 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
37
54
  if (mode === 'dev') {
38
55
  dependencyGraphDir = options.packagesCacheLocation ?? join(projectDir, packagesCacheFolder);
39
56
  dependencyGraphLocation = join(dependencyGraphDir, '.dependency-graph.js');
40
- oldDependencyGraph = {};
57
+ oldDependencyGraph = await loadDependencyGraph(dependencyGraphLocation);
41
58
  dependencyGraph = {};
42
59
  importAliases = {};
43
60
  promises = [];
@@ -46,6 +63,14 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
46
63
  config = makeBabelUserProdConfig(projectDir, codeLocation, sourceLocation, styleImportBase, watch);
47
64
  }
48
65
  config.root = getFrameworkDir();
66
+ // per-plugin attribution of the transform's time, only when someone is looking: the wrappers
67
+ // run per AST node
68
+ if (isProfiling()) {
69
+ config.plugins = tallyBabelPlugins(config.plugins);
70
+ if (mode === 'dev' && compilePackages) {
71
+ cliWarn(`profiling serves linked packages from the packages cache -` + ` delete ${dependencyGraphDir} after rebuilding one, or this run compiles stale code`);
72
+ }
73
+ }
49
74
  // compile with the API
50
75
  const codeLocationLength = codeLocation.length + 1;
51
76
  const transformPromises = [];
@@ -68,7 +93,13 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
68
93
  failedFiles.delete(file);
69
94
  return;
70
95
  }
71
- const result = await babeltransformFileAsync(file, config);
96
+ // Sync, so this can be tallied exactly - nothing else runs during it. Babel's work is
97
+ // cpu-bound on one thread either way, so the async API's 291 concurrent calls bought
98
+ // no parallelism, only scheduling. The plugins defer their async work into
99
+ // opts.promises rather than awaiting in a visitor, which is what makes sync viable.
100
+ const babelStart = performance.now();
101
+ const result = babelTransformFileSync(file, config);
102
+ tally('babel transform', performance.now() - babelStart);
72
103
  if (result === null) throw new TungstenError(`Err: babel transform on file ${file} returned null`);
73
104
  const {
74
105
  code
@@ -175,6 +206,8 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
175
206
  }
176
207
  // get a list of files
177
208
  const watchGlob = slash(`${codeLocation}/**/*.js{,x}`);
209
+ // typed rather than inferred from the pushes: it is captured by a closure below, which stops
210
+ // typescript widening it from the pushes
178
211
  const ignored = [];
179
212
  ignored.push(slash(`${buildLocation}/**`));
180
213
  if (appConfig.ignoreDirs !== undefined && appConfig.ignoreDirs !== '') {
@@ -190,6 +223,17 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
190
223
  ignored.push(appConfig.ignorePatterns);
191
224
  }
192
225
  }
226
+ // test files are a category, not noise: excluded here like ignoreDirs/ignorePatterns, but a
227
+ // test compile (`tungsten compile --tests`) wants them, where it would never want the ignored
228
+ if (options.tests !== true) {
229
+ if (appConfig.testDirs !== undefined && appConfig.testDirs !== '') {
230
+ const testDirs = typeof appConfig.testDirs === 'string' ? [appConfig.testDirs] : appConfig.testDirs;
231
+ ignored.push(...testDirs.map(folder => slash(`${codeLocation}/${folder}/**`)));
232
+ }
233
+ if (appConfig.testPatterns !== undefined && appConfig.testPatterns !== '') {
234
+ ignored.push(...(Array.isArray(appConfig.testPatterns) ? appConfig.testPatterns : [appConfig.testPatterns]));
235
+ }
236
+ }
193
237
  if (appConfig.exclude !== undefined) ignored.push(slash(`${codeLocation}/${appConfig.exclude}`));
194
238
  //console.log('appConfig', appConfig)
195
239
  //console.log('ignored', ignored)
@@ -198,11 +242,18 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
198
242
  //cliWarn(JSON.stringify(ignored))
199
243
  /// need to do an initial build before watching
200
244
  // not sure what glob matcher chokidar uses
201
- const paths = await globby([watchGlob, ...ignored.map(x => `!${x}`)]);
202
- paths.forEach(file => {
203
- transformPromises.push(transformFile(file));
245
+ // phases record into the profiler module's shared timeline, so this instance need not be the
246
+ // one the command created - it only has to be enabled when --profile is
247
+ const profiler = createTimeProfiler(isProfiling());
248
+ const paths = await profiler.timeProfileAsync('find source files', () => globby([watchGlob, ...ignored.map(x => `!${x}`)]));
249
+ // the transforms are started inside the phase, not just awaited there: a resource is charged to
250
+ // the phase that created it, so starting them outside credits all the work to the parent
251
+ const transformResults = await profiler.timeProfileAsync(`transform ${paths.length} files`, () => {
252
+ paths.forEach(file => {
253
+ transformPromises.push(transformFile(file));
254
+ });
255
+ return Promise.allSettled(transformPromises);
204
256
  });
205
- const transformResults = await Promise.allSettled(transformPromises);
206
257
  const transformFailures = transformResults.filter(result => result.status === 'rejected').map(result => result.reason);
207
258
  const settleDeferredWork = async () => {
208
259
  // splice out (rather than slice+advance-a-pointer) so settled entries are freed for GC -
@@ -212,7 +263,10 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
212
263
  const deferredResults = await Promise.allSettled(currentPromises);
213
264
  return deferredResults.filter(result => result.status === 'rejected').map(result => result.reason);
214
265
  };
215
- const initialFailures = [...transformFailures, ...(await settleDeferredWork())];
266
+ // the package walk the transforms queued - compiling, or now copying from the packages cache,
267
+ // every file of every imported package
268
+ const deferredFailures = await profiler.timeProfileAsync('settle package work', settleDeferredWork);
269
+ const initialFailures = [...transformFailures, ...deferredFailures];
216
270
  const initialSuccess = initialFailures.length === 0 && failedFiles.size === 0;
217
271
  function writeDependencyGraph() {
218
272
  // without the package walk the graph is empty - don't create the cache dir to hold it
@@ -320,7 +374,9 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
320
374
  }
321
375
  });
322
376
  }
323
- return new Promise(resolve => {
377
+ // the watchers are created above but only scan the tree now - on a large app that scan is
378
+ // seconds, and it runs after every file has already been compiled
379
+ return profiler.timeProfileAsync('watchers ready', () => new Promise(resolve => {
324
380
  const awaiting = new Map([[watcher, true]]);
325
381
  if (copyWatcher) awaiting.set(copyWatcher, true);
326
382
  watcher.on('ready', async () => {
@@ -350,5 +406,5 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
350
406
  }
351
407
  });
352
408
  }
353
- });
409
+ }));
354
410
  }
@@ -2,5 +2,5 @@ import type { CompilationInfo } from './CompilationInfo.js';
2
2
  import type { FrameworkConfigComplete } from '../FrameworkConfig.js';
3
3
  import type { Mode } from '../Mode.js';
4
4
  import { type TimeProfiler } from '../timeProfiler.js';
5
- export declare function compileUserApp(projectDir: string, appConfig: FrameworkConfigComplete, mode: Mode, profiler?: TimeProfiler, quiet?: boolean): Promise<CompilationInfo>;
5
+ export declare function compileUserApp(projectDir: string, appConfig: FrameworkConfigComplete, mode: Mode, profiler?: TimeProfiler, quiet?: boolean, tests?: boolean): Promise<CompilationInfo>;
6
6
  //# sourceMappingURL=compileUserApp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compileUserApp.d.ts","sourceRoot":"","sources":["../../src/build/compileUserApp.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAWtC,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAI1E,wBAAsB,cAAc,CAChC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,uBAAuB,EAAE,IAAI,EAAE,IAAI,EAClE,QAAQ,GAAE,YAAwC,EAAE,KAAK,UAAQ,GAClE,OAAO,CAAC,eAAe,CAAC,CA2E1B"}
1
+ {"version":3,"file":"compileUserApp.d.ts","sourceRoot":"","sources":["../../src/build/compileUserApp.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAWtC,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAI1E,wBAAsB,cAAc,CAChC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,uBAAuB,EAAE,IAAI,EAAE,IAAI,EAClE,QAAQ,GAAE,YAAwC,EAAE,KAAK,UAAQ,EAAE,KAAK,UAAQ,GACjF,OAAO,CAAC,eAAe,CAAC,CA4E1B"}