tungsten 0.4.8 → 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.
- package/dist/.build-complete +1 -1
- package/dist/Framework.d.ts +1 -0
- package/dist/Framework.d.ts.map +1 -1
- package/dist/Framework.js +30 -20
- package/dist/FrameworkConfig.d.ts +4 -0
- package/dist/FrameworkConfig.d.ts.map +1 -1
- package/dist/Servers/DevServer.d.ts +0 -1
- package/dist/Servers/DevServer.d.ts.map +1 -1
- package/dist/Servers/DevServer.js +2 -1
- package/dist/Servers/hono/TungstenServerHono.d.ts +1 -1
- package/dist/Servers/hono/TungstenServerHono.d.ts.map +1 -1
- package/dist/Servers/loadEnvsDev.d.ts.map +1 -1
- package/dist/Servers/loadEnvsDev.js +13 -20
- package/dist/Servers/tungstenCorePrivateEnvSchema.d.ts +6 -6
- package/dist/Servers/tungstenCorePrivateEnvSchema.d.ts.map +1 -1
- package/dist/Servers/tungstenCorePrivateEnvSchema.js +12 -22
- package/dist/Servers/tungstenCorePublicEnvSchema.d.ts +7 -7
- package/dist/Servers/tungstenCorePublicEnvSchema.d.ts.map +1 -1
- package/dist/Servers/tungstenCorePublicEnvSchema.js +6 -7
- package/dist/api/getApiRoutesHono.d.ts +1 -1
- package/dist/api/getApiRoutesHono.d.ts.map +1 -1
- package/dist/babelPlugins/BabelAddDevCacheBust.d.ts +1 -0
- package/dist/babelPlugins/BabelAddDevCacheBust.d.ts.map +1 -1
- package/dist/babelPlugins/BabelAddDevCacheBust.js +1 -0
- package/dist/babelPlugins/BabelTransformComponentStyles.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformComponentStyles.js +4 -2
- package/dist/babelPlugins/BabelTransformCssImports.d.ts +1 -0
- package/dist/babelPlugins/BabelTransformCssImports.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformCssImports.js +1 -0
- package/dist/babelPlugins/BabelTransformJsxExtensionsToJs.d.ts +1 -0
- package/dist/babelPlugins/BabelTransformJsxExtensionsToJs.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformJsxExtensionsToJs.js +1 -0
- package/dist/babelPlugins/BabelTransformPackageImports.d.ts +1 -0
- package/dist/babelPlugins/BabelTransformPackageImports.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformPackageImports.js +75 -15
- package/dist/bin/args.d.ts +11 -0
- package/dist/bin/args.d.ts.map +1 -0
- package/dist/bin/args.js +77 -0
- package/dist/bin/commands/build.d.ts.map +1 -1
- package/dist/bin/commands/build.js +15 -4
- package/dist/bin/commands/buildHelp.js +4 -1
- package/dist/bin/commands/clean.d.ts.map +1 -1
- package/dist/bin/commands/clean.js +2 -0
- package/dist/bin/commands/compile.d.ts.map +1 -1
- package/dist/bin/commands/compile.js +76 -75
- package/dist/bin/commands/dev.d.ts.map +1 -1
- package/dist/bin/commands/dev.js +23 -8
- package/dist/bin/commands/devmon.d.ts.map +1 -1
- package/dist/bin/commands/devmon.js +26 -30
- package/dist/bin/commands/help.js +2 -2
- package/dist/bin/commands/start.d.ts.map +1 -1
- package/dist/bin/commands/start.js +2 -1
- package/dist/bin/commands/static.d.ts.map +1 -1
- package/dist/bin/commands/static.js +2 -0
- package/dist/bin/index.d.ts.map +1 -1
- package/dist/bin/index.js +35 -1
- package/dist/bin/moduleLoadAnalysis.d.ts +2 -0
- package/dist/bin/moduleLoadAnalysis.d.ts.map +1 -0
- package/dist/bin/moduleLoadAnalysis.js +243 -0
- package/dist/build/compileJavascript.d.ts +2 -0
- package/dist/build/compileJavascript.d.ts.map +1 -1
- package/dist/build/compileJavascript.js +71 -13
- package/dist/build/compileUserApp.d.ts +1 -1
- package/dist/build/compileUserApp.d.ts.map +1 -1
- package/dist/build/compileUserApp.js +4 -2
- package/dist/build/makeBabelUserDevConfig.d.ts +1 -1
- package/dist/build/makeBabelUserDevConfig.d.ts.map +1 -1
- package/dist/build/makeBabelUserDevConfig.js +7 -3
- package/dist/build/sassImportParser.d.ts.map +1 -1
- package/dist/build/sassImportParser.js +5 -2
- package/dist/build/tallyBabelPlugins.d.ts +3 -0
- package/dist/build/tallyBabelPlugins.d.ts.map +1 -0
- package/dist/build/tallyBabelPlugins.js +68 -0
- package/dist/env/getEnvSchemas.d.ts +3 -3
- package/dist/env/getEnvSchemas.d.ts.map +1 -1
- package/dist/env/getEnvSchemas.js +9 -15
- package/dist/env/setPrivateEnvValidator.d.ts +2 -2
- package/dist/env/setPrivateEnvValidator.d.ts.map +1 -1
- package/dist/env/setPublicEnvValidator.d.ts +2 -2
- package/dist/env/setPublicEnvValidator.d.ts.map +1 -1
- package/dist/frameworkConfigSchema.d.ts +61 -192
- package/dist/frameworkConfigSchema.d.ts.map +1 -1
- package/dist/frameworkConfigSchema.js +49 -62
- package/dist/getUserConfig.d.ts.map +1 -1
- package/dist/getUserConfig.js +5 -5
- package/dist/moduleReloader.d.ts +1 -3
- package/dist/moduleReloader.d.ts.map +1 -1
- package/dist/moduleReloader.js +17 -15
- package/dist/standardSchema.d.ts +37 -0
- package/dist/standardSchema.d.ts.map +1 -0
- package/dist/standardSchema.js +47 -0
- package/dist/timeProfiler.d.ts +6 -0
- package/dist/timeProfiler.d.ts.map +1 -1
- package/dist/timeProfiler.js +253 -10
- package/package.json +3 -5
|
@@ -1,67 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const frameworkConfigBaseValidator = arkType({
|
|
25
|
-
'codeDir?': 'string',
|
|
26
|
-
'copy?': 'string',
|
|
27
|
-
'exclude?': 'string',
|
|
28
|
-
'copyExclude?': 'string',
|
|
29
|
-
'sourceDir?': 'string',
|
|
30
|
-
'publicDir?': 'string',
|
|
31
|
-
'assetsDir?': 'string',
|
|
32
|
-
'styleImportBase?': 'string',
|
|
33
|
-
//publicDir?: string
|
|
34
|
-
//siteBasePath?: "" | `/${string}`,
|
|
35
|
-
'siteBasePath?': '"" | /^\\/.*$/',
|
|
36
|
-
'clientSideNav?': 'boolean',
|
|
37
|
-
'staticSite?': 'boolean',
|
|
1
|
+
// exactOptional, not optional, throughout: tsconfig has exactOptionalPropertyTypes, and optional()
|
|
2
|
+
// widens the value to include undefined, which FrameworkConfig's consumers then reject
|
|
3
|
+
import * as v from 'valibot';
|
|
4
|
+
// shape only - babel validates its own options, and a hand-copied key list just drifts from theirs
|
|
5
|
+
const babelParserOptionsSchema = v.pipe(v.looseObject({}),
|
|
6
|
+
// eslint-disable-next-line no-type-assertion/no-type-assertion -- see above
|
|
7
|
+
v.transform(options => options));
|
|
8
|
+
const stringOrStrings = v.union([v.string(), v.array(v.string())]);
|
|
9
|
+
const frameworkConfigBaseEntries = {
|
|
10
|
+
codeDir: v.exactOptional(v.string()),
|
|
11
|
+
copy: v.exactOptional(v.string()),
|
|
12
|
+
exclude: v.exactOptional(v.string()),
|
|
13
|
+
copyExclude: v.exactOptional(v.string()),
|
|
14
|
+
sourceDir: v.exactOptional(v.string()),
|
|
15
|
+
publicDir: v.exactOptional(v.string()),
|
|
16
|
+
assetsDir: v.exactOptional(v.string()),
|
|
17
|
+
styleImportBase: v.exactOptional(v.string()),
|
|
18
|
+
// valibot has no template-literal schema, so the regex proves what the cast then states
|
|
19
|
+
siteBasePath: v.exactOptional(v.pipe(v.union([v.literal(''), v.pipe(v.string(), v.regex(/^\/.*$/))]),
|
|
20
|
+
// eslint-disable-next-line no-type-assertion/no-type-assertion -- see above
|
|
21
|
+
v.transform(path => path))),
|
|
22
|
+
clientSideNav: v.exactOptional(v.boolean()),
|
|
23
|
+
staticSite: v.exactOptional(v.boolean()),
|
|
38
24
|
// not ready // uniqueComponents?: boolean
|
|
39
25
|
// not ready // bindMode?: BindMode
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
'babelOptions?': babelParserOptionsType,
|
|
46
|
-
'packageSystem?': '"rewrites" | "aliases"',
|
|
47
|
-
'ignoreDirs?': 'string | string[]',
|
|
26
|
+
routesLocation: v.exactOptional(v.tuple([v.string(), v.string()])),
|
|
27
|
+
envSchemasLocation: v.exactOptional(v.string()),
|
|
28
|
+
babelOptions: v.exactOptional(babelParserOptionsSchema),
|
|
29
|
+
packageSystem: v.exactOptional(v.picklist(['rewrites', 'aliases'])),
|
|
30
|
+
ignoreDirs: v.exactOptional(stringOrStrings),
|
|
48
31
|
// rename to ignoreFolders?
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
32
|
+
ignorePatterns: v.exactOptional(stringOrStrings),
|
|
33
|
+
// test files: left out of every compile unless `tungsten compile --tests` asks for them -
|
|
34
|
+
// the same two shapes as ignoreDirs/ignorePatterns
|
|
35
|
+
testDirs: v.exactOptional(stringOrStrings),
|
|
36
|
+
testPatterns: v.exactOptional(stringOrStrings),
|
|
37
|
+
skipPackageCompilation: v.exactOptional(stringOrStrings),
|
|
38
|
+
serverMiddleware: v.exactOptional(v.tuple([v.string(), v.string(), v.string()])),
|
|
39
|
+
apiRoutes: v.exactOptional(v.string()),
|
|
40
|
+
apiPath: v.exactOptional(v.string()),
|
|
41
|
+
csr: v.exactOptional(v.boolean())
|
|
42
|
+
};
|
|
43
|
+
const frameworkConfigWithRootComponentSchema = v.object({
|
|
44
|
+
...frameworkConfigBaseEntries,
|
|
45
|
+
useRootComponent: v.exactOptional(v.literal(true)),
|
|
46
|
+
rootComponent: v.string()
|
|
61
47
|
});
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
'useRootComponent': 'false'
|
|
48
|
+
const frameworkConfigWithoutRootComponentSchema = v.object({
|
|
49
|
+
...frameworkConfigBaseEntries,
|
|
50
|
+
useRootComponent: v.literal(false)
|
|
66
51
|
});
|
|
67
|
-
|
|
52
|
+
// variant, not union: a plain union reports one issue at the union itself ("Expected Object but
|
|
53
|
+
// received Object"), where this picks the branch by useRootComponent and reports its actual fields
|
|
54
|
+
export const frameworkConfigValidator = v.variant('useRootComponent', [frameworkConfigWithRootComponentSchema, frameworkConfigWithoutRootComponentSchema]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getUserConfig.d.ts","sourceRoot":"","sources":["../src/getUserConfig.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"getUserConfig.d.ts","sourceRoot":"","sources":["../src/getUserConfig.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;AAOxF,wBAAsB,aAAa,CAC/B,SAAS,GAAE,wBAAwB,GAAG,IAAW,EACjD,MAAM,GAAE,QAAQ,GAAG,OAAiB,GACrC,OAAO,CAAC,uBAAuB,CAAC,CAsBlC"}
|
package/dist/getUserConfig.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { ArkErrors } from 'arktype';
|
|
2
1
|
import { defaultConfig } from "./defaultConfig.js";
|
|
3
2
|
import { findUserConfig } from "./findUserConfig.js";
|
|
4
3
|
import { frameworkConfigValidator } from "./frameworkConfigSchema.js";
|
|
5
4
|
import { makeFinalConfig } from "./makeFinalConfig.js";
|
|
6
5
|
import { throwError } from "./throwError.js";
|
|
7
6
|
import { cliOut } from "./cliOutput.js";
|
|
7
|
+
import { validate } from "./standardSchema.js";
|
|
8
8
|
export async function getUserConfig(overrides = null, volume = 'noisy') {
|
|
9
9
|
const userConfigOrNull = await findUserConfig(process.cwd(), volume);
|
|
10
10
|
//frameworkCon
|
|
11
11
|
if (userConfigOrNull !== null) {
|
|
12
12
|
const userConfig = userConfigOrNull;
|
|
13
|
-
const result = frameworkConfigValidator
|
|
14
|
-
if (result
|
|
13
|
+
const result = await validate(frameworkConfigValidator, userConfig.data);
|
|
14
|
+
if (!result.success) {
|
|
15
15
|
cliOut('Config', userConfig);
|
|
16
|
-
throwError('Your tungsten config did not match the required format',
|
|
16
|
+
throwError('Your tungsten config did not match the required format', '\n' + result.problems);
|
|
17
17
|
}
|
|
18
|
-
const finalConfig = makeFinalConfig(result, overrides);
|
|
18
|
+
const finalConfig = makeFinalConfig(result.value, overrides);
|
|
19
19
|
//console.log('finalConfig', finalConfig)
|
|
20
20
|
return finalConfig;
|
|
21
21
|
//return makeFinalConfig(userConfig.data)
|
package/dist/moduleReloader.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"moduleReloader.d.ts","sourceRoot":"","sources":["../src/moduleReloader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"moduleReloader.d.ts","sourceRoot":"","sources":["../src/moduleReloader.ts"],"names":[],"mappings":""}
|
package/dist/moduleReloader.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
if (!import.meta.url.endsWith('?loader')) {
|
|
4
|
-
register(`${import.meta.url}?loader`);
|
|
5
|
-
}
|
|
6
|
-
const selfURL = import.meta.url.replace(/\?loader$/, '');
|
|
1
|
+
import { registerHooks, isBuiltin } from 'node:module';
|
|
2
|
+
const selfURL = import.meta.url;
|
|
7
3
|
const isSelf = url => url === selfURL;
|
|
8
4
|
const hasUrlScheme = specifier => /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(specifier);
|
|
9
5
|
const isBareImport = specifier => !specifier.startsWith('.') && !specifier.startsWith('/') && !specifier.startsWith('\\') && !hasUrlScheme(specifier);
|
|
@@ -11,21 +7,24 @@ const cachedModulePattern = /\._cached_\.(?:[cm]?js|[cm]?ts|jsx|tsx)$/i;
|
|
|
11
7
|
function isCachedModule(url) {
|
|
12
8
|
return cachedModulePattern.test(url.pathname);
|
|
13
9
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
// takes the raw url rather than a parsed one, so the check can run before any URL is constructed
|
|
11
|
+
const isStaticUrl = url => {
|
|
12
|
+
const lower = url.toLowerCase();
|
|
13
|
+
return lower.includes('/node_modules/') || lower.includes('/.packages/');
|
|
17
14
|
};
|
|
18
15
|
let id = 0;
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
// registerHooks (sync, in-thread) rather than register (a separate hooks thread): the thread costs
|
|
17
|
+
// ~0.54ms per resolution against ~0.02ms here, which over a dev startup's module count is ~800ms
|
|
18
|
+
const resolve = (specifier, context, nextResolve) => {
|
|
19
|
+
const result = nextResolve(specifier, context);
|
|
21
20
|
if (!isSelf(result.url) && !isBuiltin(result.url) && context.parentURL !== undefined) {
|
|
21
|
+
// string tests before parsing: registerHooks also sees CJS require, where register did not,
|
|
22
|
+
// and a package's own relative requires are the bulk of those - none can need tagging
|
|
23
|
+
if (isBareImport(specifier) || isStaticUrl(result.url)) return result;
|
|
22
24
|
const url = new URL(result.url);
|
|
23
25
|
// A *._cached_.js/ts module and its dependency graph use Node's ordinary
|
|
24
26
|
// process-wide module cache instead of inheriting the test file instance.
|
|
25
27
|
if (isCachedModule(url)) return result;
|
|
26
|
-
if (isBareImport(specifier) || isStaticModule(url)) {
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
29
28
|
const parentUrl = new URL(context.parentURL);
|
|
30
29
|
const instance = url.searchParams.get('t-reload') === '' ? `esm-reload-${id++}` : parentUrl.searchParams.get('t-instance');
|
|
31
30
|
if (instance !== null) {
|
|
@@ -40,4 +39,7 @@ const resolve = async (specifier, context, nextResolve) => {
|
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
return result;
|
|
43
|
-
};
|
|
42
|
+
};
|
|
43
|
+
registerHooks({
|
|
44
|
+
resolve
|
|
45
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type StandardSchemaPathSegment = {
|
|
2
|
+
readonly key: PropertyKey;
|
|
3
|
+
};
|
|
4
|
+
export type StandardSchemaIssue = {
|
|
5
|
+
readonly message: string;
|
|
6
|
+
readonly path?: readonly (PropertyKey | StandardSchemaPathSegment)[] | undefined;
|
|
7
|
+
};
|
|
8
|
+
export type StandardSchemaResult<Output> = {
|
|
9
|
+
readonly value: Output;
|
|
10
|
+
readonly issues?: undefined;
|
|
11
|
+
} | {
|
|
12
|
+
readonly issues: readonly StandardSchemaIssue[];
|
|
13
|
+
};
|
|
14
|
+
export type StandardSchema<Output> = {
|
|
15
|
+
readonly '~standard': {
|
|
16
|
+
readonly version: 1;
|
|
17
|
+
readonly vendor: string;
|
|
18
|
+
readonly validate: (value: unknown) => StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;
|
|
19
|
+
readonly types?: {
|
|
20
|
+
readonly input: unknown;
|
|
21
|
+
readonly output: Output;
|
|
22
|
+
} | undefined;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
type Validation<Output> = {
|
|
26
|
+
success: true;
|
|
27
|
+
value: Output;
|
|
28
|
+
} | {
|
|
29
|
+
success: false;
|
|
30
|
+
problems: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function validate<Output>(schema: StandardSchema<Output>, value: unknown): Promise<Validation<Output>>;
|
|
33
|
+
export declare function formatIssues(issues: readonly StandardSchemaIssue[]): string;
|
|
34
|
+
export declare function checkIsObject(location: string, name: string, value: unknown): void;
|
|
35
|
+
export declare function checkIsStandardSchema(location: string, name: string, schema: unknown): void;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=standardSchema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standardSchema.d.ts","sourceRoot":"","sources":["../src/standardSchema.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,yBAAyB,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAA;CAAE,CAAA;AAErE,MAAM,MAAM,mBAAmB,GAAG;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,yBAAyB,CAAC,EAAE,GAAG,SAAS,CAAA;CACnF,CAAA;AAED,MAAM,MAAM,oBAAoB,CAAC,MAAM,IACnC;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAA;CAAE,CAAA;AAEzD,MAAM,MAAM,cAAc,CAAC,MAAM,IAAI;IACjC,QAAQ,CAAC,WAAW,EAAE;QAClB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;QACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,QAAQ,EACjB,CAAC,KAAK,EAAE,OAAO,KAAK,oBAAoB,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAA;QACxF,QAAQ,CAAC,KAAK,CAAC,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,SAAS,CAAA;KACpF,CAAA;CACJ,CAAA;AAED,KAAK,UAAU,CAAC,MAAM,IAAI;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AAGjG,wBAAsB,QAAQ,CAAC,MAAM,EACjC,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,GAC/C,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAI7B;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,GAAG,MAAM,CAE3E;AAWD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAKlF;AAGD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAgB3F"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Standard Schema v1 (https://standardschema.dev), inlined rather than taken from
|
|
2
|
+
// @standard-schema/spec so nothing new lands in tungsten's shipped types. Only the output type is
|
|
3
|
+
// parameterised - the input is always unknown, whether it is process.env or a user's config file.
|
|
4
|
+
import { throwError } from "./throwError.js";
|
|
5
|
+
// safeParse-shaped, but awaited: the spec lets a schema validate asynchronously
|
|
6
|
+
export async function validate(schema, value) {
|
|
7
|
+
const result = await schema['~standard'].validate(value);
|
|
8
|
+
if (result.issues !== undefined) return {
|
|
9
|
+
success: false,
|
|
10
|
+
problems: formatIssues(result.issues)
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
success: true,
|
|
14
|
+
value: result.value
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function formatIssues(issues) {
|
|
18
|
+
return issues.map(issue => ` ${pathOf(issue)}: ${issue.message}`).join('\n');
|
|
19
|
+
}
|
|
20
|
+
// a segment is either the key itself or an object wrapping it - both spellings are in the spec
|
|
21
|
+
function pathOf(issue) {
|
|
22
|
+
const segments = issue.path?.map(segment => String(typeof segment === 'object' ? segment.key : segment)) ?? [];
|
|
23
|
+
return segments.length === 0 ? '(whole value)' : segments.join('.');
|
|
24
|
+
}
|
|
25
|
+
// Standard Schema exposes nothing about a schema's structure, so "is it an object schema?" cannot
|
|
26
|
+
// be asked up front the way ZodObject allowed - this asks what it actually produced instead
|
|
27
|
+
export function checkIsObject(location, name, value) {
|
|
28
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
29
|
+
throwError(location, `${name} must produce an object,` + ` but it produced ${Array.isArray(value) ? 'an array' : `type '${typeof value}'`}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// throws rather than returning a result, for the load-time check where there is nothing to recover to
|
|
33
|
+
export function checkIsStandardSchema(location, name, schema) {
|
|
34
|
+
if (schema === null || typeof schema !== 'object') {
|
|
35
|
+
throwError(location, `${name} is not a schema - found ${typeof schema}`);
|
|
36
|
+
}
|
|
37
|
+
if (!('~standard' in schema)) {
|
|
38
|
+
throwError(location, `${name} does not implement Standard Schema (https://standardschema.dev).` + ` Use a recent zod, valibot, arktype or other supporting library.`);
|
|
39
|
+
}
|
|
40
|
+
const props = schema['~standard'];
|
|
41
|
+
if (props === null || typeof props !== 'object' || !('version' in props)) {
|
|
42
|
+
throwError(location, `${name} has a malformed '~standard' property`);
|
|
43
|
+
}
|
|
44
|
+
if (props.version !== 1) {
|
|
45
|
+
throwError(location, `${name} implements Standard Schema v${String(props.version)},` + ` and tungsten supports v1`);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/dist/timeProfiler.d.ts
CHANGED
|
@@ -2,5 +2,11 @@ export type TimeProfiler = {
|
|
|
2
2
|
timeProfile: <T>(name: string, task: () => T) => T;
|
|
3
3
|
timeProfileAsync: <T>(name: string, task: () => T | Promise<T>) => Promise<T>;
|
|
4
4
|
};
|
|
5
|
+
export declare function isProfiling(): boolean;
|
|
6
|
+
export declare function beginStartupProfiling(profiling: boolean): void;
|
|
7
|
+
export declare function reportStartupPhase(name: string): void;
|
|
8
|
+
export declare function tally(name: string, ms: number): void;
|
|
9
|
+
export declare function addEndOfRunReport(report: () => void): void;
|
|
10
|
+
export declare function reportRunTotal(name: string): void;
|
|
5
11
|
export declare function createTimeProfiler(profiling?: boolean): TimeProfiler;
|
|
6
12
|
//# sourceMappingURL=timeProfiler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeProfiler.d.ts","sourceRoot":"","sources":["../src/timeProfiler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"timeProfiler.d.ts","sourceRoot":"","sources":["../src/timeProfiler.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,YAAY,GAAG;IACvB,WAAW,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;IAClD,gBAAgB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;CAChF,CAAA;AA+MD,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAGD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAG9D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAKrD;AAOD,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAKpD;AA0BD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,IAAI,CAE1D;AAGD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CASjD;AAED,wBAAgB,kBAAkB,CAAC,SAAS,UAAO,GAAG,YAAY,CAoCjE"}
|
package/dist/timeProfiler.js
CHANGED
|
@@ -1,11 +1,239 @@
|
|
|
1
1
|
// Same shape as sxy-test-runner's createTimeProfiler (node_modules/sxy-test-runner/dist/packages/timeProfiler.js) -
|
|
2
2
|
// a disabled profiler just runs the task, so callers never need to branch on whether profiling
|
|
3
|
-
// is on. Framework.ts holds one instance per run (default disabled); tungsten dev/build
|
|
4
|
-
// it via --profile and wrap their own top-level phases with it too.
|
|
3
|
+
// is on. Framework.ts holds one instance per run (default disabled); tungsten dev/build/compile
|
|
4
|
+
// enable it via --profile and wrap their own top-level phases with it too.
|
|
5
|
+
import { performance } from 'node:perf_hooks';
|
|
6
|
+
import { AsyncLocalStorage, createHook } from 'node:async_hooks';
|
|
5
7
|
import chalk from 'chalk-extensions';
|
|
6
8
|
import { cliOut } from "./cliOutput.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
// Nesting has to be recorded, not inferred: sibling phases started together sit inside each other's
|
|
10
|
+
// time windows, so intervals alone cannot tell a nested call from a concurrent one. Async storage
|
|
11
|
+
// gives each task its own phase even when several run at once.
|
|
12
|
+
const currentPhase = new AsyncLocalStorage();
|
|
13
|
+
const framesInFlight = [];
|
|
14
|
+
const resourceOwner = new Map();
|
|
15
|
+
let accounting = false;
|
|
16
|
+
// Attributes each async callback's synchronous run to the phase that created the resource. Only
|
|
17
|
+
// under --profile: the hooks fire on every async operation and are not free.
|
|
18
|
+
function startCpuAccounting() {
|
|
19
|
+
if (accounting) return;
|
|
20
|
+
accounting = true;
|
|
21
|
+
createHook({
|
|
22
|
+
init(asyncId, kind) {
|
|
23
|
+
const phase = currentPhase.getStore();
|
|
24
|
+
if (phase === undefined) return;
|
|
25
|
+
resourceOwner.set(asyncId, {
|
|
26
|
+
phase,
|
|
27
|
+
created: performance.now(),
|
|
28
|
+
kind
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
before(asyncId) {
|
|
32
|
+
const owner = resourceOwner.get(asyncId);
|
|
33
|
+
if (owner === undefined) return;
|
|
34
|
+
const now = performance.now();
|
|
35
|
+
const wait = owner.phase.waits.get(owner.kind) ?? {
|
|
36
|
+
ms: 0,
|
|
37
|
+
count: 0
|
|
38
|
+
};
|
|
39
|
+
wait.ms += now - owner.created;
|
|
40
|
+
wait.count++;
|
|
41
|
+
owner.phase.waits.set(owner.kind, wait);
|
|
42
|
+
framesInFlight.push({
|
|
43
|
+
phase: owner.phase,
|
|
44
|
+
start: now,
|
|
45
|
+
childCpu: 0
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
after(asyncId) {
|
|
49
|
+
if (!resourceOwner.has(asyncId)) return;
|
|
50
|
+
const frame = framesInFlight.pop();
|
|
51
|
+
if (frame === undefined) return;
|
|
52
|
+
const total = performance.now() - frame.start;
|
|
53
|
+
const own = total - frame.childCpu;
|
|
54
|
+
frame.phase.cpu += own;
|
|
55
|
+
// deferred work: started here, ran after this phase returned
|
|
56
|
+
if (frame.phase.finished) frame.phase.cpuAfterEnd += own;
|
|
57
|
+
const parent = framesInFlight[framesInFlight.length - 1];
|
|
58
|
+
if (parent !== undefined) parent.childCpu += total;
|
|
59
|
+
},
|
|
60
|
+
destroy(asyncId) {
|
|
61
|
+
resourceOwner.delete(asyncId);
|
|
62
|
+
}
|
|
63
|
+
}).enable();
|
|
64
|
+
}
|
|
65
|
+
// Every phase reported in the run. performance.now() is process-relative, so startup phases and
|
|
66
|
+
// each profiler's phases share one timeline - which is what makes the overlaps legible: several
|
|
67
|
+
// of these phases run concurrently (the compile targets, and the wrappers around them), so their
|
|
68
|
+
// durations deliberately do not sum to the total.
|
|
69
|
+
const phases = [];
|
|
70
|
+
function ms(value) {
|
|
71
|
+
return `${Math.round(value).toLocaleString()}ms`;
|
|
72
|
+
}
|
|
73
|
+
// Created when the phase starts, so async resources made inside it can be charged to it while it
|
|
74
|
+
// runs; end is filled in by finishPhase().
|
|
75
|
+
function startPhase(name, start, parent) {
|
|
76
|
+
const phase = {
|
|
77
|
+
name,
|
|
78
|
+
start,
|
|
79
|
+
end: start,
|
|
80
|
+
depth: parent === undefined ? 0 : parent.depth + 1,
|
|
81
|
+
cpu: 0,
|
|
82
|
+
childSyncCpu: 0,
|
|
83
|
+
cpuAfterEnd: 0,
|
|
84
|
+
finished: false,
|
|
85
|
+
parent,
|
|
86
|
+
waits: new Map()
|
|
87
|
+
};
|
|
88
|
+
phases.push(phase);
|
|
89
|
+
return phase;
|
|
90
|
+
}
|
|
91
|
+
function finishPhase(phase, end) {
|
|
92
|
+
phase.end = end;
|
|
93
|
+
phase.finished = true;
|
|
94
|
+
cliOut(chalk.yellow`${phase.name}` + ' - ' + chalk.orange`${ms(end - phase.start)}`
|
|
95
|
+
// the window, not just the duration: concurrent phases share one, and a flat list of
|
|
96
|
+
// durations reads as if they ran one after another
|
|
97
|
+
+ chalk.mediumgrey` (${Math.round(phase.start).toLocaleString()} - ${ms(end)})`);
|
|
98
|
+
}
|
|
99
|
+
// The startup phases - node's boot and the command's module evaluation. They run before any phase
|
|
100
|
+
// context exists, so the hooks never see them; nothing else is on the loop then either, which makes
|
|
101
|
+
// their elapsed time their cpu time and keeps the cpu column summing to the run total.
|
|
102
|
+
function report(name, start, end) {
|
|
103
|
+
const phase = startPhase(name, start, undefined);
|
|
104
|
+
phase.cpu = end - start;
|
|
105
|
+
finishPhase(phase, end);
|
|
106
|
+
}
|
|
107
|
+
// A bar alone could not say whether two phases nest or run alongside each other, and at any width a
|
|
108
|
+
// cell is worth enough milliseconds that short phases get padded and then misreport their duration.
|
|
109
|
+
// So the structure is carried by indentation and the "with" note, and the bar only shows placement -
|
|
110
|
+
// drawn thin where it is padding rather than duration, so it cannot be read as a real span.
|
|
111
|
+
function reportTimeline() {
|
|
112
|
+
if (phases.length === 0) return;
|
|
113
|
+
// depth first from the roots, each level in start order - a flat sort by start puts a phase
|
|
114
|
+
// under whatever happened to run before it, which reads as the wrong parent
|
|
115
|
+
const childrenOf = new Map();
|
|
116
|
+
for (const phase of phases) {
|
|
117
|
+
const siblings = childrenOf.get(phase.parent) ?? [];
|
|
118
|
+
siblings.push(phase);
|
|
119
|
+
childrenOf.set(phase.parent, siblings);
|
|
120
|
+
}
|
|
121
|
+
const ordered = [];
|
|
122
|
+
const addBranch = phase => {
|
|
123
|
+
ordered.push(phase);
|
|
124
|
+
for (const child of [...(childrenOf.get(phase) ?? [])].sort((a, b) => a.start - b.start)) {
|
|
125
|
+
addBranch(child);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
for (const root of [...(childrenOf.get(undefined) ?? [])].sort((a, b) => a.start - b.start)) {
|
|
129
|
+
addBranch(root);
|
|
130
|
+
}
|
|
131
|
+
const labelWidth = Math.max(...ordered.map(phase => phase.name.length + phase.depth * 2));
|
|
132
|
+
const total = Math.max(...ordered.map(phase => phase.end));
|
|
133
|
+
// the terminal's width, less the label, the duration and room for the notes after the bar.
|
|
134
|
+
// columns is typed as a number but is undefined when stdout is not a tty, hence Number()
|
|
135
|
+
const columns = Number(process.stdout.columns) || 100;
|
|
136
|
+
const barWidth = Math.max(20, Math.min(90, columns - labelWidth - 34));
|
|
137
|
+
// everything before the notes: indent, label, elapsed, cpu, their separators, and the bar
|
|
138
|
+
const rowWidth = 2 + labelWidth + 1 + 9 + 1 + 13 + 1 + barWidth;
|
|
139
|
+
cliOut('');
|
|
140
|
+
cliOut(chalk.mediumgrey`phases - indented by nesting. elapsed is wall clock and overlaps; cpu is` + chalk.mediumgrey` work started by this phase, wherever it ran, and sums to the run total`);
|
|
141
|
+
for (const phase of ordered) {
|
|
142
|
+
// cpu is charged to the phase that created the work, so this is already exclusive of child
|
|
143
|
+
// phases once their synchronous starts are removed - unlike elapsed, these figures do sum
|
|
144
|
+
const ownCpu = phase.cpu - phase.childSyncCpu;
|
|
145
|
+
const from = Math.min(Math.round(phase.start / total * barWidth), barWidth - 1);
|
|
146
|
+
const to = Math.min(Math.max(Math.round(phase.end / total * barWidth), from + 1), barWidth);
|
|
147
|
+
const glyph = (phase.end - phase.start) / total * barWidth < 1 ? '▏' : '█';
|
|
148
|
+
const label = ' '.repeat(phase.depth) + phase.name;
|
|
149
|
+
// Solid for the phase's own cpu, hollow for the rest of its span - a phase that is mostly
|
|
150
|
+
// hollow was mostly waiting on the thread rather than using it. A proportion, not a
|
|
151
|
+
// position: the cpu is interleaved across the span, not spent at the front of it.
|
|
152
|
+
const cells = to - from;
|
|
153
|
+
const workCells = Math.min(cells, Math.max(ownCpu > 0 ? 1 : 0, Math.round(ownCpu / total * barWidth)));
|
|
154
|
+
const bar = glyph === '▏' ? glyph : chalk.orange`${'█'.repeat(workCells)}` + chalk.mediumgrey`${'░'.repeat(cells - workCells)}`;
|
|
155
|
+
// Work is charged to the phase that created the resource, wherever it later runs, so a
|
|
156
|
+
// phase that queues work for something else to await can report more cpu than it existed
|
|
157
|
+
// for - the excess ran after it returned, and whatever awaited it will read as ~0.
|
|
158
|
+
// the kind of async work the phase spent longest waiting on, named as node names it
|
|
159
|
+
const [waitedOn] = [...phase.waits].sort((a, b) => b[1].ms - a[1].ms);
|
|
160
|
+
const fullNotes = (phase.cpuAfterEnd > 1 ? ` ${ms(phase.cpuAfterEnd)} of it deferred` : '') + (waitedOn !== undefined && waitedOn[1].ms > 1 ? ` waits ${waitedOn[0]} x${waitedOn[1].count}` : '');
|
|
161
|
+
// trimmed to what is left of the line - a wrapped note breaks the column alignment of
|
|
162
|
+
// every row after it, which is worse than losing the end of one label
|
|
163
|
+
const notes = fullNotes.slice(0, Math.max(0, columns - rowWidth - 1));
|
|
164
|
+
cliOut(' ' + chalk.yellow`${label.padEnd(labelWidth)}` + ' ' + chalk.orange`${ms(phase.end - phase.start).padStart(9)}` + ' ' + chalk.mediumgrey`${`cpu ${ms(ownCpu)}`.padStart(13)}` + ' ' + ' '.repeat(from) + bar
|
|
165
|
+
// only pad out to the full bar when something follows it
|
|
166
|
+
+ (notes === '' ? '' : ' '.repeat(barWidth - to) + chalk.mediumgrey`${notes}`));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Phases no Framework profiler can see: node's own boot, then evaluating the import tree of the
|
|
170
|
+
// command module bin/index.ts dynamically imports. performance.now() counts from process start,
|
|
171
|
+
// so the first phase reported measures from there; state is module-level because index.ts starts
|
|
172
|
+
// the timing and the command module it imports finishes it.
|
|
173
|
+
let startupProfiling = false;
|
|
174
|
+
let lastPhaseEnd = 0;
|
|
175
|
+
// Whether --profile is on, for the instrumentation deeper in the build to consult directly. A
|
|
176
|
+
// global rather than a threaded parameter: profiling is a diagnostic, and carrying its switches
|
|
177
|
+
// through every signature between the CLI and a babel visitor would touch code that never uses them.
|
|
178
|
+
export function isProfiling() {
|
|
179
|
+
return startupProfiling;
|
|
180
|
+
}
|
|
181
|
+
// call from the CLI entry once the command is known
|
|
182
|
+
export function beginStartupProfiling(profiling) {
|
|
183
|
+
startupProfiling = profiling;
|
|
184
|
+
reportStartupPhase('node boot + cli entry');
|
|
185
|
+
}
|
|
186
|
+
export function reportStartupPhase(name) {
|
|
187
|
+
if (!startupProfiling) return;
|
|
188
|
+
const now = performance.now();
|
|
189
|
+
report(name, lastPhaseEnd, now);
|
|
190
|
+
lastPhaseEnd = now;
|
|
191
|
+
}
|
|
192
|
+
// Per-operation totals, for pricing one call inside a phase - a phase says where time went, this
|
|
193
|
+
// says what it went on. Accumulating is two clock reads, so callers need not check whether
|
|
194
|
+
// profiling is on; only the report is gated.
|
|
195
|
+
const tallies = new Map();
|
|
196
|
+
export function tally(name, ms) {
|
|
197
|
+
const entry = tallies.get(name) ?? {
|
|
198
|
+
calls: 0,
|
|
199
|
+
ms: 0
|
|
200
|
+
};
|
|
201
|
+
entry.calls++;
|
|
202
|
+
entry.ms += ms;
|
|
203
|
+
tallies.set(name, entry);
|
|
204
|
+
}
|
|
205
|
+
// Deliberately no async variant. Concurrent operations overlap, so summing their wall clock counts
|
|
206
|
+
// the same time once per operation - 291 parallel transforms tallied as 689,576ms of an 11s run.
|
|
207
|
+
// Time async work with a phase, whose cpu figure is attributed rather than summed.
|
|
208
|
+
function reportTallies() {
|
|
209
|
+
if (tallies.size === 0) return;
|
|
210
|
+
cliOut('');
|
|
211
|
+
cliOut(chalk.mediumgrey`tallied work - synchronous totals across the run, nested counted in both`);
|
|
212
|
+
const nameWidth = Math.max(...[...tallies.keys()].map(name => name.length));
|
|
213
|
+
for (const [name, {
|
|
214
|
+
calls,
|
|
215
|
+
ms: total
|
|
216
|
+
}] of [...tallies].sort((a, b) => b[1].ms - a[1].ms)) {
|
|
217
|
+
cliOut(' ' + chalk.yellow`${name.padEnd(nameWidth)}` + ' ' + chalk.orange`${ms(total).padStart(10)}` + ' ' + chalk.mediumgrey`${`${calls.toLocaleString()} calls`.padStart(13)}` + ' ' + chalk.mediumgrey`${`${(total / calls).toFixed(2)}ms each`}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Other reports to print where the run total goes - the module load analysis registers itself here.
|
|
221
|
+
// It cannot wait for process exit: dev and compile --watch keep running and are killed by a signal,
|
|
222
|
+
// which skips exit handlers entirely.
|
|
223
|
+
const endOfRunReports = [];
|
|
224
|
+
export function addEndOfRunReport(report) {
|
|
225
|
+
endOfRunReports.push(report);
|
|
226
|
+
}
|
|
227
|
+
// wall clock for the whole command, node boot and module loading included
|
|
228
|
+
export function reportRunTotal(name) {
|
|
229
|
+
const total = performance.now();
|
|
230
|
+
// --profile-modules works on its own, so the extra reports are not gated on --profile
|
|
231
|
+
if (startupProfiling) {
|
|
232
|
+
reportTimeline();
|
|
233
|
+
reportTallies();
|
|
234
|
+
cliOut(chalk.yellow`${name}` + ' - ' + chalk.orange`${ms(total)}`);
|
|
235
|
+
}
|
|
236
|
+
for (const report of endOfRunReports) report();
|
|
9
237
|
}
|
|
10
238
|
export function createTimeProfiler(profiling = true) {
|
|
11
239
|
if (!profiling) {
|
|
@@ -14,17 +242,32 @@ export function createTimeProfiler(profiling = true) {
|
|
|
14
242
|
timeProfileAsync: async (_name, task) => task()
|
|
15
243
|
};
|
|
16
244
|
}
|
|
245
|
+
startCpuAccounting();
|
|
17
246
|
return {
|
|
18
247
|
timeProfile(name, task) {
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
248
|
+
const parent = currentPhase.getStore();
|
|
249
|
+
const start = performance.now();
|
|
250
|
+
const phase = startPhase(name, start, parent);
|
|
251
|
+
const result = currentPhase.run(phase, task);
|
|
252
|
+
const end = performance.now();
|
|
253
|
+
// wholly synchronous, so none of it crosses an async callback for the hooks to charge
|
|
254
|
+
phase.cpu += end - start;
|
|
255
|
+
if (parent !== undefined) parent.childSyncCpu += end - start;
|
|
256
|
+
finishPhase(phase, end);
|
|
22
257
|
return result;
|
|
23
258
|
},
|
|
24
259
|
async timeProfileAsync(name, task) {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
260
|
+
const parent = currentPhase.getStore();
|
|
261
|
+
const start = performance.now();
|
|
262
|
+
const phase = startPhase(name, start, parent);
|
|
263
|
+
// the task runs synchronously up to its first await, and that stretch belongs to this
|
|
264
|
+
// phase too - the hooks only see work that arrives through an async callback
|
|
265
|
+
const pending = currentPhase.run(phase, task);
|
|
266
|
+
const prefix = performance.now() - start;
|
|
267
|
+
phase.cpu += prefix;
|
|
268
|
+
if (parent !== undefined) parent.childSyncCpu += prefix;
|
|
269
|
+
const result = await pending;
|
|
270
|
+
finishPhase(phase, performance.now());
|
|
28
271
|
return result;
|
|
29
272
|
}
|
|
30
273
|
};
|