@pikku/core 0.12.136 → 0.12.137
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/CHANGELOG.md +10 -0
- package/dist/services/meta-service.d.ts +11 -0
- package/dist/services/meta-service.js +10 -1
- package/dist/wirings/persona/persona-app-scopes.js +1 -0
- package/dist/wirings/scope/index.d.ts +1 -1
- package/dist/wirings/scope/scope.types.d.ts +14 -0
- package/dist/wirings/scope/validate-scope-definitions.js +1 -0
- package/dist/wirings/workflow/scenario-coverage.d.ts +70 -0
- package/dist/wirings/workflow/scenario-coverage.js +159 -0
- package/package.json +2 -1
- package/src/public-surface.json +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 0.12.137
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 0c865d9: Changes and changesets replace milestones. The changes commands run on a local queue when the checkout has no fabric project; `pikku changes next` routes to a changes, upgrade or knowledge agent and merges finished changesets. A changeset that creates or alters a table, or has 6 or more changes, needs a plan at `knowledge/plans/<changeset>.plan.json` (anything else goes to an optional judge at `PIKKU_PLAN_JUDGE_URL`), and `changes done` holds it to that plan. `pikku knowledge gaps` replaces `knowledge next`/`reconcile`, and also reports code a merged plan built that is gone and notes that were deleted.
|
|
6
|
+
- c36b066: Scenario coverage moves into OSS. `@pikku/core/scenario/coverage` adds `readScenarioCoverage`, which reports the lines no scenario reaches, the mutations no scenario drives, and the pages scenarios open. It is exposed through `pikku scenario coverage`, `console:getScenarioCoverage`, and a "Not tested by any scenario" card on the Scenarios page. `LocalMetaService.getRpcMeta` now reads the `.internal.gen.json` file that codegen actually writes.
|
|
7
|
+
- 0963b73: Every scope tree now records where it came from: `origin` on `ScopeDefinitionMeta` is `{ kind: 'app' }` for the app's own `defineScope`, `{ kind: 'generated' }` for a tree the CLI wrote into the project (a `.gen.ts`, or the `app` root derived from personas), and `{ kind: 'addon', package, displayName }` for one an installed addon declared. An addon's build stamps its own package and display name on its trees, and the host keeps that name while recording the package it actually installed. `MetaService.getScopesMeta()` reads the result, and the console's all-meta payload carries it as `scopes`.
|
|
8
|
+
|
|
9
|
+
The console's Scopes page becomes **Permissions** and is grouped by that origin: the app's own permissions first, then what Pikku generated into the project, then one card per addon (Pikku's own before third-party, each alphabetical), each card folding to its header — the app's own starts open, every addon's starts folded, and any search or filter opens them all. Above the cards, two filters narrow the page to what one role is given (or to what no role is given yet) and to one source. A permission group no longer says "Not part of any role yet" above lines that are each given to a role.
|
|
10
|
+
|
|
1
11
|
## 0.12.136
|
|
2
12
|
|
|
3
13
|
### Patch Changes
|
|
@@ -10,6 +10,7 @@ import type { FeaturesMeta } from '../wirings/workflow/scenario.types.js';
|
|
|
10
10
|
import type { ResolvedPersona } from './personas-service.js';
|
|
11
11
|
import type { SystemRoleDefinitionsMeta } from '../wirings/role/role.types.js';
|
|
12
12
|
import type { FeatureFlagDefinitionsMeta } from '../wirings/flag/flag.types.js';
|
|
13
|
+
import type { ScopeDefinitionsMeta } from '../wirings/scope/scope.types.js';
|
|
13
14
|
import type { AnalyticsEventsMeta } from '../analytics/analytics.types.js';
|
|
14
15
|
import type { TriggerMeta, TriggerSourceMeta } from '../wirings/trigger/trigger.types.js';
|
|
15
16
|
import type { SecretDefinitionsMeta } from '../wirings/secret/secret.types.js';
|
|
@@ -156,6 +157,14 @@ export interface MetaService {
|
|
|
156
157
|
* seed granted from rather than a second one.
|
|
157
158
|
*/
|
|
158
159
|
getSystemRolesMeta(): Promise<SystemRoleDefinitionsMeta>;
|
|
160
|
+
/**
|
|
161
|
+
* The scope trees declared with `defineScope`, keyed by root, each carrying
|
|
162
|
+
* where it came from.
|
|
163
|
+
*
|
|
164
|
+
* The scope store holds the grantable ids; this holds what the store does
|
|
165
|
+
* not — a root's display name and which addon, if any, declared it.
|
|
166
|
+
*/
|
|
167
|
+
getScopesMeta(): Promise<ScopeDefinitionsMeta>;
|
|
159
168
|
/**
|
|
160
169
|
* The flags declared with `defineFeatureFlags`, keyed by name.
|
|
161
170
|
*
|
|
@@ -199,6 +208,7 @@ export declare class LocalMetaService implements MetaService {
|
|
|
199
208
|
private workflowMetaCache;
|
|
200
209
|
private personasMetaCache;
|
|
201
210
|
private systemRolesMetaCache;
|
|
211
|
+
private scopesMetaCache;
|
|
202
212
|
private featureFlagsMetaCache;
|
|
203
213
|
private analyticsMetaCache;
|
|
204
214
|
private featuresMetaCache;
|
|
@@ -236,6 +246,7 @@ export declare class LocalMetaService implements MetaService {
|
|
|
236
246
|
getWorkflowMeta(): Promise<WorkflowsMeta>;
|
|
237
247
|
getPersonasMeta(): Promise<Record<string, ResolvedPersona>>;
|
|
238
248
|
getSystemRolesMeta(): Promise<SystemRoleDefinitionsMeta>;
|
|
249
|
+
getScopesMeta(): Promise<ScopeDefinitionsMeta>;
|
|
239
250
|
getFeatureFlagsMeta(): Promise<FeatureFlagDefinitionsMeta>;
|
|
240
251
|
getAnalyticsMeta(): Promise<AnalyticsEventsMeta>;
|
|
241
252
|
getFeaturesMeta(): Promise<FeaturesMeta>;
|
|
@@ -16,6 +16,7 @@ export class LocalMetaService {
|
|
|
16
16
|
workflowMetaCache = null;
|
|
17
17
|
personasMetaCache = null;
|
|
18
18
|
systemRolesMetaCache = null;
|
|
19
|
+
scopesMetaCache = null;
|
|
19
20
|
featureFlagsMetaCache = null;
|
|
20
21
|
analyticsMetaCache = null;
|
|
21
22
|
featuresMetaCache = null;
|
|
@@ -115,6 +116,7 @@ export class LocalMetaService {
|
|
|
115
116
|
this.workflowMetaCache = null;
|
|
116
117
|
this.personasMetaCache = null;
|
|
117
118
|
this.systemRolesMetaCache = null;
|
|
119
|
+
this.scopesMetaCache = null;
|
|
118
120
|
this.featureFlagsMetaCache = null;
|
|
119
121
|
this.analyticsMetaCache = null;
|
|
120
122
|
this.featuresMetaCache = null;
|
|
@@ -216,7 +218,7 @@ export class LocalMetaService {
|
|
|
216
218
|
if (this.rpcMetaCache)
|
|
217
219
|
return this.rpcMetaCache;
|
|
218
220
|
try {
|
|
219
|
-
const content = await this.readFile('rpc/pikku-rpc-wirings-meta.gen.json');
|
|
221
|
+
const content = await this.readFile('rpc/pikku-rpc-wirings-meta.internal.gen.json');
|
|
220
222
|
this.rpcMetaCache = content ? JSON.parse(content) : {};
|
|
221
223
|
return this.rpcMetaCache;
|
|
222
224
|
}
|
|
@@ -278,6 +280,13 @@ export class LocalMetaService {
|
|
|
278
280
|
this.systemRolesMetaCache = content ? JSON.parse(content) : {};
|
|
279
281
|
return this.systemRolesMetaCache;
|
|
280
282
|
}
|
|
283
|
+
async getScopesMeta() {
|
|
284
|
+
if (this.scopesMetaCache)
|
|
285
|
+
return this.scopesMetaCache;
|
|
286
|
+
const content = await this.readFile('scopes/pikku-scopes-meta.gen.json');
|
|
287
|
+
this.scopesMetaCache = content ? JSON.parse(content) : {};
|
|
288
|
+
return this.scopesMetaCache;
|
|
289
|
+
}
|
|
281
290
|
async getFeatureFlagsMeta() {
|
|
282
291
|
if (this.featureFlagsMetaCache)
|
|
283
292
|
return this.featureFlagsMetaCache;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { defineScope } from './define-scope.js';
|
|
2
2
|
export { flattenScopeDefinitions, validateAndBuildScopeDefinitionsMeta, } from './validate-scope-definitions.js';
|
|
3
|
-
export type { CoreScopes, CoreScopeNode, FlatScope, ScopeDefinitionMeta, ScopeDefinitionsMeta, ScopeDefinitions, ScopeNodeMeta, } from './scope.types.js';
|
|
3
|
+
export type { CoreScopes, CoreScopeNode, FlatScope, ScopeDefinitionMeta, ScopeDefinitionsMeta, ScopeDefinitions, ScopeNodeMeta, ScopeOrigin, } from './scope.types.js';
|
|
4
4
|
export { hasScopes, verifyScopes } from '../../scopes.js';
|
|
@@ -21,12 +21,26 @@ export type ScopeNodeMeta = {
|
|
|
21
21
|
description?: string;
|
|
22
22
|
scopes?: Record<string, ScopeNodeMeta>;
|
|
23
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Who put a scope tree into the app: the app's own `defineScope`, a file the
|
|
26
|
+
* CLI generated into the project, or an installed addon.
|
|
27
|
+
*/
|
|
28
|
+
export type ScopeOrigin = {
|
|
29
|
+
kind: 'app';
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'generated';
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'addon';
|
|
34
|
+
package: string;
|
|
35
|
+
displayName?: string;
|
|
36
|
+
};
|
|
24
37
|
export type ScopeDefinitionMeta = {
|
|
25
38
|
name: string;
|
|
26
39
|
displayName?: string;
|
|
27
40
|
description?: string;
|
|
28
41
|
scopes?: Record<string, ScopeNodeMeta>;
|
|
29
42
|
sourceFile?: string;
|
|
43
|
+
origin?: ScopeOrigin;
|
|
30
44
|
};
|
|
31
45
|
export type ScopeDefinitions = ScopeDefinitionMeta[];
|
|
32
46
|
export type ScopeDefinitionsMeta = Record<string, ScopeDefinitionMeta>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { MetaService } from '../../services/meta-service.js';
|
|
2
|
+
/** Per-function line coverage as `pikku scenario run --coverage` records it. */
|
|
3
|
+
export interface ScenarioFunctionCoverage {
|
|
4
|
+
name: string;
|
|
5
|
+
sourceFile: string;
|
|
6
|
+
status: 'covered' | 'partial' | 'uncovered' | 'unknown';
|
|
7
|
+
totalLines: number;
|
|
8
|
+
missedLines: number[];
|
|
9
|
+
}
|
|
10
|
+
/** The file `pikku scenario run --coverage` writes to `.pikku/coverage/scenario-coverage.json`. */
|
|
11
|
+
export interface ScenarioCoverageFile {
|
|
12
|
+
generatedAt: string;
|
|
13
|
+
environment: string;
|
|
14
|
+
scenarios: Record<string, {
|
|
15
|
+
functions?: ScenarioFunctionCoverage[];
|
|
16
|
+
summary?: {
|
|
17
|
+
total?: number;
|
|
18
|
+
};
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
/** A function with lines no scenario reaches. */
|
|
22
|
+
export interface ScenarioCoverageGap {
|
|
23
|
+
function: string;
|
|
24
|
+
sourceFile: string;
|
|
25
|
+
status: 'uncovered' | 'partial';
|
|
26
|
+
/** Collapsed line ranges, e.g. `["L14-31", "L42"]`. */
|
|
27
|
+
missing: string[];
|
|
28
|
+
missedLines: number;
|
|
29
|
+
totalLines: number;
|
|
30
|
+
}
|
|
31
|
+
/** A mutation a user can perform that no scenario drives. */
|
|
32
|
+
export interface UncoveredMutation {
|
|
33
|
+
id: string;
|
|
34
|
+
sourceFile?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ScenarioCoverage {
|
|
37
|
+
/** Line coverage across every scenario; `null` until a run with `--coverage` has been recorded. */
|
|
38
|
+
api: {
|
|
39
|
+
generatedAt: string;
|
|
40
|
+
environment: string;
|
|
41
|
+
pct: number;
|
|
42
|
+
covered: number;
|
|
43
|
+
total: number;
|
|
44
|
+
gaps: ScenarioCoverageGap[];
|
|
45
|
+
} | null;
|
|
46
|
+
mutations: {
|
|
47
|
+
required: number;
|
|
48
|
+
covered: number;
|
|
49
|
+
uncovered: UncoveredMutation[];
|
|
50
|
+
};
|
|
51
|
+
/** Paths a scenario opens; `unvisited` only when the caller knows the app's routes. */
|
|
52
|
+
routes: {
|
|
53
|
+
visited: string[];
|
|
54
|
+
total: number | null;
|
|
55
|
+
unvisited: string[] | null;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Lines no scenario reaches, per function: a line counts as covered if any scenario hit it. */
|
|
59
|
+
export declare function aggregateScenarioCoverageGaps(file: ScenarioCoverageFile): ScenarioCoverageGap[];
|
|
60
|
+
/**
|
|
61
|
+
* Everything a scenario measures about the app, read from the meta codegen and
|
|
62
|
+
* `pikku scenario run --coverage` leave in `.pikku`.
|
|
63
|
+
*
|
|
64
|
+
* A mutation is a function a user changes something with: a mutating HTTP
|
|
65
|
+
* route, or an exposed RPC whose name opens with a mutating verb. Only RPCs a
|
|
66
|
+
* scenario can drive are required.
|
|
67
|
+
*/
|
|
68
|
+
export declare function readScenarioCoverage(metaService: MetaService, { routes }?: {
|
|
69
|
+
routes?: string[];
|
|
70
|
+
}): Promise<ScenarioCoverage>;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const MUTATING_METHODS = new Set(['post', 'put', 'patch', 'delete']);
|
|
2
|
+
const MUTATING_VERB = /^(create|add|new|update|edit|set|change|rename|delete|remove|destroy|archive|restore|assign|unassign|submit|approve|reject|cancel|complete|start|stop|send|invite|upload|register|book|reserve|claim|join|leave|mark|toggle|move|reorder|import|apply|save|record|log|generate|grant|revoke|link|unlink|publish|unpublish|enable|disable|accept|decline|resend|retry|sync|trigger|run|provision|deploy|purchase|checkout|pay|refund|subscribe|unsubscribe|reset|confirm|rotate|attach|detach|duplicate|clone|merge|split|schedule|reschedule|dispatch|finish|close|reopen|promote|demote|transfer|swap|seed|clear|cleanup|refresh|rebuild|install|uninstall|connect|disconnect|activate|deactivate|block|unblock|pin|unpin|vote|rate|comment|reply|post)([A-Z0-9]|$)/;
|
|
3
|
+
const NAVIGATION_STEPS = new Set(['opensPage']);
|
|
4
|
+
const isFrameworkTagged = (tags) => (tags ?? []).some((tag) => tag === 'pikku' || tag.startsWith('pikku:'));
|
|
5
|
+
const isGenerated = (sourceFile) => /\.gen\.ts$/.test(sourceFile ?? '');
|
|
6
|
+
const normalisePath = (path) => path.replace(/\/+$/, '') || '/';
|
|
7
|
+
const pct = (covered, total) => total === 0 ? 100 : Math.round((covered / total) * 100);
|
|
8
|
+
function collapseRanges(lines) {
|
|
9
|
+
const sorted = [...new Set(lines)].sort((a, b) => a - b);
|
|
10
|
+
const ranges = [];
|
|
11
|
+
for (let i = 0; i < sorted.length;) {
|
|
12
|
+
let j = i;
|
|
13
|
+
while (j + 1 < sorted.length && sorted[j + 1] === sorted[j] + 1)
|
|
14
|
+
j++;
|
|
15
|
+
ranges.push(sorted[i] === sorted[j] ? `L${sorted[i]}` : `L${sorted[i]}-${sorted[j]}`);
|
|
16
|
+
i = j + 1;
|
|
17
|
+
}
|
|
18
|
+
return ranges;
|
|
19
|
+
}
|
|
20
|
+
/** Lines no scenario reaches, per function: a line counts as covered if any scenario hit it. */
|
|
21
|
+
export function aggregateScenarioCoverageGaps(file) {
|
|
22
|
+
const byFunction = new Map();
|
|
23
|
+
for (const report of Object.values(file.scenarios ?? {})) {
|
|
24
|
+
for (const fn of report.functions ?? []) {
|
|
25
|
+
if (fn.status === 'unknown' || fn.totalLines === 0)
|
|
26
|
+
continue;
|
|
27
|
+
let entry = byFunction.get(fn.name);
|
|
28
|
+
if (!entry) {
|
|
29
|
+
entry = {
|
|
30
|
+
sourceFile: fn.sourceFile,
|
|
31
|
+
missed: null,
|
|
32
|
+
total: fn.totalLines,
|
|
33
|
+
};
|
|
34
|
+
byFunction.set(fn.name, entry);
|
|
35
|
+
}
|
|
36
|
+
const missed = new Set(fn.missedLines);
|
|
37
|
+
entry.missed =
|
|
38
|
+
entry.missed === null
|
|
39
|
+
? missed
|
|
40
|
+
: new Set([...entry.missed].filter((line) => missed.has(line)));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const gaps = [];
|
|
44
|
+
for (const [name, entry] of byFunction) {
|
|
45
|
+
if (!entry.missed || entry.missed.size === 0)
|
|
46
|
+
continue;
|
|
47
|
+
const missed = [...entry.missed];
|
|
48
|
+
gaps.push({
|
|
49
|
+
function: name,
|
|
50
|
+
sourceFile: entry.sourceFile,
|
|
51
|
+
status: missed.length >= entry.total ? 'uncovered' : 'partial',
|
|
52
|
+
missing: collapseRanges(missed),
|
|
53
|
+
missedLines: missed.length,
|
|
54
|
+
totalLines: entry.total,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return gaps.sort((a, b) => (a.status === b.status ? 0 : a.status === 'uncovered' ? -1 : 1) ||
|
|
58
|
+
b.missedLines - a.missedLines ||
|
|
59
|
+
a.function.localeCompare(b.function));
|
|
60
|
+
}
|
|
61
|
+
function scenarioNodes(workflows) {
|
|
62
|
+
return Object.values(workflows).flatMap((meta) => {
|
|
63
|
+
const workflow = meta;
|
|
64
|
+
return workflow?.source === 'scenario'
|
|
65
|
+
? Object.values(workflow.nodes ?? {})
|
|
66
|
+
: [];
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Everything a scenario measures about the app, read from the meta codegen and
|
|
71
|
+
* `pikku scenario run --coverage` leave in `.pikku`.
|
|
72
|
+
*
|
|
73
|
+
* A mutation is a function a user changes something with: a mutating HTTP
|
|
74
|
+
* route, or an exposed RPC whose name opens with a mutating verb. Only RPCs a
|
|
75
|
+
* scenario can drive are required.
|
|
76
|
+
*/
|
|
77
|
+
export async function readScenarioCoverage(metaService, { routes } = {}) {
|
|
78
|
+
const [functions, http, rpc, workflows, coverageFile] = await Promise.all([
|
|
79
|
+
metaService.getFunctionsMeta(),
|
|
80
|
+
metaService.getHttpMeta(),
|
|
81
|
+
metaService.getRpcMeta(),
|
|
82
|
+
metaService.getWorkflowMeta(),
|
|
83
|
+
metaService.readFile('coverage/scenario-coverage.json'),
|
|
84
|
+
]);
|
|
85
|
+
const nodes = scenarioNodes(workflows);
|
|
86
|
+
const driven = new Set(nodes.map((node) => node?.rpcName).filter((name) => !!name));
|
|
87
|
+
const visited = [
|
|
88
|
+
...new Set(nodes
|
|
89
|
+
.filter((node) => node?.rpcName && NAVIGATION_STEPS.has(node.rpcName))
|
|
90
|
+
.map((node) => node.input?.['path'])
|
|
91
|
+
.filter((path) => typeof path === 'string')
|
|
92
|
+
.map(normalisePath)),
|
|
93
|
+
].sort();
|
|
94
|
+
const invocable = new Set(Object.keys(rpc));
|
|
95
|
+
const required = new Map();
|
|
96
|
+
for (const [id, meta] of Object.entries(functions)) {
|
|
97
|
+
const funcId = meta.pikkuFuncId ?? id;
|
|
98
|
+
if (meta.functionType && meta.functionType !== 'user')
|
|
99
|
+
continue;
|
|
100
|
+
if (meta.expose === false || meta.readonly === true)
|
|
101
|
+
continue;
|
|
102
|
+
if (!MUTATING_VERB.test(funcId))
|
|
103
|
+
continue;
|
|
104
|
+
if (isFrameworkTagged(meta.tags) || isGenerated(meta.sourceFile))
|
|
105
|
+
continue;
|
|
106
|
+
if (!invocable.has(funcId))
|
|
107
|
+
continue;
|
|
108
|
+
required.set(funcId, meta.sourceFile);
|
|
109
|
+
}
|
|
110
|
+
for (const [method, wirings] of Object.entries(http)) {
|
|
111
|
+
if (!MUTATING_METHODS.has(method.toLowerCase()))
|
|
112
|
+
continue;
|
|
113
|
+
for (const route of Object.values(wirings ?? {})) {
|
|
114
|
+
const funcId = route?.pikkuFuncId;
|
|
115
|
+
if (!funcId || !invocable.has(funcId))
|
|
116
|
+
continue;
|
|
117
|
+
if (isFrameworkTagged(route.tags))
|
|
118
|
+
continue;
|
|
119
|
+
const sourceFile = functions[funcId]?.sourceFile;
|
|
120
|
+
if (isGenerated(sourceFile))
|
|
121
|
+
continue;
|
|
122
|
+
required.set(funcId, sourceFile);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const uncovered = [...required]
|
|
126
|
+
.filter(([id]) => !driven.has(id))
|
|
127
|
+
.map(([id, sourceFile]) => (sourceFile ? { id, sourceFile } : { id }))
|
|
128
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
129
|
+
let api = null;
|
|
130
|
+
if (coverageFile) {
|
|
131
|
+
const file = JSON.parse(coverageFile);
|
|
132
|
+
const total = Object.values(file.scenarios ?? {}).reduce((most, one) => Math.max(most, one?.summary?.total ?? 0), 0);
|
|
133
|
+
if (total > 0) {
|
|
134
|
+
const gaps = aggregateScenarioCoverageGaps(file);
|
|
135
|
+
api = {
|
|
136
|
+
generatedAt: file.generatedAt,
|
|
137
|
+
environment: file.environment,
|
|
138
|
+
total,
|
|
139
|
+
covered: total - gaps.length,
|
|
140
|
+
pct: pct(total - gaps.length, total),
|
|
141
|
+
gaps,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const known = routes ? [...new Set(routes.map(normalisePath))].sort() : null;
|
|
146
|
+
return {
|
|
147
|
+
api,
|
|
148
|
+
mutations: {
|
|
149
|
+
required: required.size,
|
|
150
|
+
covered: required.size - uncovered.length,
|
|
151
|
+
uncovered,
|
|
152
|
+
},
|
|
153
|
+
routes: {
|
|
154
|
+
visited,
|
|
155
|
+
total: known ? known.length : null,
|
|
156
|
+
unvisited: known ? known.filter((path) => !visited.includes(path)) : null,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/core",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.137",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/pikkujs/pikku.git",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"./channel": "./dist/wirings/channel/index.js",
|
|
40
40
|
"./workflow": "./dist/wirings/workflow/index.js",
|
|
41
41
|
"./scenario": "./dist/wirings/workflow/pikku-scenario-service.js",
|
|
42
|
+
"./scenario/coverage": "./dist/wirings/workflow/scenario-coverage.js",
|
|
42
43
|
"./workflow/timeline": "./dist/wirings/workflow/run-timeline.js",
|
|
43
44
|
"./workflow/types": "./dist/wirings/workflow/workflow.types.js",
|
|
44
45
|
"./actor-flow": "./dist/wirings/actor-flow/index.js",
|
package/src/public-surface.json
CHANGED