@pikku/cli 0.12.69 → 0.12.71
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/cli.schema.json +1 -1
- package/console-app/assets/{index-BY60UXB6.js → index-Bn7IPpsQ.js} +140 -141
- package/console-app/index.html +1 -1
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +6 -11
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +27 -31
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +48 -43
- package/dist/.pikku/function/pikku-functions.gen.js +3 -1
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +4 -2
- package/dist/.pikku/pikku-services.gen.js +2 -0
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +2 -2
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +3 -5
- package/dist/.pikku/schemas/schemas/DevInput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuScenarioFunctionsOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/meta/allWorkflow.gen.json +8 -2
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/bin/pikku-bin.mjs +2 -2
- package/dist/src/cli.wiring.js +27 -29
- package/dist/src/fabric/fabric-commands.d.ts +3 -3
- package/dist/src/fabric/functions/llm-key.function.d.ts +4 -4
- package/dist/src/fabric/functions/validate.function.js +54 -15
- package/dist/src/functions/commands/all.js +14 -1
- package/dist/src/functions/commands/bun-coverage-loader.d.ts +11 -0
- package/dist/src/functions/commands/bun-coverage-loader.js +42 -0
- package/dist/src/functions/commands/dev.d.ts +6 -0
- package/dist/src/functions/commands/dev.js +19 -4
- package/dist/src/functions/commands/enable.d.ts +1 -0
- package/dist/src/functions/commands/enable.js +9 -1
- package/dist/src/functions/commands/scenario.d.ts +3 -0
- package/dist/src/functions/commands/scenario.js +52 -11
- package/dist/src/functions/commands/serve.js +4 -1
- package/dist/src/functions/commands/start-coverage.d.ts +13 -0
- package/dist/src/functions/commands/start-coverage.js +42 -0
- package/dist/src/functions/commands/watch.js +2 -1
- package/dist/src/functions/validate/workspace-validate.js +0 -4
- package/dist/src/functions/wirings/console/serialize-console-functions.js +0 -6
- package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.d.ts +1 -0
- package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.js +25 -0
- package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.d.ts +6 -0
- package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.js +98 -0
- package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +4 -5
- package/dist/src/functions/wirings/workflow/serialize-scenario-actors.d.ts +0 -6
- package/dist/src/functions/wirings/workflow/serialize-scenario-actors.js +1 -15
- package/dist/src/functions/workflows/all.workflow.js +6 -0
- package/dist/src/services.js +14 -1
- package/dist/src/utils/file-writer.d.ts +1 -0
- package/dist/src/utils/file-writer.js +9 -0
- package/dist/src/utils/meta-diff.d.ts +32 -0
- package/dist/src/utils/meta-diff.js +144 -0
- package/dist/src/utils/pikku-cli-config.js +3 -0
- package/dist/src/utils/precise-coverage-map.d.ts +4 -0
- package/dist/src/utils/precise-coverage-map.js +176 -0
- package/dist/src/utils/serialize-schemas.js +1 -1
- package/dist/src/utils/strip-verbose-meta.js +3 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -3
- package/dist/.pikku/schemas/schemas/PikkuTestsCoverageInput.schema.json +0 -1
- package/dist/.pikku/schemas/schemas/PikkuTestsInitInput.schema.json +0 -1
- package/dist/src/functions/commands/tests-coverage.d.ts +0 -10
- package/dist/src/functions/commands/tests-coverage.js +0 -273
- package/dist/src/functions/commands/tests-init.d.ts +0 -7
- package/dist/src/functions/commands/tests-init.js +0 -274
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate the scenario instrumentation functions (`pikku scenario` coverage
|
|
3
|
+
* snapshots and stub-call inspection) into the project scaffold, so scenario
|
|
4
|
+
* runs work against any server without requiring the console addon.
|
|
5
|
+
*/
|
|
6
|
+
export const serializeScenarioFunctions = (pathToPikkuTypes, requireAuth = true) => {
|
|
7
|
+
const authFlag = requireAuth ? 'true' : 'false';
|
|
8
|
+
return `/**
|
|
9
|
+
* Auto-generated scenario instrumentation functions
|
|
10
|
+
* Do not edit manually - regenerate with 'npx pikku'
|
|
11
|
+
*/
|
|
12
|
+
import { readFile } from 'node:fs/promises'
|
|
13
|
+
import { join } from 'node:path'
|
|
14
|
+
import { pikkuFunc } from '${pathToPikkuTypes}'
|
|
15
|
+
import {
|
|
16
|
+
getStubTracker,
|
|
17
|
+
isTestRun,
|
|
18
|
+
type CoverageFunctionMeta,
|
|
19
|
+
type FunctionCoverageReport,
|
|
20
|
+
type StubCall,
|
|
21
|
+
} from '@pikku/core/services'
|
|
22
|
+
|
|
23
|
+
export const pikkuScenarioTakeLiveCoverage = pikkuFunc<
|
|
24
|
+
null,
|
|
25
|
+
FunctionCoverageReport | null
|
|
26
|
+
>({
|
|
27
|
+
tags: ['pikku'],
|
|
28
|
+
title: 'Take Live Coverage',
|
|
29
|
+
description:
|
|
30
|
+
'Snapshots the live coverage collected since the server started (or since the last reset) — V8 precise coverage on Node, istanbul instrumentation on Bun — and maps it onto function body spans. Returns null unless the server was started with coverage enabled (pikku dev --coverage).',
|
|
31
|
+
expose: true,
|
|
32
|
+
auth: ${authFlag},
|
|
33
|
+
func: async ({ coverageService, metaService }) => {
|
|
34
|
+
if (!coverageService?.takeReport || !metaService?.basePath) return null
|
|
35
|
+
let functionsMeta: Record<string, CoverageFunctionMeta>
|
|
36
|
+
try {
|
|
37
|
+
const content = await readFile(
|
|
38
|
+
join(
|
|
39
|
+
metaService.basePath,
|
|
40
|
+
'function',
|
|
41
|
+
'pikku-functions-meta-verbose.gen.json'
|
|
42
|
+
),
|
|
43
|
+
'utf-8'
|
|
44
|
+
)
|
|
45
|
+
functionsMeta = JSON.parse(content)
|
|
46
|
+
} catch {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
return await coverageService.takeReport(functionsMeta)
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
export const pikkuScenarioResetLiveCoverage = pikkuFunc<
|
|
54
|
+
null,
|
|
55
|
+
{ enabled: boolean }
|
|
56
|
+
>({
|
|
57
|
+
tags: ['pikku'],
|
|
58
|
+
title: 'Reset Live Coverage',
|
|
59
|
+
description:
|
|
60
|
+
'Clears V8 precise-coverage call counts so the next takeLiveCoverage snapshot is attributable to a single scenario run. Reports enabled: false when the server was not started with coverage enabled.',
|
|
61
|
+
expose: true,
|
|
62
|
+
auth: ${authFlag},
|
|
63
|
+
func: async ({ coverageService }) => {
|
|
64
|
+
if (!coverageService) return { enabled: false }
|
|
65
|
+
await coverageService.reset()
|
|
66
|
+
return { enabled: true }
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
export const pikkuScenarioResetStubs = pikkuFunc<null, { enabled: boolean }>({
|
|
71
|
+
tags: ['pikku'],
|
|
72
|
+
title: 'Reset Stubs',
|
|
73
|
+
description:
|
|
74
|
+
'Clears recorded stub calls so the next getStubCalls result is attributable to a single scenario run. Reports enabled: false when the server was not started in test mode.',
|
|
75
|
+
expose: true,
|
|
76
|
+
auth: ${authFlag},
|
|
77
|
+
func: async () => {
|
|
78
|
+
getStubTracker().reset()
|
|
79
|
+
return { enabled: isTestRun() }
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
export const pikkuScenarioGetStubCalls = pikkuFunc<
|
|
84
|
+
{ service?: string },
|
|
85
|
+
StubCall[]
|
|
86
|
+
>({
|
|
87
|
+
tags: ['pikku'],
|
|
88
|
+
title: 'Get Stub Calls',
|
|
89
|
+
description:
|
|
90
|
+
'Returns calls recorded against stubbed/spied services (via the stub()/spy() core utils). Empty unless the server records service calls (pikku dev --test).',
|
|
91
|
+
expose: true,
|
|
92
|
+
auth: ${authFlag},
|
|
93
|
+
func: async (_services, data) => {
|
|
94
|
+
return getStubTracker().getCalls(data?.service ?? undefined)
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
`;
|
|
98
|
+
};
|
|
@@ -10,7 +10,7 @@ import { serializeWorkflowBootstrapMap } from './serialize-workflow-bootstrap-ma
|
|
|
10
10
|
import { serializeWorkflowMeta } from './serialize-workflow-meta.js';
|
|
11
11
|
import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
|
|
12
12
|
import { stripVerboseFields, hasVerboseFields, } from '../../../utils/strip-verbose-meta.js';
|
|
13
|
-
import { join
|
|
13
|
+
import { join } from 'path';
|
|
14
14
|
export const pikkuWorkflow = pikkuSessionlessFunc({
|
|
15
15
|
func: async ({ logger, config, getInspectorState }, input) => {
|
|
16
16
|
const bootstrap = input?.bootstrap === true;
|
|
@@ -67,10 +67,9 @@ export const pikkuWorkflow = pikkuSessionlessFunc({
|
|
|
67
67
|
if (scenarioActors && Object.keys(scenarioActors).length > 0) {
|
|
68
68
|
const agentMapImportPath = getFileImportRelativePath(config.scenarioActorsFile, config.agentMapDeclarationFile, packageMappings);
|
|
69
69
|
await writeFileInDir(logger, config.scenarioActorsFile, serializeScenarioActors(scenarioActors, agentMapImportPath));
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
await writeFileInDir(logger, join(dirname(config.scenarioActorsFile), 'scenario-actors.gen.json'), JSON.stringify(scenarioActors, null, 2));
|
|
70
|
+
// Fixed path getScenarioActorsMeta() reads; kept out of workflow/meta,
|
|
71
|
+
// which getWorkflowMeta() globs as workflows.
|
|
72
|
+
await writeFileInDir(logger, join(config.outDir, 'workflow', 'scenario-actors.gen.json'), JSON.stringify(scenarioActors, null, 2));
|
|
74
73
|
}
|
|
75
74
|
await writeFileInDir(logger, workflowMapDeclarationFile, bootstrap
|
|
76
75
|
? serializeWorkflowBootstrapMap(workflows.meta, workflows.graphMeta)
|
|
@@ -1,8 +1,2 @@
|
|
|
1
1
|
import type { PikkuCLIConfig } from '../../../../types/config.js';
|
|
2
|
-
/**
|
|
3
|
-
* Generate the typed scenario actor registry from pikku.config.json's
|
|
4
|
-
* `scenarios.actors`. Apps wire the result as the `actors` singleton service:
|
|
5
|
-
*
|
|
6
|
-
* actors: createScenarioActors({ apiUrl, secret })
|
|
7
|
-
*/
|
|
8
2
|
export declare const serializeScenarioActors: (actors: NonNullable<NonNullable<PikkuCLIConfig["scenarios"]>["actors"]>, agentMapImportPath: string) => string;
|
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate the typed scenario actor registry from pikku.config.json's
|
|
3
|
-
* `scenarios.actors`. Apps wire the result as the `actors` singleton service:
|
|
4
|
-
*
|
|
5
|
-
* actors: createScenarioActors({ apiUrl, secret })
|
|
6
|
-
*/
|
|
7
1
|
export const serializeScenarioActors = (actors, agentMapImportPath) => {
|
|
8
|
-
return `/**
|
|
9
|
-
* Scenario actors declared in pikku.config.json (\`scenarios.actors\`).
|
|
10
|
-
* Any scenario can impersonate any actor.
|
|
11
|
-
*/
|
|
2
|
+
return `/** Scenario actors declared in pikku.config.json (\`scenarios.actors\`) */
|
|
12
3
|
import {
|
|
13
4
|
createHttpScenarioActors,
|
|
14
5
|
type HttpScenarioActorsConfig,
|
|
@@ -21,17 +12,12 @@ export const scenarioActorConfigs = ${JSON.stringify(actors, null, 2)} as const
|
|
|
21
12
|
|
|
22
13
|
export type ScenarioActorName = keyof typeof scenarioActorConfigs
|
|
23
14
|
|
|
24
|
-
/** \`actor.converse({ agent })\` is constrained to the project's agent names. */
|
|
25
15
|
export type AgentName = keyof AgentMap & string
|
|
26
16
|
export type TypedScenarioActors = Record<
|
|
27
17
|
ScenarioActorName,
|
|
28
18
|
ScenarioActor<AgentName>
|
|
29
19
|
>
|
|
30
20
|
|
|
31
|
-
/**
|
|
32
|
-
* Build the injected \`actors\` service: one lazy HTTP actor per registry
|
|
33
|
-
* entry, signing in via the Better Auth actor plugin on first invoke.
|
|
34
|
-
*/
|
|
35
21
|
export const createScenarioActors = (
|
|
36
22
|
options: Omit<HttpScenarioActorsConfig, 'actors'>
|
|
37
23
|
): TypedScenarioActors =>
|
|
@@ -11,6 +11,11 @@ const scaffoldFiles = (config) => {
|
|
|
11
11
|
file: config.consoleFunctionsFile,
|
|
12
12
|
generator: 'pikkuConsoleFunctions',
|
|
13
13
|
});
|
|
14
|
+
if (config.scaffold?.scenarios && config.scenariosFunctionsFile)
|
|
15
|
+
files.push({
|
|
16
|
+
file: config.scenariosFunctionsFile,
|
|
17
|
+
generator: 'pikkuScenarioFunctions',
|
|
18
|
+
});
|
|
14
19
|
if (config.scaffold?.agent && config.publicAgentFile)
|
|
15
20
|
files.push({
|
|
16
21
|
file: config.publicAgentFile,
|
|
@@ -126,6 +131,7 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
|
|
|
126
131
|
await Promise.all([
|
|
127
132
|
workflow.do('Public RPC', 'pikkuPublicRPC', null),
|
|
128
133
|
workflow.do('Console functions', 'pikkuConsoleFunctions', null),
|
|
134
|
+
workflow.do('Scenario functions', 'pikkuScenarioFunctions', null),
|
|
129
135
|
workflow.do('Events scaffold', 'pikkuEventsScaffold', null),
|
|
130
136
|
workflow.do('Emails', 'pikkuEmails', null),
|
|
131
137
|
workflow.do('Secret definition types', 'pikkuSecretDefinitionTypes', null),
|
package/dist/src/services.js
CHANGED
|
@@ -10,6 +10,7 @@ import { CLILoggerForwarder } from './services/cli-logger-forwarder.service.js';
|
|
|
10
10
|
import { existsSync } from 'fs';
|
|
11
11
|
import { readFile, writeFile } from 'fs/promises';
|
|
12
12
|
import { loadManifest } from './utils/contract-versions.js';
|
|
13
|
+
import { getTsWriteGeneration } from './utils/file-writer.js';
|
|
13
14
|
import { join } from 'path';
|
|
14
15
|
import { NodeBundler } from './deploy/bundler/node-bundler.js';
|
|
15
16
|
import { BunBundler } from './deploy/bundler/bun-bundler.js';
|
|
@@ -142,6 +143,8 @@ export const createSingletonServices = async (config) => {
|
|
|
142
143
|
// Store unfiltered state
|
|
143
144
|
let unfilteredState = preloadedInspectorState;
|
|
144
145
|
let unfilteredStateIsSetupOnly = false;
|
|
146
|
+
let inspectedTsGeneration;
|
|
147
|
+
let inspectorInvalidated = false;
|
|
145
148
|
const getInspectorState = async (refresh = false, setupOnly = false, bootstrapMode = false) => {
|
|
146
149
|
// In bootstrap mode, return a minimal "zero state" with core types
|
|
147
150
|
// This allows bootstrap to run immediately without inspecting the codebase
|
|
@@ -184,7 +187,10 @@ export const createSingletonServices = async (config) => {
|
|
|
184
187
|
// inspection (setupOnly=false) is now requested — setupOnly skips
|
|
185
188
|
// visitRoutes so variables/secrets/etc. are absent from that cache.
|
|
186
189
|
if (!unfilteredState ||
|
|
187
|
-
(refresh &&
|
|
190
|
+
(refresh &&
|
|
191
|
+
!preloadedInspectorState &&
|
|
192
|
+
(inspectorInvalidated ||
|
|
193
|
+
getTsWriteGeneration() !== inspectedTsGeneration)) ||
|
|
188
194
|
(unfilteredStateIsSetupOnly && !setupOnly && !preloadedInspectorState)) {
|
|
189
195
|
// Run inspector WITHOUT filters to get full state
|
|
190
196
|
const wiringFiles = (await Promise.all(srcDirectories.map((dir) => glob(`${path.join(rootDir, dir)}/**/*.ts`, {
|
|
@@ -223,6 +229,7 @@ export const createSingletonServices = async (config) => {
|
|
|
223
229
|
const oldProgram = unfilteredState?.program ?? undefined;
|
|
224
230
|
const inspectStart = Date.now();
|
|
225
231
|
unfilteredStateIsSetupOnly = setupOnly;
|
|
232
|
+
const tsGenerationAtInspect = getTsWriteGeneration();
|
|
226
233
|
unfilteredState = await inspect(logger, wiringFiles, {
|
|
227
234
|
setupOnly,
|
|
228
235
|
rootDir,
|
|
@@ -256,6 +263,8 @@ export const createSingletonServices = async (config) => {
|
|
|
256
263
|
manifest,
|
|
257
264
|
});
|
|
258
265
|
logger.debug(`Inspector took ${Date.now() - inspectStart}ms`);
|
|
266
|
+
inspectedTsGeneration = tsGenerationAtInspect;
|
|
267
|
+
inspectorInvalidated = false;
|
|
259
268
|
if ('diagnostics' in unfilteredState &&
|
|
260
269
|
unfilteredState.diagnostics.length > 0) {
|
|
261
270
|
processDiagnostics(unfilteredState.diagnostics, config.lint);
|
|
@@ -277,6 +286,9 @@ export const createSingletonServices = async (config) => {
|
|
|
277
286
|
const filteredState = filterInspectorState(unfilteredState, filters, logger);
|
|
278
287
|
return filteredState;
|
|
279
288
|
};
|
|
289
|
+
const invalidateInspectorState = () => {
|
|
290
|
+
inspectorInvalidated = true;
|
|
291
|
+
};
|
|
280
292
|
const workflowService = new InMemoryWorkflowService();
|
|
281
293
|
// Resolve the runtime ONCE here, then inject runtime-specific implementations.
|
|
282
294
|
// Keeping the check in this single place avoids `typeof Bun` branches leaking
|
|
@@ -289,6 +301,7 @@ export const createSingletonServices = async (config) => {
|
|
|
289
301
|
secrets: new LocalSecretService(variables),
|
|
290
302
|
audit: new NoopAuditService(),
|
|
291
303
|
getInspectorState,
|
|
304
|
+
invalidateInspectorState,
|
|
292
305
|
workflowService,
|
|
293
306
|
bundler: isBun ? new BunBundler() : new NodeBundler(),
|
|
294
307
|
devServerRunner: isBun ? new BunServerRunner() : new NodeServerRunner(),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CLILogger } from '../services/cli-logger.service.js';
|
|
2
2
|
export declare const DO_NOT_MODIFY_COMMENT: string;
|
|
3
|
+
export declare const getTsWriteGeneration: () => number;
|
|
3
4
|
export declare const writeFileInDir: (logger: CLILogger, path: string, content: string, { ignoreModifyComment, logWrite, }?: {
|
|
4
5
|
ignoreModifyComment?: boolean;
|
|
5
6
|
logWrite?: boolean;
|
|
@@ -6,6 +6,9 @@ export const DO_NOT_MODIFY_COMMENT = `/**
|
|
|
6
6
|
* This file was generated by @pikku/cli@${getCLIVersion()}
|
|
7
7
|
*/
|
|
8
8
|
`;
|
|
9
|
+
let tsWriteGeneration = 0;
|
|
10
|
+
const TS_FILE_REGEX = /\.[mc]?tsx?$/;
|
|
11
|
+
export const getTsWriteGeneration = () => tsWriteGeneration;
|
|
9
12
|
export const writeFileInDir = async (logger, path, content, { ignoreModifyComment = false, logWrite = true, } = {}) => {
|
|
10
13
|
if (path.includes('.json')) {
|
|
11
14
|
ignoreModifyComment = true;
|
|
@@ -34,6 +37,9 @@ export const writeFileInDir = async (logger, path, content, { ignoreModifyCommen
|
|
|
34
37
|
if (existingContent !== content) {
|
|
35
38
|
await mkdir(dirname(path), { recursive: true });
|
|
36
39
|
await writeFile(path, content, 'utf-8');
|
|
40
|
+
if (TS_FILE_REGEX.test(path)) {
|
|
41
|
+
tsWriteGeneration++;
|
|
42
|
+
}
|
|
37
43
|
if (logWrite) {
|
|
38
44
|
logger.debug({ message: `✓ File written to ${path}`, type: 'success' });
|
|
39
45
|
}
|
|
@@ -56,6 +62,9 @@ export const removeFileInDir = async (logger, path, { logRemove = true } = {}) =
|
|
|
56
62
|
// Check if file exists before attempting removal
|
|
57
63
|
if (existsSync(path)) {
|
|
58
64
|
await rm(path, { force: true });
|
|
65
|
+
if (TS_FILE_REGEX.test(path)) {
|
|
66
|
+
tsWriteGeneration++;
|
|
67
|
+
}
|
|
59
68
|
if (logRemove) {
|
|
60
69
|
logger.debug({ message: `✓ File removed at ${path}`, type: 'success' });
|
|
61
70
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural diff of the generated `.pikku` meta between two points in time —
|
|
3
|
+
* used by `pikku all --diff` to report what a codegen run added/removed/changed
|
|
4
|
+
* (functions, wirings, workflows, emails, …). The snapshot is a plain in-memory
|
|
5
|
+
* copy of the meta files, so diffing is a couple of small JSON reads, not a
|
|
6
|
+
* second inspection pass. Only user-visible buildable surfaces are included;
|
|
7
|
+
* plumbing (secrets/variables/middleware/permissions) is intentionally omitted.
|
|
8
|
+
*/
|
|
9
|
+
export type MetaDiffCategoryName = 'functions' | 'http' | 'workflow' | 'email' | 'scheduler' | 'queue' | 'channel' | 'trigger' | 'mcp' | 'agent';
|
|
10
|
+
export interface MetaDiffEntry {
|
|
11
|
+
id: string;
|
|
12
|
+
status: 'added' | 'removed' | 'modified';
|
|
13
|
+
}
|
|
14
|
+
export interface MetaDiffCategory {
|
|
15
|
+
added: number;
|
|
16
|
+
removed: number;
|
|
17
|
+
modified: number;
|
|
18
|
+
entries: MetaDiffEntry[];
|
|
19
|
+
}
|
|
20
|
+
export interface MetaDiff {
|
|
21
|
+
totals: {
|
|
22
|
+
added: number;
|
|
23
|
+
removed: number;
|
|
24
|
+
modified: number;
|
|
25
|
+
};
|
|
26
|
+
categories: Partial<Record<MetaDiffCategoryName, MetaDiffCategory>>;
|
|
27
|
+
}
|
|
28
|
+
export type MetaSnapshot = Partial<Record<MetaDiffCategoryName, Record<string, unknown>>>;
|
|
29
|
+
/** Read the current meta from disk into an in-memory snapshot. */
|
|
30
|
+
export declare function readMetaSnapshot(outDir: string): MetaSnapshot;
|
|
31
|
+
/** Diff two snapshots; only changed ids and non-empty categories are returned. */
|
|
32
|
+
export declare function computeMetaDiff(before: MetaSnapshot, after: MetaSnapshot): MetaDiff;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
// Single-file categories: one JSON file whose top-level keys are the ids
|
|
4
|
+
// (http is nested `{ method: { route: meta } }` and flattened to `method route`).
|
|
5
|
+
const SINGLE_FILE = {
|
|
6
|
+
functions: 'function/pikku-functions-meta.gen.json',
|
|
7
|
+
http: 'http/pikku-http-wirings-meta.gen.json',
|
|
8
|
+
email: 'email/pikku-emails-meta.gen.json',
|
|
9
|
+
scheduler: 'scheduler/pikku-schedulers-wirings-meta.gen.json',
|
|
10
|
+
queue: 'queue/pikku-queue-workers-wirings-meta.gen.json',
|
|
11
|
+
channel: 'channel/pikku-channels-meta.gen.json',
|
|
12
|
+
trigger: 'trigger/pikku-trigger-meta.gen.json',
|
|
13
|
+
mcp: 'mcp/pikku-mcp-wirings-meta.gen.json',
|
|
14
|
+
agent: 'agent/pikku-agent-wirings-meta.gen.json',
|
|
15
|
+
};
|
|
16
|
+
// A few meta files wrap their real entities under a single key rather than
|
|
17
|
+
// keying them at the top level — unwrap to that map so ids are the entities.
|
|
18
|
+
const UNWRAP = {
|
|
19
|
+
email: 'templates',
|
|
20
|
+
agent: 'agentsMeta',
|
|
21
|
+
};
|
|
22
|
+
// Workflows (incl. userflows/scenarios) are written one file per workflow under
|
|
23
|
+
// this dir; the file basename (sans `.gen.json`) is the id, `-verbose` excluded.
|
|
24
|
+
const WORKFLOW_META_DIR = 'workflow/meta';
|
|
25
|
+
function readJson(path) {
|
|
26
|
+
if (!existsSync(path))
|
|
27
|
+
return {};
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
30
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
31
|
+
return {};
|
|
32
|
+
return parsed;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function readSingle(outDir, category, relPath) {
|
|
39
|
+
let raw = readJson(join(outDir, relPath));
|
|
40
|
+
const unwrapKey = UNWRAP[category];
|
|
41
|
+
if (unwrapKey) {
|
|
42
|
+
const inner = raw[unwrapKey];
|
|
43
|
+
raw = inner && typeof inner === 'object' && !Array.isArray(inner) ? inner : {};
|
|
44
|
+
}
|
|
45
|
+
if (category !== 'http')
|
|
46
|
+
return raw;
|
|
47
|
+
const flat = {};
|
|
48
|
+
for (const [method, routes] of Object.entries(raw)) {
|
|
49
|
+
if (routes && typeof routes === 'object' && !Array.isArray(routes)) {
|
|
50
|
+
for (const [route, meta] of Object.entries(routes)) {
|
|
51
|
+
flat[`${method.toUpperCase()} ${route}`] = meta;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return flat;
|
|
56
|
+
}
|
|
57
|
+
function readWorkflows(outDir) {
|
|
58
|
+
const dir = join(outDir, WORKFLOW_META_DIR);
|
|
59
|
+
if (!existsSync(dir))
|
|
60
|
+
return {};
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const file of readdirSync(dir)) {
|
|
63
|
+
if (!file.endsWith('.gen.json') || file.endsWith('-verbose.gen.json'))
|
|
64
|
+
continue;
|
|
65
|
+
out[file.slice(0, -'.gen.json'.length)] = readJson(join(dir, file));
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
/** Read the current meta from disk into an in-memory snapshot. */
|
|
70
|
+
export function readMetaSnapshot(outDir) {
|
|
71
|
+
if (!outDir || !existsSync(outDir))
|
|
72
|
+
return {};
|
|
73
|
+
const snap = {};
|
|
74
|
+
for (const [category, relPath] of Object.entries(SINGLE_FILE)) {
|
|
75
|
+
snap[category] = readSingle(outDir, category, relPath);
|
|
76
|
+
}
|
|
77
|
+
snap.workflow = readWorkflows(outDir);
|
|
78
|
+
return snap;
|
|
79
|
+
}
|
|
80
|
+
// Volatile fields that vary by absolute path / environment but aren't a
|
|
81
|
+
// meaningful structural change — excluded from equality (matches StateDiffService).
|
|
82
|
+
const NORMALIZE_IGNORE = new Set(['sourceFile']);
|
|
83
|
+
function normalize(value) {
|
|
84
|
+
if (Array.isArray(value))
|
|
85
|
+
return value.map(normalize);
|
|
86
|
+
if (value && typeof value === 'object') {
|
|
87
|
+
const out = {};
|
|
88
|
+
for (const key of Object.keys(value).sort()) {
|
|
89
|
+
if (NORMALIZE_IGNORE.has(key))
|
|
90
|
+
continue;
|
|
91
|
+
out[key] = normalize(value[key]);
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
function equal(a, b) {
|
|
98
|
+
return JSON.stringify(normalize(a)) === JSON.stringify(normalize(b));
|
|
99
|
+
}
|
|
100
|
+
/** Diff two snapshots; only changed ids and non-empty categories are returned. */
|
|
101
|
+
export function computeMetaDiff(before, after) {
|
|
102
|
+
const categories = {};
|
|
103
|
+
const totals = { added: 0, removed: 0, modified: 0 };
|
|
104
|
+
const names = new Set([
|
|
105
|
+
...Object.keys(before),
|
|
106
|
+
...Object.keys(after),
|
|
107
|
+
]);
|
|
108
|
+
for (const name of names) {
|
|
109
|
+
const o = before[name] ?? {};
|
|
110
|
+
const a = after[name] ?? {};
|
|
111
|
+
const ids = new Set([...Object.keys(o), ...Object.keys(a)]);
|
|
112
|
+
const entries = [];
|
|
113
|
+
let added = 0;
|
|
114
|
+
let removed = 0;
|
|
115
|
+
let modified = 0;
|
|
116
|
+
for (const id of ids) {
|
|
117
|
+
const inO = id in o;
|
|
118
|
+
const inA = id in a;
|
|
119
|
+
if (inA && !inO) {
|
|
120
|
+
entries.push({ id, status: 'added' });
|
|
121
|
+
added++;
|
|
122
|
+
}
|
|
123
|
+
else if (inO && !inA) {
|
|
124
|
+
entries.push({ id, status: 'removed' });
|
|
125
|
+
removed++;
|
|
126
|
+
}
|
|
127
|
+
else if (!equal(o[id], a[id])) {
|
|
128
|
+
entries.push({ id, status: 'modified' });
|
|
129
|
+
modified++;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (entries.length === 0)
|
|
133
|
+
continue;
|
|
134
|
+
entries.sort((x, y) => {
|
|
135
|
+
const order = { added: 0, modified: 1, removed: 2 };
|
|
136
|
+
return order[x.status] - order[y.status] || x.id.localeCompare(y.id);
|
|
137
|
+
});
|
|
138
|
+
categories[name] = { added, removed, modified, entries };
|
|
139
|
+
totals.added += added;
|
|
140
|
+
totals.removed += removed;
|
|
141
|
+
totals.modified += modified;
|
|
142
|
+
}
|
|
143
|
+
return { totals, categories };
|
|
144
|
+
}
|
|
@@ -221,6 +221,9 @@ const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields
|
|
|
221
221
|
if (result.scaffold?.console && !result.consoleFunctionsFile) {
|
|
222
222
|
result.consoleFunctionsFile = join(resolvedScaffoldDir, 'console.gen.ts');
|
|
223
223
|
}
|
|
224
|
+
if (result.scaffold?.scenarios && !result.scenariosFunctionsFile) {
|
|
225
|
+
result.scenariosFunctionsFile = join(resolvedScaffoldDir, 'scenarios.gen.ts');
|
|
226
|
+
}
|
|
224
227
|
if (!result.authFile) {
|
|
225
228
|
result.authFile = join(resolvedScaffoldDir, 'auth.gen.ts');
|
|
226
229
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CoverageFunctionMeta, FunctionCoverageReport, ScriptCoverage as ScriptCoverageInput } from '@pikku/core/services';
|
|
2
|
+
export type { ScriptCoverageInput };
|
|
3
|
+
export declare function mapPreciseCoverage(scripts: ScriptCoverageInput[], getScriptSource: (scriptId: string) => Promise<string>, functionsMeta: Record<string, CoverageFunctionMeta>): Promise<FunctionCoverageReport>;
|
|
4
|
+
export declare function mapLineHitsToReport(lineHits: Map<string, Map<number, number>>, functionsMeta: Record<string, CoverageFunctionMeta>): FunctionCoverageReport;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Offsets are mapped through the inline source map rather than line-based
|
|
2
|
+
// statement maps: esbuild emits the whole module on one generated line, which
|
|
3
|
+
// would collapse every statement onto line 1.
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping';
|
|
6
|
+
const INLINE_SOURCE_MAP = /\/\/# sourceMappingURL=data:application\/json[^,]*;base64,([A-Za-z0-9+/=]+)/;
|
|
7
|
+
function extractInlineSourceMap(source) {
|
|
8
|
+
const match = source.match(INLINE_SOURCE_MAP);
|
|
9
|
+
if (!match)
|
|
10
|
+
return undefined;
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(Buffer.from(match[1], 'base64').toString('utf-8'));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function computeLineStarts(source) {
|
|
19
|
+
const starts = [0];
|
|
20
|
+
for (let i = 0; i < source.length; i++) {
|
|
21
|
+
if (source[i] === '\n')
|
|
22
|
+
starts.push(i + 1);
|
|
23
|
+
}
|
|
24
|
+
return starts;
|
|
25
|
+
}
|
|
26
|
+
function offsetToGeneratedPosition(offset, lineStarts) {
|
|
27
|
+
let low = 0;
|
|
28
|
+
let high = lineStarts.length - 1;
|
|
29
|
+
while (low < high) {
|
|
30
|
+
const mid = (low + high + 1) >> 1;
|
|
31
|
+
if (lineStarts[mid] <= offset)
|
|
32
|
+
low = mid;
|
|
33
|
+
else
|
|
34
|
+
high = mid - 1;
|
|
35
|
+
}
|
|
36
|
+
return { line: low + 1, column: offset - lineStarts[low] };
|
|
37
|
+
}
|
|
38
|
+
function normalizeSourcePath(source) {
|
|
39
|
+
if (source.startsWith('file://')) {
|
|
40
|
+
try {
|
|
41
|
+
return fileURLToPath(source);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return source;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return source;
|
|
48
|
+
}
|
|
49
|
+
async function scriptToLineCoverage(script, getScriptSource, lineHits) {
|
|
50
|
+
let scriptPath;
|
|
51
|
+
try {
|
|
52
|
+
scriptPath = fileURLToPath(script.url);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let source;
|
|
58
|
+
try {
|
|
59
|
+
source = await getScriptSource(script.scriptId);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const sourcemap = extractInlineSourceMap(source);
|
|
65
|
+
let tracer;
|
|
66
|
+
if (sourcemap) {
|
|
67
|
+
try {
|
|
68
|
+
tracer = new TraceMap(sourcemap);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
tracer = undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const lineStarts = computeLineStarts(source);
|
|
75
|
+
const mapOffset = (offset) => {
|
|
76
|
+
const generated = offsetToGeneratedPosition(offset, lineStarts);
|
|
77
|
+
if (!tracer)
|
|
78
|
+
return { file: scriptPath, line: generated.line };
|
|
79
|
+
const original = originalPositionFor(tracer, generated);
|
|
80
|
+
if (original.source == null || original.line == null)
|
|
81
|
+
return undefined;
|
|
82
|
+
const sourceIndex = tracer.sources.indexOf(original.source);
|
|
83
|
+
const resolved = sourceIndex >= 0
|
|
84
|
+
? (tracer.resolvedSources[sourceIndex] ?? original.source)
|
|
85
|
+
: original.source;
|
|
86
|
+
return { file: normalizeSourcePath(resolved), line: original.line };
|
|
87
|
+
};
|
|
88
|
+
const setHits = (file, from, to, count) => {
|
|
89
|
+
let hits = lineHits.get(file);
|
|
90
|
+
if (!hits) {
|
|
91
|
+
hits = new Map();
|
|
92
|
+
lineHits.set(file, hits);
|
|
93
|
+
}
|
|
94
|
+
for (let line = from; line <= to; line++) {
|
|
95
|
+
hits.set(line, count);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
for (const fn of script.functions) {
|
|
99
|
+
// Inner ranges overwrite the outer count (V8 override semantics).
|
|
100
|
+
for (const range of fn.ranges) {
|
|
101
|
+
const start = mapOffset(range.startOffset);
|
|
102
|
+
const end = mapOffset(Math.max(range.startOffset, range.endOffset - 1));
|
|
103
|
+
if (!start && !end)
|
|
104
|
+
continue;
|
|
105
|
+
const file = (start ?? end).file;
|
|
106
|
+
const from = start?.line ?? end.line;
|
|
107
|
+
const to = end && end.file === file ? Math.max(end.line, from) : from;
|
|
108
|
+
setHits(file, from, to, range.count);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export async function mapPreciseCoverage(scripts, getScriptSource, functionsMeta) {
|
|
113
|
+
const lineHits = new Map();
|
|
114
|
+
for (const script of scripts) {
|
|
115
|
+
await scriptToLineCoverage(script, getScriptSource, lineHits);
|
|
116
|
+
}
|
|
117
|
+
return mapLineHitsToReport(lineHits, functionsMeta);
|
|
118
|
+
}
|
|
119
|
+
export function mapLineHitsToReport(lineHits, functionsMeta) {
|
|
120
|
+
const functions = Object.values(functionsMeta)
|
|
121
|
+
.filter((m) => m.sourceFile && !m.sourceFile.endsWith('.gen.ts'))
|
|
122
|
+
.map((m) => {
|
|
123
|
+
const hits = lineHits.get(m.bodySourceFile ?? m.sourceFile);
|
|
124
|
+
const covered = [];
|
|
125
|
+
const missed = [];
|
|
126
|
+
if (hits && m.bodyStart !== undefined && m.bodyEnd !== undefined) {
|
|
127
|
+
for (const [line, count] of hits) {
|
|
128
|
+
if (line < m.bodyStart || line > m.bodyEnd)
|
|
129
|
+
continue;
|
|
130
|
+
if (count > 0)
|
|
131
|
+
covered.push(line);
|
|
132
|
+
else
|
|
133
|
+
missed.push(line);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const total = covered.length + missed.length;
|
|
137
|
+
const ratio = total ? covered.length / total : 0;
|
|
138
|
+
const status = total === 0
|
|
139
|
+
? 'unknown'
|
|
140
|
+
: covered.length === 0
|
|
141
|
+
? 'uncovered'
|
|
142
|
+
: missed.length === 0
|
|
143
|
+
? 'covered'
|
|
144
|
+
: 'partial';
|
|
145
|
+
return {
|
|
146
|
+
name: m.name,
|
|
147
|
+
sourceFile: m.sourceFile,
|
|
148
|
+
exposed: m.expose !== false,
|
|
149
|
+
description: m.description ?? null,
|
|
150
|
+
coveredLines: covered.length,
|
|
151
|
+
totalLines: total,
|
|
152
|
+
missedLines: missed.sort((a, b) => a - b),
|
|
153
|
+
ratio: Number(ratio.toFixed(3)),
|
|
154
|
+
status,
|
|
155
|
+
};
|
|
156
|
+
})
|
|
157
|
+
.sort((a, b) => a.ratio - b.ratio || a.name.localeCompare(b.name));
|
|
158
|
+
const summary = {
|
|
159
|
+
total: functions.length,
|
|
160
|
+
covered: 0,
|
|
161
|
+
partial: 0,
|
|
162
|
+
uncovered: 0,
|
|
163
|
+
unknown: 0,
|
|
164
|
+
overallRatio: 0,
|
|
165
|
+
};
|
|
166
|
+
for (const f of functions)
|
|
167
|
+
summary[f.status]++;
|
|
168
|
+
summary.overallRatio = functions.length
|
|
169
|
+
? Number((functions.reduce((a, f) => a + f.ratio, 0) / functions.length).toFixed(3))
|
|
170
|
+
: 0;
|
|
171
|
+
return {
|
|
172
|
+
generatedAt: new Date().toISOString(),
|
|
173
|
+
summary,
|
|
174
|
+
functions,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
@@ -8,8 +8,8 @@ function toValidIdentifier(name) {
|
|
|
8
8
|
return result;
|
|
9
9
|
}
|
|
10
10
|
export async function saveSchemas(logger, schemaParentDir, schemas, requiredSchemas, supportsImportAttributes, packageName) {
|
|
11
|
-
await writeFileInDir(logger, `${schemaParentDir}/register.gen.ts`, 'export const empty = null;');
|
|
12
11
|
if (requiredSchemas.size === 0) {
|
|
12
|
+
await writeFileInDir(logger, `${schemaParentDir}/register.gen.ts`, 'export const empty = null;');
|
|
13
13
|
logger.info(`• Skipping schemas since none found.\x1b[0m`);
|
|
14
14
|
return;
|
|
15
15
|
}
|