@pikku/inspector 0.12.45 → 0.12.47
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 +45 -0
- package/dist/utils/detect-schema-vendor.d.ts +1 -1
- package/dist/utils/detect-schema-vendor.js +26 -0
- package/dist/utils/post-process.js +13 -0
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +19 -1
- package/package.json +3 -3
- package/src/utils/detect-schema-vendor.test.ts +123 -0
- package/src/utils/detect-schema-vendor.ts +31 -1
- package/src/utils/post-process.test.ts +36 -2
- package/src/utils/post-process.ts +14 -0
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +21 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,48 @@
|
|
|
1
|
+
## 0.12.47
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 91077ff: The auth factory's services are read from the auth definition, not from the handler generated out of it.
|
|
6
|
+
|
|
7
|
+
`pikkuBetterAuth((services) => ...)`'s destructured services are what `authorize` and the session callbacks actually use, and they reach the generated services map by being stamped onto the auth handler's function meta. On the first run in a clean checkout that handler's file has not been written yet — so there is nothing to stamp, nothing to aggregate, and the map comes out marking those services unused. Run it a second time, with the file now on disk, and it says the opposite.
|
|
8
|
+
|
|
9
|
+
The consequence is not cosmetic: `RequiredSingletonServices` is built from that map, so a clean build types the services as optional and tree-shakes them out of the deployed auth worker — the exact failure the stamping exists to prevent, reintroduced by the one case where it cannot run. A CI checkout is always the first run.
|
|
10
|
+
|
|
11
|
+
The definition is inspected from hand-written source on every pass, so its services are now folded into the required set directly. A clean build and an incremental one give the same answer.
|
|
12
|
+
|
|
13
|
+
## 0.12.46
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- a8f9a7d: Distinguish an unresolvable schema type from an unsupported schema library.
|
|
18
|
+
|
|
19
|
+
Both failures shared one message, so a plain `z.object({...})` whose type TypeScript
|
|
20
|
+
could not resolve — a file outside tsconfig `include`, or a generated file such as
|
|
21
|
+
`.pikku/db/zod.gen.ts` that had not been written yet — was reported as
|
|
22
|
+
"Ensure your schema is imported from a supported validation library". The schema is
|
|
23
|
+
dropped either way (`inputSchemaName: null`, no generated schema file), so the message
|
|
24
|
+
is the only signal that a function has silently lost its input contract, and that
|
|
25
|
+
advice is unactionable when the schema already is zod.
|
|
26
|
+
|
|
27
|
+
- eaabcbf: fix(inspector): don't reject plain destructuring in DSL workflows
|
|
28
|
+
|
|
29
|
+
`extractDestructuredDeclaration` reported "Destructuring a step result is not
|
|
30
|
+
supported in DSL workflows" for EVERY destructuring statement whose initializer
|
|
31
|
+
wasn't `await Promise.all([...])` — including ordinary local bindings that
|
|
32
|
+
involve no step at all, such as `const { runId } = input`.
|
|
33
|
+
|
|
34
|
+
Destructuring the workflow's own input is the single most idiomatic line in a
|
|
35
|
+
DSL workflow, so this rejected working workflows wholesale under a message
|
|
36
|
+
about step results that named nothing the author had written.
|
|
37
|
+
|
|
38
|
+
The diagnostic now fires only when the destructured initializer really is a
|
|
39
|
+
step (`await workflow.do(...)`, a parallel group, or a parallel fanout).
|
|
40
|
+
Anything else passes through as a non-step, exactly as the identifier path
|
|
41
|
+
already does for `const x = someLocal`.
|
|
42
|
+
|
|
43
|
+
- Updated dependencies [f11675f]
|
|
44
|
+
- @pikku/core@0.12.68
|
|
45
|
+
|
|
1
46
|
## 0.12.45
|
|
2
47
|
|
|
3
48
|
### Patch Changes
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
1
2
|
import { ErrorCode } from '../error-codes.js';
|
|
3
|
+
/**
|
|
4
|
+
* Whether the checker could type the identifier at all.
|
|
5
|
+
*
|
|
6
|
+
* `any` here is not a schema written in an exotic library — it is TypeScript telling us
|
|
7
|
+
* it could not resolve the declaration, which happens when the schema comes from a file
|
|
8
|
+
* outside the program (not matched by tsconfig `include`) or from a generated file that
|
|
9
|
+
* has not been written yet (`.pikku/db/zod.gen.ts` before `pikku db generate` runs).
|
|
10
|
+
* The distinction matters because the two have opposite fixes, and conflating them told
|
|
11
|
+
* users to "import from a supported validation library" about a plain `z.object`.
|
|
12
|
+
*/
|
|
13
|
+
const isUnresolved = (type) => (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0;
|
|
2
14
|
/**
|
|
3
15
|
* Detect the schema vendor by tracing the type back to its library origin.
|
|
4
16
|
* This handles locally-defined schemas like `export const MySchema = z.object({...})`
|
|
@@ -67,6 +79,20 @@ export const detectSchemaVendor = (identifier, checker) => {
|
|
|
67
79
|
export const detectSchemaVendorOrError = (identifier, checker, logger, context, sourceFile) => {
|
|
68
80
|
const vendor = detectSchemaVendor(identifier, checker);
|
|
69
81
|
if (vendor === 'unknown') {
|
|
82
|
+
// Two different failures used to share one message. Reporting the wrong one is not a
|
|
83
|
+
// cosmetic problem: the schema is dropped either way (`inputSchemaName: null`, no
|
|
84
|
+
// generated schema file), so the message is the only thing standing between the user
|
|
85
|
+
// and a function that silently validates nothing — and "use a supported validation
|
|
86
|
+
// library" is unactionable when the schema already IS `z.object({...})`.
|
|
87
|
+
if (isUnresolved(checker.getTypeAtLocation(identifier))) {
|
|
88
|
+
logger.critical(ErrorCode.INLINE_SCHEMA, `${context} schema '${identifier.text}' could not be TYPED from '${sourceFile}' — ` +
|
|
89
|
+
`TypeScript resolved it to 'any', so its vendor cannot be traced. This is a ` +
|
|
90
|
+
`resolution problem, not a schema problem: the file it is imported from is either ` +
|
|
91
|
+
`outside your tsconfig 'include', or has not been generated yet (a schema built ` +
|
|
92
|
+
`from '.pikku/db/zod.gen.ts' needs 'pikku db generate' to have run first). ` +
|
|
93
|
+
`Fix the import so the type resolves, then re-run.`);
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
70
96
|
logger.critical(ErrorCode.INLINE_SCHEMA, `${context} schema vendor could not be determined from '${sourceFile}'. ` +
|
|
71
97
|
`Supported vendors: zod, valibot, arktype, @effect/schema. ` +
|
|
72
98
|
`Ensure your schema is imported from a supported validation library.`);
|
|
@@ -120,6 +120,19 @@ export function aggregateRequiredServices(state) {
|
|
|
120
120
|
addServices(funcMeta.services);
|
|
121
121
|
}
|
|
122
122
|
});
|
|
123
|
+
// 1b. Services the auth factory touches, read from the definition rather than
|
|
124
|
+
// from the generated handler.
|
|
125
|
+
//
|
|
126
|
+
// `stampAuthHandlerServices` copies them onto that handler's meta, but on the
|
|
127
|
+
// first run in a clean checkout the handler's file has not been written yet,
|
|
128
|
+
// so there is nothing to stamp and nothing to aggregate — and the services map
|
|
129
|
+
// comes out claiming `kysely` (or whatever else `authorize` reads) is unused.
|
|
130
|
+
// The second run, with the file on disk, says the opposite. The definition is
|
|
131
|
+
// inspected from hand-written source either way, so taking the answer from
|
|
132
|
+
// there makes a clean build agree with an incremental one.
|
|
133
|
+
if (state.auth?.definition) {
|
|
134
|
+
addServices(state.auth.definition.services);
|
|
135
|
+
}
|
|
123
136
|
// 2. Services from used middleware (individual + groups)
|
|
124
137
|
usedMiddleware.forEach((middlewareName) => {
|
|
125
138
|
const middlewareMeta = state.middleware.definitions[middlewareName];
|
|
@@ -304,8 +304,15 @@ function extractVariableDeclaration(statement, context) {
|
|
|
304
304
|
*
|
|
305
305
|
* `const [a, b] = await Promise.all([...])` is the idiomatic way to run steps
|
|
306
306
|
* in parallel and keep both results, so each element of the pattern is bound to
|
|
307
|
-
* the matching child step's output. Every other destructuring
|
|
307
|
+
* the matching child step's output. Every other destructuring OF A STEP reports a
|
|
308
308
|
* diagnostic rather than silently dropping the step.
|
|
309
|
+
*
|
|
310
|
+
* A destructure whose initializer is NOT a step is just an ordinary local binding
|
|
311
|
+
* — `const { runId } = input`, `const { a } = someObject` — and has nothing to do
|
|
312
|
+
* with step results. Those pass through as non-steps, exactly as the identifier
|
|
313
|
+
* path already does for `const x = someLocal`. Reporting them was rejecting the
|
|
314
|
+
* single most idiomatic line in a DSL workflow (destructuring the workflow's own
|
|
315
|
+
* input), under a message about step results that named nothing the author wrote.
|
|
309
316
|
*/
|
|
310
317
|
function extractDestructuredDeclaration(statement, decl, context) {
|
|
311
318
|
const init = decl.initializer;
|
|
@@ -342,6 +349,17 @@ function extractDestructuredDeclaration(statement, decl, context) {
|
|
|
342
349
|
}
|
|
343
350
|
}
|
|
344
351
|
}
|
|
352
|
+
// Only a destructure OF A STEP is an error. Anything else is a plain local
|
|
353
|
+
// binding the DSL simply doesn't model as a step.
|
|
354
|
+
const stepCall = init && ts.isAwaitExpression(init) && ts.isCallExpression(init.expression)
|
|
355
|
+
? init.expression
|
|
356
|
+
: null;
|
|
357
|
+
const destructuresAStep = stepCall !== null &&
|
|
358
|
+
(isWorkflowDoCall(stepCall, context.checker) ||
|
|
359
|
+
isParallelGroup(stepCall) ||
|
|
360
|
+
isParallelFanout(stepCall));
|
|
361
|
+
if (!destructuresAStep)
|
|
362
|
+
return null;
|
|
345
363
|
context.errors.push({
|
|
346
364
|
message: `Destructuring a step result is not supported in DSL workflows. Assign it to a variable first (e.g. \`const result = await workflow.do(...)\`) and read its properties.`,
|
|
347
365
|
node: statement,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.47",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"type": "module",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"zod-to-ts": "^2.1.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@pikku/core": "^0.12.
|
|
47
|
+
"@pikku/core": "^0.12.68"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@pikku/core": "^0.12.
|
|
50
|
+
"@pikku/core": "^0.12.68",
|
|
51
51
|
"@types/node": "^24.11.0"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { test, describe, before, after } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'
|
|
4
|
+
import { tmpdir } from 'os'
|
|
5
|
+
import { join } from 'path'
|
|
6
|
+
import * as ts from 'typescript'
|
|
7
|
+
|
|
8
|
+
import { detectSchemaVendorOrError } from './detect-schema-vendor.js'
|
|
9
|
+
|
|
10
|
+
let projectDir: string
|
|
11
|
+
let program: ts.Program
|
|
12
|
+
let checker: ts.TypeChecker
|
|
13
|
+
let sourceFile: ts.SourceFile
|
|
14
|
+
|
|
15
|
+
const criticals: string[] = []
|
|
16
|
+
const logger = {
|
|
17
|
+
debug() {},
|
|
18
|
+
info() {},
|
|
19
|
+
warn() {},
|
|
20
|
+
error() {},
|
|
21
|
+
critical(_code: unknown, message: string) {
|
|
22
|
+
criticals.push(message)
|
|
23
|
+
},
|
|
24
|
+
} as any
|
|
25
|
+
|
|
26
|
+
const initializerIdentifier = (name: string): ts.Identifier => {
|
|
27
|
+
let found: ts.Identifier | undefined
|
|
28
|
+
const visit = (node: ts.Node) => {
|
|
29
|
+
if (
|
|
30
|
+
ts.isVariableDeclaration(node) &&
|
|
31
|
+
ts.isIdentifier(node.name) &&
|
|
32
|
+
node.name.text === name &&
|
|
33
|
+
node.initializer &&
|
|
34
|
+
ts.isIdentifier(node.initializer)
|
|
35
|
+
) {
|
|
36
|
+
found = node.initializer
|
|
37
|
+
}
|
|
38
|
+
ts.forEachChild(node, visit)
|
|
39
|
+
}
|
|
40
|
+
visit(sourceFile)
|
|
41
|
+
assert.ok(found, `no identifier initializer found for '${name}'`)
|
|
42
|
+
return found
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
before(() => {
|
|
46
|
+
projectDir = mkdtempSync(join(tmpdir(), 'pikku-schema-vendor-'))
|
|
47
|
+
mkdirSync(join(projectDir, 'src'))
|
|
48
|
+
writeFileSync(
|
|
49
|
+
join(projectDir, 'src', 'schemas.ts'),
|
|
50
|
+
[
|
|
51
|
+
// Imported from a file that does not exist — exactly what a schema built
|
|
52
|
+
// from an ungenerated '.pikku/db/zod.gen.ts' looks like to the checker.
|
|
53
|
+
`import { UngeneratedSchema } from './not-written-yet.js'`,
|
|
54
|
+
`export const unresolved = UngeneratedSchema`,
|
|
55
|
+
`const plainObject = { a: 1 }`,
|
|
56
|
+
`export const resolvedButNotASchema = plainObject`,
|
|
57
|
+
'',
|
|
58
|
+
].join('\n')
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
program = ts.createProgram([join(projectDir, 'src', 'schemas.ts')], {
|
|
62
|
+
target: ts.ScriptTarget.ESNext,
|
|
63
|
+
module: ts.ModuleKind.NodeNext,
|
|
64
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
65
|
+
strict: true,
|
|
66
|
+
skipLibCheck: true,
|
|
67
|
+
noEmit: true,
|
|
68
|
+
})
|
|
69
|
+
checker = program.getTypeChecker()
|
|
70
|
+
sourceFile = program.getSourceFile(join(projectDir, 'src', 'schemas.ts'))!
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
after(() => {
|
|
74
|
+
rmSync(projectDir, { recursive: true, force: true })
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('detectSchemaVendorOrError', () => {
|
|
78
|
+
test('reports a type the checker could not resolve as a resolution failure', () => {
|
|
79
|
+
criticals.length = 0
|
|
80
|
+
|
|
81
|
+
const vendor = detectSchemaVendorOrError(
|
|
82
|
+
initializerIdentifier('unresolved'),
|
|
83
|
+
checker,
|
|
84
|
+
logger,
|
|
85
|
+
`Function 'createTodo' input`,
|
|
86
|
+
'src/schemas.ts'
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
assert.equal(vendor, undefined)
|
|
90
|
+
assert.equal(criticals.length, 1)
|
|
91
|
+
const [message] = criticals
|
|
92
|
+
assert.match(message, /could not be TYPED/)
|
|
93
|
+
assert.match(message, /UngeneratedSchema/)
|
|
94
|
+
assert.match(message, /pikku db generate/)
|
|
95
|
+
// The unactionable advice must NOT be given for a type that simply failed
|
|
96
|
+
// to resolve — the schema may well already be zod.
|
|
97
|
+
assert.doesNotMatch(
|
|
98
|
+
message,
|
|
99
|
+
/Ensure your schema is imported from a supported validation library/
|
|
100
|
+
)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('still reports a resolvable non-vendor type as an unsupported library', () => {
|
|
104
|
+
criticals.length = 0
|
|
105
|
+
|
|
106
|
+
const vendor = detectSchemaVendorOrError(
|
|
107
|
+
initializerIdentifier('resolvedButNotASchema'),
|
|
108
|
+
checker,
|
|
109
|
+
logger,
|
|
110
|
+
`Function 'createTodo' input`,
|
|
111
|
+
'src/schemas.ts'
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
assert.equal(vendor, undefined)
|
|
115
|
+
assert.equal(criticals.length, 1)
|
|
116
|
+
const [message] = criticals
|
|
117
|
+
assert.match(
|
|
118
|
+
message,
|
|
119
|
+
/Ensure your schema is imported from a supported validation library/
|
|
120
|
+
)
|
|
121
|
+
assert.doesNotMatch(message, /could not be TYPED/)
|
|
122
|
+
})
|
|
123
|
+
})
|
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
2
|
import type { SchemaVendor, InspectorLogger } from '../types.js'
|
|
3
3
|
import { ErrorCode } from '../error-codes.js'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Whether the checker could type the identifier at all.
|
|
7
|
+
*
|
|
8
|
+
* `any` here is not a schema written in an exotic library — it is TypeScript telling us
|
|
9
|
+
* it could not resolve the declaration, which happens when the schema comes from a file
|
|
10
|
+
* outside the program (not matched by tsconfig `include`) or from a generated file that
|
|
11
|
+
* has not been written yet (`.pikku/db/zod.gen.ts` before `pikku db generate` runs).
|
|
12
|
+
* The distinction matters because the two have opposite fixes, and conflating them told
|
|
13
|
+
* users to "import from a supported validation library" about a plain `z.object`.
|
|
14
|
+
*/
|
|
15
|
+
const isUnresolved = (type: ts.Type): boolean =>
|
|
16
|
+
(type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0
|
|
17
|
+
|
|
5
18
|
/**
|
|
6
19
|
* Detect the schema vendor by tracing the type back to its library origin.
|
|
7
20
|
* This handles locally-defined schemas like `export const MySchema = z.object({...})`
|
|
@@ -78,6 +91,23 @@ export const detectSchemaVendorOrError = (
|
|
|
78
91
|
): Exclude<SchemaVendor, 'unknown'> | undefined => {
|
|
79
92
|
const vendor = detectSchemaVendor(identifier, checker)
|
|
80
93
|
if (vendor === 'unknown') {
|
|
94
|
+
// Two different failures used to share one message. Reporting the wrong one is not a
|
|
95
|
+
// cosmetic problem: the schema is dropped either way (`inputSchemaName: null`, no
|
|
96
|
+
// generated schema file), so the message is the only thing standing between the user
|
|
97
|
+
// and a function that silently validates nothing — and "use a supported validation
|
|
98
|
+
// library" is unactionable when the schema already IS `z.object({...})`.
|
|
99
|
+
if (isUnresolved(checker.getTypeAtLocation(identifier))) {
|
|
100
|
+
logger.critical(
|
|
101
|
+
ErrorCode.INLINE_SCHEMA,
|
|
102
|
+
`${context} schema '${identifier.text}' could not be TYPED from '${sourceFile}' — ` +
|
|
103
|
+
`TypeScript resolved it to 'any', so its vendor cannot be traced. This is a ` +
|
|
104
|
+
`resolution problem, not a schema problem: the file it is imported from is either ` +
|
|
105
|
+
`outside your tsconfig 'include', or has not been generated yet (a schema built ` +
|
|
106
|
+
`from '.pikku/db/zod.gen.ts' needs 'pikku db generate' to have run first). ` +
|
|
107
|
+
`Fix the import so the type resolves, then re-run.`
|
|
108
|
+
)
|
|
109
|
+
return undefined
|
|
110
|
+
}
|
|
81
111
|
logger.critical(
|
|
82
112
|
ErrorCode.INLINE_SCHEMA,
|
|
83
113
|
`${context} schema vendor could not be determined from '${sourceFile}'. ` +
|
|
@@ -63,6 +63,7 @@ function makeState(
|
|
|
63
63
|
functionsMeta?: Record<string, any>
|
|
64
64
|
addonFunctions?: Record<string, Record<string, any>>
|
|
65
65
|
addonRequiredParentServices?: string[]
|
|
66
|
+
authServices?: string[]
|
|
66
67
|
} = {}
|
|
67
68
|
): Omit<InspectorState, 'typesLookup'> {
|
|
68
69
|
return {
|
|
@@ -99,6 +100,13 @@ function makeState(
|
|
|
99
100
|
rpc: { internalMeta: {}, exposedMeta: {} },
|
|
100
101
|
addonFunctions: overrides.addonFunctions ?? {},
|
|
101
102
|
addonRequiredParentServices: overrides.addonRequiredParentServices ?? [],
|
|
103
|
+
auth: overrides.authServices
|
|
104
|
+
? {
|
|
105
|
+
definition: {
|
|
106
|
+
services: { optimized: true, services: overrides.authServices },
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
: {},
|
|
102
110
|
} as unknown as Omit<InspectorState, 'typesLookup'>
|
|
103
111
|
}
|
|
104
112
|
|
|
@@ -306,6 +314,26 @@ describe('aggregateRequiredServices — per-function addon services', () => {
|
|
|
306
314
|
})
|
|
307
315
|
})
|
|
308
316
|
|
|
317
|
+
describe('aggregateRequiredServices — the auth factory’s own services', () => {
|
|
318
|
+
test('an auth definition contributes its services before its handler exists', () => {
|
|
319
|
+
// The state a clean checkout produces: the project declares
|
|
320
|
+
// `pikkuBetterAuth`, so the definition is inspected, but the handler the CLI
|
|
321
|
+
// generates from it has not been written yet — so nothing in functions.meta
|
|
322
|
+
// mentions the services `authorize` reads.
|
|
323
|
+
const state = makeState({ authServices: ['kysely', 'jwt'] })
|
|
324
|
+
aggregateRequiredServices(state)
|
|
325
|
+
const required = state.serviceAggregation.requiredServices
|
|
326
|
+
assert.ok(required.has('kysely'))
|
|
327
|
+
assert.ok(required.has('jwt'))
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
test('a project without auth is unaffected', () => {
|
|
331
|
+
const state = makeState()
|
|
332
|
+
aggregateRequiredServices(state)
|
|
333
|
+
assert.equal(state.serviceAggregation.requiredServices.size, 0)
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
|
|
309
337
|
describe('override validation resolves the override target (value), not the logical key', () => {
|
|
310
338
|
test('validateSecretOverrides accepts an override whose value is a declared secret', () => {
|
|
311
339
|
const { logger, criticals } = makeCriticalLogger()
|
|
@@ -422,7 +450,10 @@ describe('validateRemoteAddonDependencies (wireRemoteAddon must be a devDependen
|
|
|
422
450
|
})
|
|
423
451
|
)
|
|
424
452
|
assert.equal(criticals.length, 1)
|
|
425
|
-
assert.equal(
|
|
453
|
+
assert.equal(
|
|
454
|
+
criticals[0]!.code,
|
|
455
|
+
ErrorCode.REMOTE_ADDON_NOT_DEV_DEPENDENCY
|
|
456
|
+
)
|
|
426
457
|
assert.match(criticals[0]!.message, /devDependencies/)
|
|
427
458
|
} finally {
|
|
428
459
|
rmSync(dir, { recursive: true, force: true })
|
|
@@ -441,7 +472,10 @@ describe('validateRemoteAddonDependencies (wireRemoteAddon must be a devDependen
|
|
|
441
472
|
})
|
|
442
473
|
)
|
|
443
474
|
assert.equal(criticals.length, 1)
|
|
444
|
-
assert.equal(
|
|
475
|
+
assert.equal(
|
|
476
|
+
criticals[0]!.code,
|
|
477
|
+
ErrorCode.REMOTE_ADDON_NOT_DEV_DEPENDENCY
|
|
478
|
+
)
|
|
445
479
|
} finally {
|
|
446
480
|
rmSync(dir, { recursive: true, force: true })
|
|
447
481
|
}
|
|
@@ -154,6 +154,20 @@ export function aggregateRequiredServices(
|
|
|
154
154
|
}
|
|
155
155
|
})
|
|
156
156
|
|
|
157
|
+
// 1b. Services the auth factory touches, read from the definition rather than
|
|
158
|
+
// from the generated handler.
|
|
159
|
+
//
|
|
160
|
+
// `stampAuthHandlerServices` copies them onto that handler's meta, but on the
|
|
161
|
+
// first run in a clean checkout the handler's file has not been written yet,
|
|
162
|
+
// so there is nothing to stamp and nothing to aggregate — and the services map
|
|
163
|
+
// comes out claiming `kysely` (or whatever else `authorize` reads) is unused.
|
|
164
|
+
// The second run, with the file on disk, says the opposite. The definition is
|
|
165
|
+
// inspected from hand-written source either way, so taking the answer from
|
|
166
|
+
// there makes a clean build agree with an incremental one.
|
|
167
|
+
if (state.auth?.definition) {
|
|
168
|
+
addServices(state.auth.definition.services)
|
|
169
|
+
}
|
|
170
|
+
|
|
157
171
|
// 2. Services from used middleware (individual + groups)
|
|
158
172
|
usedMiddleware.forEach((middlewareName) => {
|
|
159
173
|
const middlewareMeta = state.middleware.definitions[middlewareName]
|
|
@@ -447,8 +447,15 @@ function extractVariableDeclaration(
|
|
|
447
447
|
*
|
|
448
448
|
* `const [a, b] = await Promise.all([...])` is the idiomatic way to run steps
|
|
449
449
|
* in parallel and keep both results, so each element of the pattern is bound to
|
|
450
|
-
* the matching child step's output. Every other destructuring
|
|
450
|
+
* the matching child step's output. Every other destructuring OF A STEP reports a
|
|
451
451
|
* diagnostic rather than silently dropping the step.
|
|
452
|
+
*
|
|
453
|
+
* A destructure whose initializer is NOT a step is just an ordinary local binding
|
|
454
|
+
* — `const { runId } = input`, `const { a } = someObject` — and has nothing to do
|
|
455
|
+
* with step results. Those pass through as non-steps, exactly as the identifier
|
|
456
|
+
* path already does for `const x = someLocal`. Reporting them was rejecting the
|
|
457
|
+
* single most idiomatic line in a DSL workflow (destructuring the workflow's own
|
|
458
|
+
* input), under a message about step results that named nothing the author wrote.
|
|
452
459
|
*/
|
|
453
460
|
function extractDestructuredDeclaration(
|
|
454
461
|
statement: ts.VariableStatement,
|
|
@@ -499,6 +506,19 @@ function extractDestructuredDeclaration(
|
|
|
499
506
|
}
|
|
500
507
|
}
|
|
501
508
|
|
|
509
|
+
// Only a destructure OF A STEP is an error. Anything else is a plain local
|
|
510
|
+
// binding the DSL simply doesn't model as a step.
|
|
511
|
+
const stepCall =
|
|
512
|
+
init && ts.isAwaitExpression(init) && ts.isCallExpression(init.expression)
|
|
513
|
+
? init.expression
|
|
514
|
+
: null
|
|
515
|
+
const destructuresAStep =
|
|
516
|
+
stepCall !== null &&
|
|
517
|
+
(isWorkflowDoCall(stepCall, context.checker) ||
|
|
518
|
+
isParallelGroup(stepCall) ||
|
|
519
|
+
isParallelFanout(stepCall))
|
|
520
|
+
if (!destructuresAStep) return null
|
|
521
|
+
|
|
502
522
|
context.errors.push({
|
|
503
523
|
message: `Destructuring a step result is not supported in DSL workflows. Assign it to a variable first (e.g. \`const result = await workflow.do(...)\`) and read its properties.`,
|
|
504
524
|
node: statement,
|