@pikku/cli 0.12.43 → 0.12.45
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-CRLT8CXr.js +254 -0
- package/console-app/assets/{index-VleHndkw.css → index-DwyRdRuZ.css} +1 -1
- package/console-app/index.html +2 -2
- 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 +11 -1
- 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 +5 -0
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +5 -0
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +474 -0
- 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 +55 -0
- 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 +14 -1
- package/dist/.pikku/function/pikku-function-types.gen.js +17 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +174 -136
- package/dist/.pikku/function/pikku-functions.gen.js +1 -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 +1 -1
- 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-meta.gen.json +8 -8
- 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 +6 -4
- 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 +13 -5
- package/dist/.pikku/schemas/schemas/FabricAddInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricAddOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricPublishInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricPublishOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.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 +15 -15
- 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 +12 -0
- package/dist/src/fabric/fabric-commands.d.ts +61 -3
- package/dist/src/fabric/fabric-commands.js +27 -1
- package/dist/src/fabric/functions/add.function.d.ts +50 -0
- package/dist/src/fabric/functions/add.function.js +144 -0
- package/dist/src/fabric/functions/publish.function.d.ts +45 -0
- package/dist/src/fabric/functions/publish.function.js +85 -0
- package/dist/src/fabric/functions/validate-core.d.ts +1 -1
- package/dist/src/fabric/functions/validate.function.d.ts +4 -4
- package/dist/src/fabric/functions/validate.function.js +119 -0
- package/dist/src/functions/commands/pikku-command-summary.js +3 -2
- package/dist/src/functions/commands/versions-update.js +10 -4
- package/dist/src/functions/commands/workspace-validate.d.ts +3 -3
- package/dist/src/functions/db/better-auth-schema.js +15 -2
- package/dist/src/functions/db/sqlite/sqlite-kysely.js +11 -3
- package/dist/src/functions/validate/workspace-validate.d.ts +2 -2
- package/dist/src/functions/validate/workspace-validate.js +4 -0
- package/dist/src/functions/wirings/channels/pikku-channels.js +20 -11
- package/dist/src/functions/wirings/channels/pikku-command-channels-map.js +2 -2
- package/dist/src/functions/wirings/channels/pikku-command-channels.js +20 -6
- package/dist/src/functions/wirings/channels/serialize-typed-channel-map.d.ts +1 -1
- package/dist/src/functions/wirings/channels/serialize-typed-channel-map.js +59 -11
- package/dist/src/functions/wirings/cli/pikku-command-cli.js +20 -6
- package/dist/src/functions/wirings/emails/pikku-command-emails.js +48 -19
- package/dist/src/functions/wirings/functions/pikku-command-function-types-split.js +7 -1
- package/dist/src/functions/wirings/functions/serialize-addon-refs.d.ts +14 -0
- package/dist/src/functions/wirings/functions/serialize-addon-refs.js +129 -0
- package/dist/src/functions/wirings/http/pikku-command-http-routes.js +21 -6
- package/dist/src/functions/wirings/http/serialize-typed-http-map.js +12 -0
- package/dist/src/functions/workflows/all.workflow.js +7 -0
- package/dist/src/scaffold/rpc-remote.gen.js +1 -1
- package/dist/src/services/cli-logger-forwarder.service.d.ts +4 -1
- package/dist/src/services/cli-logger-forwarder.service.js +20 -2
- package/dist/src/services/cli-logger.service.d.ts +16 -2
- package/dist/src/services/cli-logger.service.js +33 -5
- package/dist/src/services.js +7 -0
- package/dist/src/utils/pikku-cli-config.js +18 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/skills/pikku-emails/SKILL.md +157 -0
- package/console-app/assets/index-AwGnKyWe.js +0 -254
|
@@ -59,6 +59,41 @@ async function readTextSafe(path) {
|
|
|
59
59
|
return null;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
// Minimum @pikku/* versions Fabric requires. The pikku packages are versioned
|
|
63
|
+
// independently (e.g. @pikku/cli moves faster than @pikku/core), so this is a
|
|
64
|
+
// per-package floor map, not a single number. Only listed packages are
|
|
65
|
+
// enforced — others are skipped to avoid false positives on packages with
|
|
66
|
+
// their own (lower) version lines. Bump these as the supported floor moves.
|
|
67
|
+
// - @pikku/cli < 0.12.43 ships a `pikku dev` that hangs without ever
|
|
68
|
+
// listening (the sandbox never serves routes).
|
|
69
|
+
// - @pikku/core mismatches split pikkuState into duplicate copies, so app
|
|
70
|
+
// and console routes 404; pin the floor that matches the runtime.
|
|
71
|
+
const PIKKU_MIN_VERSIONS = {
|
|
72
|
+
'@pikku/cli': '0.12.43',
|
|
73
|
+
'@pikku/core': '0.12.34',
|
|
74
|
+
};
|
|
75
|
+
// Pull major.minor.patch from a spec, ignoring range prefixes (^ ~ >=),
|
|
76
|
+
// npm: aliases, and pre-release/build suffixes. null if no semver is present
|
|
77
|
+
// (file:, workspace:, *, latest — resolved only at install time).
|
|
78
|
+
function parseSemver(spec) {
|
|
79
|
+
const m = spec.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
80
|
+
if (!m)
|
|
81
|
+
return null;
|
|
82
|
+
return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
|
|
83
|
+
}
|
|
84
|
+
function semverLt(a, b) {
|
|
85
|
+
for (let i = 0; i < 3; i++) {
|
|
86
|
+
if (a[i] !== b[i])
|
|
87
|
+
return a[i] < b[i];
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
// Fall back to the installed version when the spec carries no semver
|
|
92
|
+
// (file:/workspace:/* deps resolve to a concrete version on disk).
|
|
93
|
+
async function installedSemver(root, pkg) {
|
|
94
|
+
const j = await readJsonSafe(join(root, 'node_modules', pkg, 'package.json'));
|
|
95
|
+
return j?.version ? parseSemver(j.version) : null;
|
|
96
|
+
}
|
|
62
97
|
// PostgreSQL-specific syntax that won't work on SQLite/libSQL (Turso)
|
|
63
98
|
const POSTGRES_SQL_PATTERNS = [
|
|
64
99
|
{
|
|
@@ -146,6 +181,59 @@ export async function runValidate(startDir = process.cwd()) {
|
|
|
146
181
|
}
|
|
147
182
|
}
|
|
148
183
|
}
|
|
184
|
+
// ── @pikku/* minimum versions ──────────────────────────────────────────
|
|
185
|
+
// Scan every workspace manifest for @pikku/* deps below the required floor.
|
|
186
|
+
// A stale @pikku/cli hangs `pikku dev`; a stale @pikku/core duplicates
|
|
187
|
+
// pikkuState and 404s every route — both are hard blockers, so error.
|
|
188
|
+
{
|
|
189
|
+
const manifestPaths = [rootPkgPath];
|
|
190
|
+
for (const group of ['packages', 'apps']) {
|
|
191
|
+
const groupDir = join(root, group);
|
|
192
|
+
if (!existsSync(groupDir))
|
|
193
|
+
continue;
|
|
194
|
+
try {
|
|
195
|
+
for (const d of await readdir(groupDir, { withFileTypes: true })) {
|
|
196
|
+
if (d.isDirectory()) {
|
|
197
|
+
manifestPaths.push(join(groupDir, d.name, 'package.json'));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// ignore
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const lowestByPkg = new Map();
|
|
206
|
+
for (const mPath of manifestPaths) {
|
|
207
|
+
const m = await readJsonSafe(mPath);
|
|
208
|
+
if (!m)
|
|
209
|
+
continue;
|
|
210
|
+
const deps = {
|
|
211
|
+
...m.dependencies,
|
|
212
|
+
...m.devDependencies,
|
|
213
|
+
...m.peerDependencies,
|
|
214
|
+
};
|
|
215
|
+
for (const [pkg, spec] of Object.entries(deps)) {
|
|
216
|
+
if (!pkg.startsWith('@pikku/') || !(pkg in PIKKU_MIN_VERSIONS))
|
|
217
|
+
continue;
|
|
218
|
+
if (typeof spec !== 'string')
|
|
219
|
+
continue;
|
|
220
|
+
const version = parseSemver(spec) ?? (await installedSemver(root, pkg));
|
|
221
|
+
if (!version)
|
|
222
|
+
continue;
|
|
223
|
+
const prev = lowestByPkg.get(pkg);
|
|
224
|
+
if (!prev || semverLt(version, prev.version)) {
|
|
225
|
+
lowestByPkg.set(pkg, { version, manifest: mPath, spec });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const [pkg, seen] of lowestByPkg) {
|
|
230
|
+
const floorStr = PIKKU_MIN_VERSIONS[pkg];
|
|
231
|
+
const floor = parseSemver(floorStr);
|
|
232
|
+
if (floor && semverLt(seen.version, floor)) {
|
|
233
|
+
e(`pikku-version-below-min-${pkg.replace(/[@/]/g, '-')}`, `${pkg} is ${seen.version.join('.')} (spec "${seen.spec}") — Fabric requires >= ${floorStr}`, seen.manifest, lines(`Bump ${pkg} to ^${floorStr} (or newer) and reinstall:`, ` yarn up ${pkg}@^${floorStr}`, 'Then run `yarn install` and re-run `pikku fabric validate`.'));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
149
237
|
// ── packages/functions/ ────────────────────────────────────────────────
|
|
150
238
|
const fnDir = join(root, 'packages', 'functions');
|
|
151
239
|
const functionsSdkPkgName = (await readJsonSafe(join(root, 'packages', 'functions-sdk', 'package.json')))?.name;
|
|
@@ -199,6 +287,37 @@ export async function runValidate(startDir = process.cwd()) {
|
|
|
199
287
|
e('missing-kysely-sqlite', 'services.ts imports @pikku/kysely-sqlite but it is not in root package.json', rootPkgPath, 'Add "@pikku/kysely-sqlite": "file:./vendor/pikku-kysely-sqlite.tgz" to dependencies');
|
|
200
288
|
}
|
|
201
289
|
}
|
|
290
|
+
// ── better-auth client baseURL must include the /auth segment ──────────
|
|
291
|
+
// The Fabric deploy edge keeps the /api prefix for the better-auth unit
|
|
292
|
+
// (it registers /api/auth/*) and strips /api only for the other units; the
|
|
293
|
+
// sandbox Caddy mirrors that with a non-stripping /api/auth/* handler. So
|
|
294
|
+
// the DEFAULT basePath (/api/auth) is the CORRECT server config — do NOT
|
|
295
|
+
// override it. The real footgun is the client: better-auth appends the
|
|
296
|
+
// endpoint to baseURL verbatim, so a bare /api baseURL yields
|
|
297
|
+
// /api/sign-in/email (no /auth) and 404s. The client baseURL must resolve
|
|
298
|
+
// to /api/auth.
|
|
299
|
+
const appsDir = join(root, 'apps');
|
|
300
|
+
if (existsSync(appsDir)) {
|
|
301
|
+
try {
|
|
302
|
+
const appFiles = (await readdir(appsDir, { recursive: true })).filter((f) => typeof f === 'string' &&
|
|
303
|
+
(f.endsWith('.ts') || f.endsWith('.tsx')) &&
|
|
304
|
+
!f.includes('node_modules'));
|
|
305
|
+
for (const rel of appFiles) {
|
|
306
|
+
const text = await readTextSafe(join(appsDir, rel));
|
|
307
|
+
if (!text || !/\bcreateAuthClient\s*\(/.test(text))
|
|
308
|
+
continue;
|
|
309
|
+
const baseURL = text.match(/createAuthClient\s*\([^)]*baseURL\s*:\s*([^,)\n]+)/)?.[1];
|
|
310
|
+
// Heuristic: flag a bare /api baseURL with no /auth segment anywhere
|
|
311
|
+
// near the client config.
|
|
312
|
+
if (baseURL && /['"`]\/api['"`]/.test(baseURL) && !/auth/i.test(baseURL)) {
|
|
313
|
+
w('better-auth-client-baseurl-missing-auth', `createAuthClient baseURL is ${baseURL.trim()} — it omits the /auth segment, so the client calls /api/sign-in/email instead of /api/auth/sign-in/email and auth 404s`, join(appsDir, rel), "Append the auth basePath: baseURL: `${apiUrl()}/auth` (resolving to /api/auth)");
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
// readdir failure — skip
|
|
319
|
+
}
|
|
320
|
+
}
|
|
202
321
|
// Database layout is declared by pikku.config.json db.engine.
|
|
203
322
|
const migrationsDir = join(root, 'db', dbEngine === 'postgres' ? 'postgres' : 'sqlite');
|
|
204
323
|
if (!existsSync(migrationsDir)) {
|
|
@@ -54,8 +54,9 @@ export const pikkuSummary = pikkuSessionlessFunc({
|
|
|
54
54
|
// stdout (which would break NDJSON consumers).
|
|
55
55
|
logger.info({ message: summary.format(), type: 'summary' });
|
|
56
56
|
}
|
|
57
|
-
if (logger.
|
|
58
|
-
|
|
57
|
+
if (logger.hasBlockingDiagnostics()) {
|
|
58
|
+
const severities = logger.blockingSeverities().join(', ');
|
|
59
|
+
throw new Error(`Pikku inspection failed due to ${severities} diagnostics`);
|
|
59
60
|
}
|
|
60
61
|
},
|
|
61
62
|
});
|
|
@@ -12,11 +12,17 @@ export const pikkuVersionsUpdate = pikkuSessionlessFunc({
|
|
|
12
12
|
}
|
|
13
13
|
const immutabilityErrors = visitState.manifest.errors.filter((e) => e.code === ErrorCode.FUNCTION_VERSION_MODIFIED);
|
|
14
14
|
if (immutabilityErrors.length > 0) {
|
|
15
|
+
// A published contract changed without a version bump. We must not save
|
|
16
|
+
// (that would overwrite an immutable record), but a contract drift should
|
|
17
|
+
// not crash `pikku all` / the dev server. Surface it as an `error`
|
|
18
|
+
// diagnostic: printed always, blocking only under `--fail-on-error`.
|
|
19
|
+
// `pikku versions check` remains the hard deploy gate.
|
|
15
20
|
for (const e of immutabilityErrors) {
|
|
16
|
-
logger.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
logger.diagnostic({
|
|
22
|
+
severity: 'error',
|
|
23
|
+
code: ErrorCode.FUNCTION_VERSION_MODIFIED,
|
|
24
|
+
message: e.message,
|
|
25
|
+
});
|
|
20
26
|
}
|
|
21
27
|
return;
|
|
22
28
|
}
|
|
@@ -4,7 +4,7 @@ export declare const workspaceValidate: import("#pikku").PikkuFunctionConfig<Rec
|
|
|
4
4
|
root: string;
|
|
5
5
|
findings: {
|
|
6
6
|
id: string;
|
|
7
|
-
severity: "
|
|
7
|
+
severity: "warn" | "error" | "info";
|
|
8
8
|
message: string;
|
|
9
9
|
path: string;
|
|
10
10
|
fixHint: string;
|
|
@@ -14,7 +14,7 @@ export declare const workspaceValidate: import("#pikku").PikkuFunctionConfig<Rec
|
|
|
14
14
|
root: string;
|
|
15
15
|
findings: {
|
|
16
16
|
id: string;
|
|
17
|
-
severity: "
|
|
17
|
+
severity: "warn" | "error" | "info";
|
|
18
18
|
message: string;
|
|
19
19
|
path: string;
|
|
20
20
|
fixHint: string;
|
|
@@ -24,7 +24,7 @@ export declare const workspaceValidate: import("#pikku").PikkuFunctionConfig<Rec
|
|
|
24
24
|
root: string;
|
|
25
25
|
findings: {
|
|
26
26
|
id: string;
|
|
27
|
-
severity: "
|
|
27
|
+
severity: "warn" | "error" | "info";
|
|
28
28
|
message: string;
|
|
29
29
|
path: string;
|
|
30
30
|
fixHint: string;
|
|
@@ -3,7 +3,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
3
3
|
import { readdirSync, statSync, readFileSync, existsSync } from 'node:fs';
|
|
4
4
|
import { join, extname, dirname } from 'node:path';
|
|
5
5
|
import { PIKKU_BETTER_AUTH } from '@pikku/better-auth';
|
|
6
|
-
import {
|
|
6
|
+
import { LocalVariablesService } from '@pikku/core/services';
|
|
7
7
|
import { loadUserModule } from '../commands/load-user-project.js';
|
|
8
8
|
let cachedGetMigrations = null;
|
|
9
9
|
async function loadGetMigrations() {
|
|
@@ -87,9 +87,22 @@ async function loadAuthFactory(sourceFile) {
|
|
|
87
87
|
}
|
|
88
88
|
return null;
|
|
89
89
|
}
|
|
90
|
+
// Schema-only auth introspection never executes auth — it just reads the Better
|
|
91
|
+
// Auth options to derive the table/column shape. Secret *values* don't affect the
|
|
92
|
+
// schema, so we hand the factory a fake secret service that resolves every key to
|
|
93
|
+
// a placeholder. This keeps `pikku db migrate`'s drift check from requiring the
|
|
94
|
+
// app's real secrets (BETTER_AUTH_SECRET etc.) to be present in the environment.
|
|
95
|
+
function fakeSecretService() {
|
|
96
|
+
const placeholder = 'schema-introspection-only';
|
|
97
|
+
return {
|
|
98
|
+
getSecret: async () => placeholder,
|
|
99
|
+
hasSecret: async () => true,
|
|
100
|
+
setSecret: async () => { },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
90
103
|
function schemaServicesStub(kysely, logger) {
|
|
91
104
|
const variables = new LocalVariablesService();
|
|
92
|
-
const secrets =
|
|
105
|
+
const secrets = fakeSecretService();
|
|
93
106
|
const base = {
|
|
94
107
|
kysely,
|
|
95
108
|
logger,
|
|
@@ -12,12 +12,19 @@ function coerce(v) {
|
|
|
12
12
|
return JSON.stringify(v);
|
|
13
13
|
return v;
|
|
14
14
|
}
|
|
15
|
+
// A statement returns rows when it is a SELECT or carries a RETURNING clause.
|
|
16
|
+
// node:sqlite's StatementSync has no `reader` flag (always undefined), so without
|
|
17
|
+
// this kysely would run INSERT ... RETURNING via `.run()` and drop the returned
|
|
18
|
+
// rows — which breaks better-auth sign-up (it inserts and expects the row back).
|
|
19
|
+
function isReaderSql(sql) {
|
|
20
|
+
return /^\s*select/i.test(sql) || /\breturning\b/i.test(sql);
|
|
21
|
+
}
|
|
15
22
|
class RuntimeSqliteStatement {
|
|
16
23
|
stmt;
|
|
17
24
|
reader;
|
|
18
|
-
constructor(stmt) {
|
|
25
|
+
constructor(stmt, reader) {
|
|
19
26
|
this.stmt = stmt;
|
|
20
|
-
this.reader =
|
|
27
|
+
this.reader = reader;
|
|
21
28
|
}
|
|
22
29
|
all(parameters) {
|
|
23
30
|
return this.stmt.all(...parameters.map(coerce));
|
|
@@ -41,7 +48,8 @@ class RuntimeSqliteDatabase {
|
|
|
41
48
|
this.db = db;
|
|
42
49
|
}
|
|
43
50
|
prepare(sql) {
|
|
44
|
-
|
|
51
|
+
const stmt = this.db.prepare(sql);
|
|
52
|
+
return new RuntimeSqliteStatement(stmt, Boolean(stmt.reader) || isReaderSql(sql));
|
|
45
53
|
}
|
|
46
54
|
close() {
|
|
47
55
|
this.db.close();
|
|
@@ -2,9 +2,9 @@ import { z } from 'zod';
|
|
|
2
2
|
export declare const FindingSchema: z.ZodObject<{
|
|
3
3
|
id: z.ZodString;
|
|
4
4
|
severity: z.ZodEnum<{
|
|
5
|
-
info: "info";
|
|
6
5
|
warn: "warn";
|
|
7
6
|
error: "error";
|
|
7
|
+
info: "info";
|
|
8
8
|
}>;
|
|
9
9
|
message: z.ZodString;
|
|
10
10
|
path: z.ZodString;
|
|
@@ -18,9 +18,9 @@ export declare const WorkspaceValidateOutput: z.ZodObject<{
|
|
|
18
18
|
findings: z.ZodArray<z.ZodObject<{
|
|
19
19
|
id: z.ZodString;
|
|
20
20
|
severity: z.ZodEnum<{
|
|
21
|
-
info: "info";
|
|
22
21
|
warn: "warn";
|
|
23
22
|
error: "error";
|
|
23
|
+
info: "info";
|
|
24
24
|
}>;
|
|
25
25
|
message: z.ZodString;
|
|
26
26
|
path: z.ZodString;
|
|
@@ -95,6 +95,10 @@ export async function runWorkspaceValidate(startDir = process.cwd()) {
|
|
|
95
95
|
if (!pikkuConfig.clientFiles) {
|
|
96
96
|
info('pikku-config-no-client-files', 'pikku.config.json missing "clientFiles" — generated RPC client files and React Query hooks will not be written', pikkuConfigPath, 'Add clientFiles.rpcMapDeclarationFile and clientFiles.reactQueryFile pointing to packages/functions-sdk/src/pikku/ (for example: rpc-map.gen.d.ts and api.gen.ts)');
|
|
97
97
|
}
|
|
98
|
+
const scaffold = pikkuConfig.scaffold;
|
|
99
|
+
if (!scaffold?.console) {
|
|
100
|
+
e('pikku-config-no-console-scaffold', 'pikku.config.json missing "scaffold.console" — Fabric cannot introspect the running app (console:getFunctionsMeta and friends 404), so the sandbox builder shows no functions', pikkuConfigPath, 'Add "console": "no-auth" under "scaffold" in pikku.config.json (use "auth" to require a session)');
|
|
101
|
+
}
|
|
98
102
|
}
|
|
99
103
|
const rootPkgPath = join(root, 'package.json');
|
|
100
104
|
const rootPkg = await readJsonSafe(rootPkgPath);
|
|
@@ -6,19 +6,28 @@ import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
|
|
|
6
6
|
export const pikkuChannels = pikkuVoidFunc({
|
|
7
7
|
func: async ({ logger, config, getInspectorState }) => {
|
|
8
8
|
const visitState = await getInspectorState();
|
|
9
|
-
const { channelsWiringFile, channelsWiringMetaFile, channelsWiringMetaJsonFile, packageMappings, schema, } = config;
|
|
10
|
-
const { channels } = visitState;
|
|
11
|
-
|
|
9
|
+
const { channelsWiringFile, channelsWiringMetaFile, channelsWiringMetaJsonFile, channelContractsMetaJsonFile, packageMappings, schema, } = config;
|
|
10
|
+
const { channels, exportedContracts } = visitState;
|
|
11
|
+
const hasChannelContracts = Object.keys(exportedContracts.channel).length > 0;
|
|
12
|
+
if ((channels.files.size === 0 || Object.keys(channels.meta).length === 0) &&
|
|
13
|
+
!hasChannelContracts) {
|
|
12
14
|
return;
|
|
13
15
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
if (channels.files.size > 0 && Object.keys(channels.meta).length > 0) {
|
|
17
|
+
await writeFileInDir(logger, channelsWiringFile, serializeFileImports('addChannel', channelsWiringFile, channels.files, packageMappings));
|
|
18
|
+
}
|
|
19
|
+
if (Object.keys(channels.meta).length > 0) {
|
|
20
|
+
await writeFileInDir(logger, channelsWiringMetaJsonFile, JSON.stringify(channels.meta, null, 2));
|
|
21
|
+
}
|
|
22
|
+
await writeFileInDir(logger, channelContractsMetaJsonFile, JSON.stringify(exportedContracts.channel, null, 2));
|
|
23
|
+
if (Object.keys(channels.meta).length > 0) {
|
|
24
|
+
const jsonImportPath = getFileImportRelativePath(channelsWiringMetaFile, channelsWiringMetaJsonFile, packageMappings);
|
|
25
|
+
const supportsImportAttributes = schema?.supportsImportAttributes ?? false;
|
|
26
|
+
const importStatement = supportsImportAttributes
|
|
27
|
+
? `import metaData from '${jsonImportPath}' with { type: 'json' }`
|
|
28
|
+
: `import metaData from '${jsonImportPath}'`;
|
|
29
|
+
await writeFileInDir(logger, channelsWiringMetaFile, `import { pikkuState } from '@pikku/core/internal'\nimport { ChannelsMeta } from '@pikku/core/channel'\n${importStatement}\npikkuState(null, 'channel', 'meta', metaData as ChannelsMeta)`);
|
|
30
|
+
}
|
|
22
31
|
},
|
|
23
32
|
middleware: [
|
|
24
33
|
logCommandInfoAndTime({
|
|
@@ -5,8 +5,8 @@ import { serializeTypedChannelsMap } from './serialize-typed-channel-map.js';
|
|
|
5
5
|
export const pikkuChannelsMap = pikkuSessionlessFunc({
|
|
6
6
|
func: async ({ logger, config, getInspectorState }) => {
|
|
7
7
|
const state = await getInspectorState();
|
|
8
|
-
const { channelsMapDeclarationFile, packageMappings } = config;
|
|
9
|
-
const content = serializeTypedChannelsMap(logger, channelsMapDeclarationFile, packageMappings, state.functions.typesMap, state.functions.meta, state.addonFunctions, state.channels.meta);
|
|
8
|
+
const { channelsMapDeclarationFile, packageMappings, rpcInternalMapDeclarationFile, } = config;
|
|
9
|
+
const content = serializeTypedChannelsMap(logger, channelsMapDeclarationFile, packageMappings, state.functions.typesMap, state.functions.meta, state.addonFunctions, state.channels.meta, rpcInternalMapDeclarationFile);
|
|
10
10
|
await writeFileInDir(logger, channelsMapDeclarationFile, content);
|
|
11
11
|
},
|
|
12
12
|
middleware: [
|
|
@@ -7,21 +7,35 @@ import { stripVerboseFields, hasVerboseFields, } from '../../../utils/strip-verb
|
|
|
7
7
|
export const pikkuCommandChannels = pikkuSessionlessFunc({
|
|
8
8
|
func: async ({ logger, config, getInspectorState }) => {
|
|
9
9
|
const visitState = await getInspectorState();
|
|
10
|
-
const { channelsWiringFile, channelsWiringMetaFile, channelsWiringMetaJsonFile, packageMappings, schema, } = config;
|
|
11
|
-
const { channels } = visitState;
|
|
12
|
-
|
|
10
|
+
const { channelsWiringFile, channelsWiringMetaFile, channelsWiringMetaJsonFile, channelContractsMetaJsonFile, channelContractsMetaFile, packageMappings, schema, } = config;
|
|
11
|
+
const { channels, exportedContracts } = visitState;
|
|
12
|
+
const hasChannelContracts = Object.keys(exportedContracts.channel).length > 0;
|
|
13
|
+
if ((channels.files.size === 0 || Object.keys(channels.meta).length === 0) &&
|
|
14
|
+
!hasChannelContracts) {
|
|
13
15
|
return undefined;
|
|
14
16
|
}
|
|
17
|
+
// The bootstrap imports channelsWiringFile and channelsWiringMetaFile
|
|
18
|
+
// whenever this command reports channels as active (truthy return), so both
|
|
19
|
+
// must always be written once past the guard above — including the
|
|
20
|
+
// contracts-only case where there are no local channel source files
|
|
21
|
+
// (channels.files is empty). Skipping either leaves the bootstrap importing
|
|
22
|
+
// a file that was never generated and the per-unit deploy bundle fails.
|
|
15
23
|
await writeFileInDir(logger, channelsWiringFile, serializeFileImports('addChannel', channelsWiringFile, channels.files, packageMappings));
|
|
16
|
-
// Write minimal JSON (runtime-only fields)
|
|
17
24
|
const minimalMeta = stripVerboseFields(channels.meta);
|
|
18
25
|
await writeFileInDir(logger, channelsWiringMetaJsonFile, JSON.stringify(minimalMeta, null, 2));
|
|
19
|
-
// Write verbose JSON only if it has additional fields
|
|
20
26
|
if (hasVerboseFields(channels.meta)) {
|
|
21
27
|
const verbosePath = channelsWiringMetaJsonFile.replace(/\.gen\.json$/, '-verbose.gen.json');
|
|
22
28
|
await writeFileInDir(logger, verbosePath, JSON.stringify(channels.meta, null, 2));
|
|
23
29
|
}
|
|
24
|
-
|
|
30
|
+
await writeFileInDir(logger, channelContractsMetaJsonFile, JSON.stringify(exportedContracts.channel, null, 2));
|
|
31
|
+
if (hasChannelContracts) {
|
|
32
|
+
const contractsJsonImportPath = getFileImportRelativePath(channelContractsMetaFile, channelContractsMetaJsonFile, packageMappings);
|
|
33
|
+
const supportsImportAttributes = schema?.supportsImportAttributes ?? false;
|
|
34
|
+
const contractsImportStatement = supportsImportAttributes
|
|
35
|
+
? `import contractsMeta from '${contractsJsonImportPath}' with { type: 'json' }`
|
|
36
|
+
: `import contractsMeta from '${contractsJsonImportPath}'`;
|
|
37
|
+
await writeFileInDir(logger, channelContractsMetaFile, `${contractsImportStatement}\nexport default contractsMeta`);
|
|
38
|
+
}
|
|
25
39
|
const jsonImportPath = getFileImportRelativePath(channelsWiringMetaFile, channelsWiringMetaJsonFile, packageMappings);
|
|
26
40
|
const supportsImportAttributes = schema?.supportsImportAttributes ?? false;
|
|
27
41
|
const importStatement = supportsImportAttributes
|
|
@@ -2,4 +2,4 @@ import type { ChannelsMeta } from '@pikku/core/channel';
|
|
|
2
2
|
import type { TypesMap } from '@pikku/inspector';
|
|
3
3
|
import type { FunctionsMeta } from '@pikku/core';
|
|
4
4
|
import type { Logger } from '@pikku/core/services';
|
|
5
|
-
export declare const serializeTypedChannelsMap: (logger: Logger, relativeToPath: string, packageMappings: Record<string, string>, typesMap: TypesMap, functionsMeta: FunctionsMeta, addonFunctions: Record<string, FunctionsMeta>, channelsMeta: ChannelsMeta) => string;
|
|
5
|
+
export declare const serializeTypedChannelsMap: (logger: Logger, relativeToPath: string, packageMappings: Record<string, string>, typesMap: TypesMap, functionsMeta: FunctionsMeta, addonFunctions: Record<string, FunctionsMeta>, channelsMeta: ChannelsMeta, rpcInternalMapDeclarationFile: string) => string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { serializeImportMap } from '../../../utils/serialize-import-map.js';
|
|
2
|
+
import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
|
|
2
3
|
import { generateCustomTypes, resolveFunctionMeta } from '@pikku/inspector';
|
|
3
|
-
export const serializeTypedChannelsMap = (logger, relativeToPath, packageMappings, typesMap, functionsMeta, addonFunctions, channelsMeta) => {
|
|
4
|
-
const { channels, requiredTypes } = generateChannels(functionsMeta, addonFunctions, channelsMeta);
|
|
4
|
+
export const serializeTypedChannelsMap = (logger, relativeToPath, packageMappings, typesMap, functionsMeta, addonFunctions, channelsMeta, rpcInternalMapDeclarationFile) => {
|
|
5
|
+
const { channels, requiredTypes } = generateChannels(logger, typesMap, functionsMeta, addonFunctions, channelsMeta);
|
|
5
6
|
typesMap.customTypes.forEach(({ references }) => {
|
|
6
7
|
for (const reference of references) {
|
|
7
8
|
if (reference !== '__object' && !reference.startsWith('__object_')) {
|
|
@@ -9,13 +10,25 @@ export const serializeTypedChannelsMap = (logger, relativeToPath, packageMapping
|
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
});
|
|
13
|
+
const needsFlattenedRPCMap = Array.from(requiredTypes).some((t) => t.includes('FlattenedRPCMap'));
|
|
14
|
+
if (needsFlattenedRPCMap) {
|
|
15
|
+
for (const t of Array.from(requiredTypes)) {
|
|
16
|
+
if (t.includes('FlattenedRPCMap')) {
|
|
17
|
+
requiredTypes.delete(t);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
12
21
|
const imports = serializeImportMap(logger, relativeToPath, packageMappings, typesMap, requiredTypes);
|
|
22
|
+
const rpcMapImport = needsFlattenedRPCMap
|
|
23
|
+
? `import type { FlattenedRPCMap } from '${getFileImportRelativePath(relativeToPath, rpcInternalMapDeclarationFile, packageMappings)}'`
|
|
24
|
+
: '';
|
|
13
25
|
const serializedCustomTypes = generateCustomTypes(typesMap, requiredTypes);
|
|
14
26
|
return `/**
|
|
15
27
|
* This provides the structure needed for TypeScript to be aware of channels
|
|
16
28
|
*/
|
|
17
29
|
|
|
18
30
|
${imports}
|
|
31
|
+
${rpcMapImport}
|
|
19
32
|
${serializedCustomTypes}
|
|
20
33
|
|
|
21
34
|
interface ChannelHandler<I, O> {
|
|
@@ -40,7 +53,7 @@ export type ChannelWiringHandlerOf<
|
|
|
40
53
|
: never;
|
|
41
54
|
`;
|
|
42
55
|
};
|
|
43
|
-
function generateChannels(functionsMeta, addonFunctions, channelsMeta) {
|
|
56
|
+
function generateChannels(logger, typesMap, functionsMeta, addonFunctions, channelsMeta) {
|
|
44
57
|
const state = { functions: { meta: functionsMeta }, addonFunctions };
|
|
45
58
|
const requiredTypes = new Set();
|
|
46
59
|
const channelsObject = {};
|
|
@@ -57,17 +70,30 @@ function generateChannels(functionsMeta, addonFunctions, channelsMeta) {
|
|
|
57
70
|
const inputTypes = func.inputs || null;
|
|
58
71
|
const outputTypes = func.outputs || null;
|
|
59
72
|
channelsObject[name].message = {
|
|
60
|
-
inputs: inputTypes,
|
|
61
|
-
outputs: outputTypes,
|
|
73
|
+
inputs: normalizeTypes(logger, typesMap, inputTypes),
|
|
74
|
+
outputs: normalizeTypes(logger, typesMap, outputTypes),
|
|
62
75
|
};
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
channelsObject[name].message.inputs?.forEach((type) => requiredTypes.add(type));
|
|
77
|
+
channelsObject[name].message.outputs?.forEach((type) => requiredTypes.add(type));
|
|
65
78
|
}
|
|
66
79
|
for (const [key, route] of Object.entries(messageWirings)) {
|
|
67
80
|
if (!channelsObject[name].routes[key]) {
|
|
68
81
|
channelsObject[name].routes[key] = {};
|
|
69
82
|
}
|
|
70
83
|
for (const [method, { pikkuFuncId }] of Object.entries(route)) {
|
|
84
|
+
// Addon functions are namespaced ('ns:fn') and their types aren't in
|
|
85
|
+
// the consumer's local typesMap, but are reachable via FlattenedRPCMap.
|
|
86
|
+
if (pikkuFuncId.includes(':')) {
|
|
87
|
+
const inputType = `FlattenedRPCMap['${pikkuFuncId}']['input']`;
|
|
88
|
+
const outputType = `FlattenedRPCMap['${pikkuFuncId}']['output']`;
|
|
89
|
+
channelsObject[name].routes[key][method] = {
|
|
90
|
+
inputTypes: [inputType],
|
|
91
|
+
outputTypes: [outputType],
|
|
92
|
+
};
|
|
93
|
+
requiredTypes.add(inputType);
|
|
94
|
+
requiredTypes.add(outputType);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
71
97
|
const func = resolveFunctionMeta(state, pikkuFuncId);
|
|
72
98
|
if (!func) {
|
|
73
99
|
throw new Error(`Function ${pikkuFuncId} not found in functionsMeta for channel ${name}, route ${key}, method ${method}`);
|
|
@@ -75,11 +101,11 @@ function generateChannels(functionsMeta, addonFunctions, channelsMeta) {
|
|
|
75
101
|
const inputTypes = func.inputs || null;
|
|
76
102
|
const outputTypes = func.outputs || null;
|
|
77
103
|
channelsObject[name].routes[key][method] = {
|
|
78
|
-
inputTypes,
|
|
79
|
-
outputTypes,
|
|
104
|
+
inputTypes: normalizeTypes(logger, typesMap, inputTypes),
|
|
105
|
+
outputTypes: normalizeTypes(logger, typesMap, outputTypes),
|
|
80
106
|
};
|
|
81
|
-
inputTypes?.forEach((type) => requiredTypes.add(type));
|
|
82
|
-
outputTypes?.forEach((type) => requiredTypes.add(type));
|
|
107
|
+
channelsObject[name].routes[key][method].inputTypes?.forEach((type) => requiredTypes.add(type));
|
|
108
|
+
channelsObject[name].routes[key][method].outputTypes?.forEach((type) => requiredTypes.add(type));
|
|
83
109
|
}
|
|
84
110
|
}
|
|
85
111
|
}
|
|
@@ -112,3 +138,25 @@ function generateChannels(functionsMeta, addonFunctions, channelsMeta) {
|
|
|
112
138
|
function formatTypeArray(types) {
|
|
113
139
|
return types ? types.join(' | ') : 'null';
|
|
114
140
|
}
|
|
141
|
+
function normalizeTypes(logger, typesMap, types) {
|
|
142
|
+
if (!types || types.length === 0)
|
|
143
|
+
return types;
|
|
144
|
+
const resolved = types.filter((type) => hasType(typesMap, type));
|
|
145
|
+
if (resolved.length > 0) {
|
|
146
|
+
return resolved;
|
|
147
|
+
}
|
|
148
|
+
logger.warn(`Channel type '${types.join(' | ')}' not found in local typesMap, falling back to unknown`);
|
|
149
|
+
return ['unknown'];
|
|
150
|
+
}
|
|
151
|
+
function hasType(typesMap, type) {
|
|
152
|
+
if (['void', 'never', 'unknown', 'null', 'undefined'].includes(type)) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
typesMap.getTypeMeta(type);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -7,21 +7,35 @@ import { stripVerboseFields, hasVerboseFields, } from '../../../utils/strip-verb
|
|
|
7
7
|
export const pikkuCLI = pikkuSessionlessFunc({
|
|
8
8
|
func: async ({ logger, config, getInspectorState }) => {
|
|
9
9
|
const visitState = await getInspectorState();
|
|
10
|
-
const { cliWiringsFile, cliWiringMetaFile, cliWiringMetaJsonFile, packageMappings, schema, } = config;
|
|
11
|
-
const { cli } = visitState;
|
|
12
|
-
|
|
10
|
+
const { cliWiringsFile, cliWiringMetaFile, cliWiringMetaJsonFile, cliContractsMetaJsonFile, cliContractsMetaFile, packageMappings, schema, } = config;
|
|
11
|
+
const { cli, exportedContracts } = visitState;
|
|
12
|
+
const hasCLIContracts = Object.keys(exportedContracts.cli).length > 0;
|
|
13
|
+
if ((cli.files.size === 0 || Object.keys(cli.meta).length === 0) &&
|
|
14
|
+
!hasCLIContracts) {
|
|
13
15
|
return undefined;
|
|
14
16
|
}
|
|
15
|
-
//
|
|
17
|
+
// The bootstrap imports cliWiringsFile and cliWiringMetaFile whenever this
|
|
18
|
+
// command reports CLI as active (truthy return), so both must always be
|
|
19
|
+
// written once past the guard above — including the contracts-only case
|
|
20
|
+
// where there are no local wireCLI source files (cli.files is empty).
|
|
21
|
+
// Skipping either leaves the bootstrap importing a file that was never
|
|
22
|
+
// generated and the per-unit deploy bundle fails.
|
|
16
23
|
await writeFileInDir(logger, cliWiringsFile, serializeFileImports('wireCLI', cliWiringsFile, cli.files, packageMappings));
|
|
17
|
-
// Write minimal JSON (runtime-only fields)
|
|
18
24
|
const minimalMeta = stripVerboseFields(cli.meta);
|
|
19
25
|
await writeFileInDir(logger, cliWiringMetaJsonFile, JSON.stringify(minimalMeta, null, 2));
|
|
20
|
-
// Write verbose JSON only if it has additional fields
|
|
21
26
|
if (hasVerboseFields(cli.meta)) {
|
|
22
27
|
const verbosePath = cliWiringMetaJsonFile.replace(/\.gen\.json$/, '-verbose.gen.json');
|
|
23
28
|
await writeFileInDir(logger, verbosePath, JSON.stringify(cli.meta, null, 2));
|
|
24
29
|
}
|
|
30
|
+
await writeFileInDir(logger, cliContractsMetaJsonFile, JSON.stringify(exportedContracts.cli, null, 2));
|
|
31
|
+
if (hasCLIContracts) {
|
|
32
|
+
const contractsJsonImportPath = getFileImportRelativePath(cliContractsMetaFile, cliContractsMetaJsonFile, packageMappings);
|
|
33
|
+
const supportsImportAttributes = schema?.supportsImportAttributes ?? false;
|
|
34
|
+
const contractsImportStatement = supportsImportAttributes
|
|
35
|
+
? `import contractsMeta from '${contractsJsonImportPath}' with { type: 'json' }`
|
|
36
|
+
: `import contractsMeta from '${contractsJsonImportPath}'`;
|
|
37
|
+
await writeFileInDir(logger, cliContractsMetaFile, `${contractsImportStatement}\nexport default contractsMeta`);
|
|
38
|
+
}
|
|
25
39
|
const jsonImportPath = getFileImportRelativePath(cliWiringMetaFile, cliWiringMetaJsonFile, packageMappings);
|
|
26
40
|
const supportsImportAttributes = schema?.supportsImportAttributes ?? false;
|
|
27
41
|
const importStatement = supportsImportAttributes
|