@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
|
@@ -8,18 +8,57 @@ import { serializeEmailsModule } from './serialize-emails.js';
|
|
|
8
8
|
function sha256(input) {
|
|
9
9
|
return createHash('sha256').update(input).digest('hex');
|
|
10
10
|
}
|
|
11
|
-
function
|
|
11
|
+
function resolveLocaleString(strings, dottedPath) {
|
|
12
|
+
let current = strings;
|
|
13
|
+
for (const segment of dottedPath.split('.')) {
|
|
14
|
+
if (!current || typeof current !== 'object' || !(segment in current))
|
|
15
|
+
return undefined;
|
|
16
|
+
current = current[segment];
|
|
17
|
+
}
|
|
18
|
+
return typeof current === 'string' ? current : undefined;
|
|
19
|
+
}
|
|
20
|
+
// Variables a template actually references — scoped to the locale keys and
|
|
21
|
+
// partials it uses (transitively), not every string in the shared locale file.
|
|
22
|
+
function collectTemplateVariables(rootSources, locales, partials) {
|
|
12
23
|
const variables = new Set();
|
|
13
|
-
|
|
24
|
+
const visited = new Set();
|
|
25
|
+
const queue = [...rootSources];
|
|
26
|
+
while (queue.length > 0) {
|
|
27
|
+
const source = queue.shift();
|
|
28
|
+
if (!source)
|
|
29
|
+
continue;
|
|
14
30
|
for (const match of source.matchAll(/\{\{\s*([^}]+?)\s*\}\}/g)) {
|
|
15
31
|
const key = String(match[1]).trim();
|
|
16
|
-
if (!key
|
|
32
|
+
if (!key)
|
|
33
|
+
continue;
|
|
34
|
+
if (key.startsWith('>')) {
|
|
35
|
+
const name = key.slice(1).trim();
|
|
36
|
+
if (!visited.has(`partial:${name}`)) {
|
|
37
|
+
visited.add(`partial:${name}`);
|
|
38
|
+
if (partials[name])
|
|
39
|
+
queue.push(partials[name]);
|
|
40
|
+
}
|
|
17
41
|
continue;
|
|
42
|
+
}
|
|
18
43
|
const rootKey = key.split('.')[0];
|
|
19
|
-
if (rootKey === '
|
|
44
|
+
if (rootKey === 'content' ||
|
|
45
|
+
rootKey === 'subject' ||
|
|
46
|
+
rootKey === 'theme' ||
|
|
47
|
+
rootKey === 'locale') {
|
|
20
48
|
continue;
|
|
21
|
-
|
|
49
|
+
}
|
|
50
|
+
if (rootKey === 't') {
|
|
51
|
+
const path = key.split('.').slice(1).join('.');
|
|
52
|
+
if (!path || visited.has(`t:${path}`))
|
|
53
|
+
continue;
|
|
54
|
+
visited.add(`t:${path}`);
|
|
55
|
+
for (const strings of Object.values(locales)) {
|
|
56
|
+
const resolved = resolveLocaleString(strings, path);
|
|
57
|
+
if (resolved)
|
|
58
|
+
queue.push(resolved);
|
|
59
|
+
}
|
|
22
60
|
continue;
|
|
61
|
+
}
|
|
23
62
|
variables.add(rootKey);
|
|
24
63
|
}
|
|
25
64
|
}
|
|
@@ -37,18 +76,6 @@ function stableStringify(value) {
|
|
|
37
76
|
}
|
|
38
77
|
return JSON.stringify(value);
|
|
39
78
|
}
|
|
40
|
-
function collectStringLeaves(value) {
|
|
41
|
-
if (typeof value === 'string') {
|
|
42
|
-
return [value];
|
|
43
|
-
}
|
|
44
|
-
if (Array.isArray(value)) {
|
|
45
|
-
return value.flatMap((item) => collectStringLeaves(item));
|
|
46
|
-
}
|
|
47
|
-
if (value && typeof value === 'object') {
|
|
48
|
-
return Object.values(value).flatMap((nested) => collectStringLeaves(nested));
|
|
49
|
-
}
|
|
50
|
-
return [];
|
|
51
|
-
}
|
|
52
79
|
export const pikkuEmails = pikkuSessionlessFunc({
|
|
53
80
|
func: async ({ logger, config }) => {
|
|
54
81
|
return generateEmailsArtifacts(logger, config);
|
|
@@ -116,8 +143,10 @@ export async function generateEmailsArtifacts(logger, config) {
|
|
|
116
143
|
readFile(join(emailDir, 'templates', `${templateName}.subject.txt`), 'utf8'),
|
|
117
144
|
readFile(join(emailDir, 'templates', `${templateName}.text.txt`), 'utf8').catch(() => ''),
|
|
118
145
|
]);
|
|
119
|
-
|
|
120
|
-
|
|
146
|
+
// layout.html wraps every template at render time (see serialize-emails),
|
|
147
|
+
// so include it as a discovery root or layout-only variables get dropped.
|
|
148
|
+
const layout = partials.layout ?? '';
|
|
149
|
+
const variables = collectTemplateVariables([html, subject, text, layout], locales, partials);
|
|
121
150
|
const hashes = Object.fromEntries(Object.entries(locales).map(([locale, strings]) => {
|
|
122
151
|
const localePayload = stableStringify({
|
|
123
152
|
templateName,
|
|
@@ -4,6 +4,7 @@ import { checkRequiredTypes } from '../../../utils/check-required-types.js';
|
|
|
4
4
|
import { writeFileInDir } from '../../../utils/file-writer.js';
|
|
5
5
|
import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
|
|
6
6
|
import { serializeFunctionTypes } from './serialize-function-types.js';
|
|
7
|
+
import { serializeAddonRefs } from './serialize-addon-refs.js';
|
|
7
8
|
export const pikkuFunctionTypesSplit = pikkuSessionlessFunc({
|
|
8
9
|
func: async ({ logger, config, getInspectorState }, data) => {
|
|
9
10
|
const visitState = await getInspectorState(false, true, data?.bootstrap ?? false);
|
|
@@ -22,7 +23,12 @@ export const pikkuFunctionTypesSplit = pikkuSessionlessFunc({
|
|
|
22
23
|
? `import type { ${pikkuConfigType.type} } from '${getFileImportRelativePath(functionTypesFile, pikkuConfigType.typePath, packageMappings)}'`
|
|
23
24
|
: '// Config type not found, will use fallback';
|
|
24
25
|
const content = serializeFunctionTypes(`import type { ${userSessionType.type} } from '${getFileImportRelativePath(functionTypesFile, userSessionType.typePath, packageMappings)}'`, userSessionType.type, `import type { ${singletonServicesType.type} } from '${getFileImportRelativePath(functionTypesFile, singletonServicesType.typePath, packageMappings)}'`, singletonServicesType.type, `import type { ${wireServicesType.type} } from '${getFileImportRelativePath(functionTypesFile, wireServicesType.typePath, packageMappings)}'`, wireServicesType.type, `import type { TypedPikkuRPC, FlattenedRPCMap } from '${getFileImportRelativePath(functionTypesFile, rpcInternalMapDeclarationFile, packageMappings)}'`, `import type { RequiredSingletonServices, RequiredWireServices } from '${getFileImportRelativePath(functionTypesFile, servicesFile, packageMappings)}'`, configTypeImport, config.addonName, undefined, typeof config.addon === 'object' ? config.addon?.categories : undefined);
|
|
25
|
-
|
|
26
|
+
const addonRefs = serializeAddonRefs({
|
|
27
|
+
addonHttp: visitState.exportedContracts.addonHttp,
|
|
28
|
+
addonChannel: visitState.exportedContracts.addonChannel,
|
|
29
|
+
addonCli: visitState.exportedContracts.addonCli,
|
|
30
|
+
});
|
|
31
|
+
await writeFileInDir(logger, functionTypesFile, `${content}\n${addonRefs}`);
|
|
26
32
|
},
|
|
27
33
|
middleware: [
|
|
28
34
|
logCommandInfoAndTime({
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface AddonContracts {
|
|
2
|
+
addonHttp: Record<string, Record<string, any>>;
|
|
3
|
+
addonChannel: Record<string, Record<string, any>>;
|
|
4
|
+
addonCli: Record<string, Record<string, any>>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Emit the addon contract reference helpers (refHTTP / refChannel / refCLI)
|
|
8
|
+
* mirroring `ref`. Each helper indexes a generated const map whose functions
|
|
9
|
+
* are pre-bound to ref('namespace:fn') RPC proxies, so type-checking and
|
|
10
|
+
* runtime wiring resolve from the same artifact. basePath can be overridden
|
|
11
|
+
* per HTTP reference; otherwise the addon contract's own basePath is preserved.
|
|
12
|
+
*/
|
|
13
|
+
export declare const serializeAddonRefs: (state: AddonContracts) => string;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const refFunc = (pikkuFuncId) => `ref(${JSON.stringify(pikkuFuncId)})`;
|
|
2
|
+
const isHttpRouteConfig = (value) => value &&
|
|
3
|
+
typeof value === 'object' &&
|
|
4
|
+
'method' in value &&
|
|
5
|
+
'route' in value &&
|
|
6
|
+
'func' in value;
|
|
7
|
+
const isHttpRouteGroup = (value) => value &&
|
|
8
|
+
typeof value === 'object' &&
|
|
9
|
+
'routes' in value &&
|
|
10
|
+
!('method' in value);
|
|
11
|
+
const serializeHttpRouteMap = (routes) => {
|
|
12
|
+
const entries = Object.entries(routes).map(([key, value]) => {
|
|
13
|
+
if (isHttpRouteConfig(value)) {
|
|
14
|
+
const parts = [];
|
|
15
|
+
for (const [field, fieldValue] of Object.entries(value)) {
|
|
16
|
+
if (field === 'func') {
|
|
17
|
+
parts.push(`func: ${refFunc(fieldValue.pikkuFuncId)}`);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (fieldValue === null || fieldValue === undefined)
|
|
21
|
+
continue;
|
|
22
|
+
parts.push(`${JSON.stringify(field)}: ${JSON.stringify(fieldValue)}`);
|
|
23
|
+
}
|
|
24
|
+
return `${JSON.stringify(key)}: { ${parts.join(', ')} }`;
|
|
25
|
+
}
|
|
26
|
+
if (isHttpRouteGroup(value)) {
|
|
27
|
+
const parts = [];
|
|
28
|
+
if (value.basePath) {
|
|
29
|
+
parts.push(`basePath: ${JSON.stringify(value.basePath)}`);
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value.tags) && value.tags.length > 0) {
|
|
32
|
+
parts.push(`tags: ${JSON.stringify(value.tags)}`);
|
|
33
|
+
}
|
|
34
|
+
if (value.auth !== undefined && value.auth !== null) {
|
|
35
|
+
parts.push(`auth: ${JSON.stringify(value.auth)}`);
|
|
36
|
+
}
|
|
37
|
+
parts.push(`routes: ${serializeHttpRouteMap(value.routes)}`);
|
|
38
|
+
return `${JSON.stringify(key)}: { ${parts.join(', ')} }`;
|
|
39
|
+
}
|
|
40
|
+
return `${JSON.stringify(key)}: ${serializeHttpRouteMap(value)}`;
|
|
41
|
+
});
|
|
42
|
+
return `{ ${entries.join(', ')} }`;
|
|
43
|
+
};
|
|
44
|
+
const serializeHttpContract = (contract) => {
|
|
45
|
+
const parts = [];
|
|
46
|
+
if (contract.basePath) {
|
|
47
|
+
parts.push(`basePath: ${JSON.stringify(contract.basePath)}`);
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(contract.tags) && contract.tags.length > 0) {
|
|
50
|
+
parts.push(`tags: ${JSON.stringify(contract.tags)}`);
|
|
51
|
+
}
|
|
52
|
+
if (contract.auth !== undefined && contract.auth !== null) {
|
|
53
|
+
parts.push(`auth: ${JSON.stringify(contract.auth)}`);
|
|
54
|
+
}
|
|
55
|
+
parts.push(`routes: ${serializeHttpRouteMap(contract.routes ?? {})}`);
|
|
56
|
+
return `{ ${parts.join(', ')} }`;
|
|
57
|
+
};
|
|
58
|
+
const serializeChannelContract = (contract) => {
|
|
59
|
+
const entries = Object.entries(contract).map(([action, value]) => `${JSON.stringify(action)}: { func: ${refFunc(value.pikkuFuncId)} }`);
|
|
60
|
+
return `{ ${entries.join(', ')} }`;
|
|
61
|
+
};
|
|
62
|
+
const serializeCliContract = (contract) => {
|
|
63
|
+
const entries = Object.entries(contract).map(([command, value]) => {
|
|
64
|
+
const parts = [`func: ${refFunc(value.pikkuFuncId)}`];
|
|
65
|
+
if (Array.isArray(value.positionals) && value.positionals.length > 0) {
|
|
66
|
+
parts.push(`positionals: ${JSON.stringify(value.positionals)}`);
|
|
67
|
+
}
|
|
68
|
+
parts.push(`options: ${JSON.stringify(value.options ?? {})}`);
|
|
69
|
+
return `${JSON.stringify(command)}: { ${parts.join(', ')} }`;
|
|
70
|
+
});
|
|
71
|
+
return `{ ${entries.join(', ')} }`;
|
|
72
|
+
};
|
|
73
|
+
const serializeMap = (namespaces, serializeContract) => {
|
|
74
|
+
const entries = [];
|
|
75
|
+
for (const [namespace, contracts] of Object.entries(namespaces)) {
|
|
76
|
+
for (const [contractName, contract] of Object.entries(contracts)) {
|
|
77
|
+
const key = `${namespace}:${contractName}`;
|
|
78
|
+
entries.push(` ${JSON.stringify(key)}: ${serializeContract(contract)},`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return entries.length > 0 ? `{\n${entries.join('\n')}\n}` : '{}';
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Emit the addon contract reference helpers (refHTTP / refChannel / refCLI)
|
|
85
|
+
* mirroring `ref`. Each helper indexes a generated const map whose functions
|
|
86
|
+
* are pre-bound to ref('namespace:fn') RPC proxies, so type-checking and
|
|
87
|
+
* runtime wiring resolve from the same artifact. basePath can be overridden
|
|
88
|
+
* per HTTP reference; otherwise the addon contract's own basePath is preserved.
|
|
89
|
+
*/
|
|
90
|
+
export const serializeAddonRefs = (state) => {
|
|
91
|
+
const exported = state ?? {
|
|
92
|
+
addonHttp: {},
|
|
93
|
+
addonChannel: {},
|
|
94
|
+
addonCli: {},
|
|
95
|
+
};
|
|
96
|
+
const httpMap = serializeMap(exported.addonHttp ?? {}, serializeHttpContract);
|
|
97
|
+
const channelMap = serializeMap(exported.addonChannel ?? {}, serializeChannelContract);
|
|
98
|
+
const cliMap = serializeMap(exported.addonCli ?? {}, serializeCliContract);
|
|
99
|
+
return `
|
|
100
|
+
/**
|
|
101
|
+
* Addon contract references. Generated from each wired addon's published
|
|
102
|
+
* contract metadata — no addon source is imported. Functions are proxied via
|
|
103
|
+
* ref() (RPC) exactly like ref('namespace:fn').
|
|
104
|
+
*/
|
|
105
|
+
const __addonHttp = ${httpMap} as const
|
|
106
|
+
const __addonChannel = ${channelMap} as const
|
|
107
|
+
const __addonCli = ${cliMap} as const
|
|
108
|
+
|
|
109
|
+
export const refHTTP = <Name extends keyof typeof __addonHttp>(
|
|
110
|
+
name: Name,
|
|
111
|
+
options?: { basePath?: string }
|
|
112
|
+
): (typeof __addonHttp)[Name] => {
|
|
113
|
+
const contract = __addonHttp[name] as any
|
|
114
|
+
return (
|
|
115
|
+
options?.basePath !== undefined
|
|
116
|
+
? { ...contract, basePath: options.basePath }
|
|
117
|
+
: contract
|
|
118
|
+
) as (typeof __addonHttp)[Name]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const refChannel = <Name extends keyof typeof __addonChannel>(
|
|
122
|
+
name: Name
|
|
123
|
+
): (typeof __addonChannel)[Name] => __addonChannel[name]
|
|
124
|
+
|
|
125
|
+
export const refCLI = <Name extends keyof typeof __addonCli>(
|
|
126
|
+
name: Name
|
|
127
|
+
): (typeof __addonCli)[Name] => __addonCli[name]
|
|
128
|
+
`;
|
|
129
|
+
};
|
|
@@ -7,21 +7,36 @@ import { stripVerboseFields, hasVerboseFields, } from '../../../utils/strip-verb
|
|
|
7
7
|
export const pikkuCommandHTTP = pikkuSessionlessFunc({
|
|
8
8
|
func: async ({ logger, config, getInspectorState }) => {
|
|
9
9
|
const visitState = await getInspectorState();
|
|
10
|
-
const { httpWiringsFile, httpWiringMetaFile, httpWiringMetaJsonFile, packageMappings, schema, } = config;
|
|
11
|
-
const { http } = visitState;
|
|
12
|
-
|
|
10
|
+
const { httpWiringsFile, httpWiringMetaFile, httpWiringMetaJsonFile, httpContractsMetaJsonFile, httpContractsMetaFile, packageMappings, schema, } = config;
|
|
11
|
+
const { http, exportedContracts } = visitState;
|
|
12
|
+
const hasHTTPContracts = Object.keys(exportedContracts.http).length > 0;
|
|
13
|
+
if ((http.files.size === 0 || Object.keys(http.meta).length === 0) &&
|
|
14
|
+
!hasHTTPContracts) {
|
|
13
15
|
return undefined;
|
|
14
16
|
}
|
|
17
|
+
// The bootstrap imports httpWiringsFile and httpWiringMetaFile whenever
|
|
18
|
+
// this command reports HTTP as active (truthy return), so both must
|
|
19
|
+
// always be written once past the guard above — including the
|
|
20
|
+
// contracts-only or synthetic-route case where there are no local
|
|
21
|
+
// wireHTTP source files (http.files is empty). Skipping either leaves the
|
|
22
|
+
// bootstrap importing a file that was never generated and the per-unit
|
|
23
|
+
// deploy bundle fails to resolve it.
|
|
15
24
|
await writeFileInDir(logger, httpWiringsFile, serializeFileImports('wireHTTP', httpWiringsFile, http.files, packageMappings));
|
|
16
|
-
// Write minimal JSON (runtime-only fields)
|
|
17
25
|
const minimalMeta = stripVerboseFields(http.meta);
|
|
18
26
|
await writeFileInDir(logger, httpWiringMetaJsonFile, JSON.stringify(minimalMeta, null, 2));
|
|
19
|
-
// Write verbose JSON only if it has additional fields
|
|
20
27
|
if (hasVerboseFields(http.meta)) {
|
|
21
28
|
const verbosePath = httpWiringMetaJsonFile.replace(/\.gen\.json$/, '-verbose.gen.json');
|
|
22
29
|
await writeFileInDir(logger, verbosePath, JSON.stringify(http.meta, null, 2));
|
|
23
30
|
}
|
|
24
|
-
|
|
31
|
+
await writeFileInDir(logger, httpContractsMetaJsonFile, JSON.stringify(exportedContracts.http, null, 2));
|
|
32
|
+
if (hasHTTPContracts) {
|
|
33
|
+
const contractsJsonImportPath = getFileImportRelativePath(httpContractsMetaFile, httpContractsMetaJsonFile, packageMappings);
|
|
34
|
+
const supportsImportAttributes = schema?.supportsImportAttributes ?? false;
|
|
35
|
+
const contractsImportStatement = supportsImportAttributes
|
|
36
|
+
? `import contractsMeta from '${contractsJsonImportPath}' with { type: 'json' }`
|
|
37
|
+
: `import contractsMeta from '${contractsJsonImportPath}'`;
|
|
38
|
+
await writeFileInDir(logger, httpContractsMetaFile, `${contractsImportStatement}\nexport default contractsMeta`);
|
|
39
|
+
}
|
|
25
40
|
const jsonImportPath = getFileImportRelativePath(httpWiringMetaFile, httpWiringMetaJsonFile, packageMappings);
|
|
26
41
|
const supportsImportAttributes = schema?.supportsImportAttributes ?? false;
|
|
27
42
|
const importStatement = supportsImportAttributes
|
|
@@ -58,6 +58,18 @@ function generateHTTPWirings(routesMeta, resolvedIOTypes, requiredTypes) {
|
|
|
58
58
|
}
|
|
59
59
|
const resolved = resolvedIOTypes[pikkuFuncId];
|
|
60
60
|
if (!resolved) {
|
|
61
|
+
if (meta.packageName) {
|
|
62
|
+
// Addon functions aren't in the consumer's local resolvedIOTypes, but
|
|
63
|
+
// their real types are reachable through the generated FlattenedRPCMap
|
|
64
|
+
// (the addon's RPC map is merged in under its namespace). Adding these
|
|
65
|
+
// to requiredTypes triggers the FlattenedRPCMap import below.
|
|
66
|
+
const inputType = `FlattenedRPCMap['${pikkuFuncId}']['input']`;
|
|
67
|
+
const outputType = `FlattenedRPCMap['${pikkuFuncId}']['output']`;
|
|
68
|
+
requiredTypes.add(inputType);
|
|
69
|
+
requiredTypes.add(outputType);
|
|
70
|
+
routesObj[route][method] = { inputType, outputType };
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
61
73
|
throw new Error(`Function ${pikkuFuncId} not found in resolvedIOTypes. Please check your configuration.`);
|
|
62
74
|
}
|
|
63
75
|
requiredTypes.add(resolved.inputType);
|
|
@@ -189,6 +189,13 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
|
|
|
189
189
|
allImports.push(config.triggersWiringMetaFile, config.triggerSourcesMetaFile, config.triggersWiringFile);
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
+
if (config.addon) {
|
|
193
|
+
await Promise.all([
|
|
194
|
+
workflow.do('HTTP', 'pikkuCommandHTTP', null),
|
|
195
|
+
workflow.do('Channels', 'pikkuCommandChannels', null),
|
|
196
|
+
workflow.do('CLI', 'pikkuCLI', null),
|
|
197
|
+
]);
|
|
198
|
+
}
|
|
192
199
|
const hasFunctionRegistrations = await workflow.do('Functions', 'pikkuFunctions', null);
|
|
193
200
|
allImports.push(config.functionsMetaFile);
|
|
194
201
|
if (hasFunctionRegistrations) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Logger } from '@pikku/core/services';
|
|
2
2
|
import { LogLevel } from '@pikku/core/services';
|
|
3
3
|
import type { PikkuChannel } from '@pikku/core/channel';
|
|
4
|
-
import type { ErrorCode } from '@pikku/inspector';
|
|
4
|
+
import type { ErrorCode, CodedDiagnostic, DiagnosticSeverity } from '@pikku/inspector';
|
|
5
5
|
/**
|
|
6
6
|
* Log message structure sent through the channel
|
|
7
7
|
*/
|
|
@@ -29,6 +29,9 @@ export declare class CLILoggerForwarder implements Logger {
|
|
|
29
29
|
warn(messageOrObj: string | Record<string, any>, ..._meta: any[]): void;
|
|
30
30
|
debug(message: string, ..._meta: any[]): void;
|
|
31
31
|
trace(message: string, ..._meta: any[]): void;
|
|
32
|
+
diagnostic({ severity, code, message }: CodedDiagnostic): void;
|
|
32
33
|
critical(code: ErrorCode, message: string): void;
|
|
33
34
|
hasCriticalErrors(): boolean;
|
|
35
|
+
hasBlockingDiagnostics(): boolean;
|
|
36
|
+
blockingSeverities(): DiagnosticSeverity[];
|
|
34
37
|
}
|
|
@@ -59,13 +59,31 @@ export class CLILoggerForwarder {
|
|
|
59
59
|
trace(message, ..._meta) {
|
|
60
60
|
this.log('trace', LogLevel.trace, message);
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
diagnostic({ severity, code, message }) {
|
|
63
|
+
// Delegate tracking to the underlying CLILogger so the build gate still
|
|
64
|
+
// sees diagnostics emitted through the forwarder, then forward to the
|
|
65
|
+
// channel for display.
|
|
66
|
+
;
|
|
67
|
+
this.logger.diagnostic?.({ severity, code, message });
|
|
63
68
|
const url = `https://pikku.dev/docs/pikku-cli/errors/${code.toLowerCase()}`;
|
|
64
69
|
const formattedMessage = `[${code}] ${message}\n → ${url}`;
|
|
65
|
-
|
|
70
|
+
if (severity === 'warn')
|
|
71
|
+
this.warn(formattedMessage);
|
|
72
|
+
else
|
|
73
|
+
this.error(formattedMessage);
|
|
74
|
+
}
|
|
75
|
+
critical(code, message) {
|
|
76
|
+
this.diagnostic({ severity: 'critical', code, message });
|
|
66
77
|
}
|
|
67
78
|
hasCriticalErrors() {
|
|
68
79
|
// The underlying logger (CLILogger) tracks critical errors
|
|
69
80
|
return this.logger.hasCriticalErrors?.() ?? false;
|
|
70
81
|
}
|
|
82
|
+
hasBlockingDiagnostics() {
|
|
83
|
+
// The underlying logger (CLILogger) owns failOn + diagnostic tracking
|
|
84
|
+
return this.logger.hasBlockingDiagnostics?.() ?? false;
|
|
85
|
+
}
|
|
86
|
+
blockingSeverities() {
|
|
87
|
+
return this.logger.blockingSeverities?.() ?? [];
|
|
88
|
+
}
|
|
71
89
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { Logger } from '@pikku/core/services';
|
|
2
2
|
import { LogLevel } from '@pikku/core/services';
|
|
3
|
-
import type { ErrorCode } from '@pikku/inspector';
|
|
3
|
+
import type { ErrorCode, DiagnosticSeverity, CodedDiagnostic } from '@pikku/inspector';
|
|
4
4
|
export type CLIOutputMode = 'text' | 'json';
|
|
5
5
|
export declare class CLILogger implements Logger {
|
|
6
6
|
private silent;
|
|
7
7
|
private level;
|
|
8
|
-
private
|
|
8
|
+
private diagnostics;
|
|
9
|
+
private failOn;
|
|
9
10
|
private outputMode;
|
|
10
11
|
private jsonFlushHookRegistered;
|
|
11
12
|
constructor({ logLogo, silent, }: {
|
|
@@ -42,7 +43,20 @@ export declare class CLILogger implements Logger {
|
|
|
42
43
|
type?: string;
|
|
43
44
|
data?: Record<string, unknown>;
|
|
44
45
|
}): void;
|
|
46
|
+
diagnostic({ severity, code, message }: CodedDiagnostic): void;
|
|
47
|
+
/** Sugar for `diagnostic({ severity: 'critical', code, message })`. */
|
|
45
48
|
critical(code: ErrorCode, message: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Configure which severities fail the build. Critical is always included.
|
|
51
|
+
*/
|
|
52
|
+
setFailOn(severities: Iterable<DiagnosticSeverity>): void;
|
|
46
53
|
hasCriticalErrors(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* True if any tracked diagnostic matches a severity configured to fail the
|
|
56
|
+
* build (default: critical only).
|
|
57
|
+
*/
|
|
58
|
+
hasBlockingDiagnostics(): boolean;
|
|
59
|
+
/** Distinct severities among tracked diagnostics that would fail the build. */
|
|
60
|
+
blockingSeverities(): DiagnosticSeverity[];
|
|
47
61
|
logLogo(): void;
|
|
48
62
|
}
|
|
@@ -9,7 +9,12 @@ const ANSI_ESCAPE_REGEX = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
|
|
9
9
|
export class CLILogger {
|
|
10
10
|
silent;
|
|
11
11
|
level = LogLevel.info; // default to info level
|
|
12
|
-
|
|
12
|
+
diagnostics = [];
|
|
13
|
+
// Severities that should fail the build. Critical always blocks; error/warn
|
|
14
|
+
// are opt-in via --fail-on-error / --fail-on-warn.
|
|
15
|
+
failOn = new Set([
|
|
16
|
+
'critical',
|
|
17
|
+
]);
|
|
13
18
|
outputMode = 'text';
|
|
14
19
|
jsonFlushHookRegistered = false;
|
|
15
20
|
constructor({ logLogo, silent = false, }) {
|
|
@@ -138,14 +143,37 @@ export class CLILogger {
|
|
|
138
143
|
const data = typeof message === 'string' ? undefined : message.data;
|
|
139
144
|
this.emit('debug', msg, type, undefined, data);
|
|
140
145
|
}
|
|
141
|
-
|
|
146
|
+
diagnostic({ severity, code, message }) {
|
|
142
147
|
const url = `${BASE_ERROR_URL}/${code.toLowerCase()}`;
|
|
143
148
|
const formattedMessage = `[${code}] ${message}\n → ${url}`;
|
|
144
|
-
this.
|
|
145
|
-
|
|
149
|
+
this.diagnostics.push({ severity, code, message });
|
|
150
|
+
// critical → bold red, error → red, warn → yellow. Always printed so the
|
|
151
|
+
// issue surfaces even when it doesn't fail the build.
|
|
152
|
+
this.emit(severity, formattedMessage, undefined, code);
|
|
153
|
+
}
|
|
154
|
+
/** Sugar for `diagnostic({ severity: 'critical', code, message })`. */
|
|
155
|
+
critical(code, message) {
|
|
156
|
+
this.diagnostic({ severity: 'critical', code, message });
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Configure which severities fail the build. Critical is always included.
|
|
160
|
+
*/
|
|
161
|
+
setFailOn(severities) {
|
|
162
|
+
this.failOn = new Set(['critical', ...severities]);
|
|
146
163
|
}
|
|
147
164
|
hasCriticalErrors() {
|
|
148
|
-
return this.
|
|
165
|
+
return this.diagnostics.some((d) => d.severity === 'critical');
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* True if any tracked diagnostic matches a severity configured to fail the
|
|
169
|
+
* build (default: critical only).
|
|
170
|
+
*/
|
|
171
|
+
hasBlockingDiagnostics() {
|
|
172
|
+
return this.diagnostics.some((d) => this.failOn.has(d.severity));
|
|
173
|
+
}
|
|
174
|
+
/** Distinct severities among tracked diagnostics that would fail the build. */
|
|
175
|
+
blockingSeverities() {
|
|
176
|
+
return [...this.failOn].filter((s) => this.diagnostics.some((d) => d.severity === s));
|
|
149
177
|
}
|
|
150
178
|
logLogo() {
|
|
151
179
|
if (this.silent || this.outputMode === 'json')
|
package/dist/src/services.js
CHANGED
|
@@ -79,6 +79,13 @@ export const createConfig = async (_variablesService, data) => {
|
|
|
79
79
|
}
|
|
80
80
|
logger.setLevel(logLevel);
|
|
81
81
|
logger.setSilent(isSilent);
|
|
82
|
+
// Build gate: critical always fails; --fail-on-warn implies --fail-on-error.
|
|
83
|
+
const extraFailOn = [];
|
|
84
|
+
if (data.failOnWarn)
|
|
85
|
+
extraFailOn.push('warn', 'error');
|
|
86
|
+
else if (data.failOnError)
|
|
87
|
+
extraFailOn.push('error');
|
|
88
|
+
logger.setFailOn(extraFailOn);
|
|
82
89
|
// Display logo unless in silent mode
|
|
83
90
|
if (!isSilent && outputMode !== 'json') {
|
|
84
91
|
logger.logLogo();
|
|
@@ -137,6 +137,12 @@ const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields
|
|
|
137
137
|
if (!result.httpWiringMetaJsonFile) {
|
|
138
138
|
result.httpWiringMetaJsonFile = join(httpDir, 'pikku-http-wirings-meta.gen.json');
|
|
139
139
|
}
|
|
140
|
+
if (!result.httpContractsMetaJsonFile) {
|
|
141
|
+
result.httpContractsMetaJsonFile = join(httpDir, 'pikku-http-contracts-meta.gen.json');
|
|
142
|
+
}
|
|
143
|
+
if (!result.httpContractsMetaFile) {
|
|
144
|
+
result.httpContractsMetaFile = join(httpDir, 'pikku-http-contracts-meta.gen.ts');
|
|
145
|
+
}
|
|
140
146
|
if (!result.httpMapDeclarationFile) {
|
|
141
147
|
result.httpMapDeclarationFile = join(httpDir, 'pikku-http-wirings-map.gen.d.ts');
|
|
142
148
|
}
|
|
@@ -158,6 +164,12 @@ const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields
|
|
|
158
164
|
if (!result.channelsWiringMetaJsonFile) {
|
|
159
165
|
result.channelsWiringMetaJsonFile = join(channelDir, 'pikku-channels-meta.gen.json');
|
|
160
166
|
}
|
|
167
|
+
if (!result.channelContractsMetaJsonFile) {
|
|
168
|
+
result.channelContractsMetaJsonFile = join(channelDir, 'pikku-channel-contracts-meta.gen.json');
|
|
169
|
+
}
|
|
170
|
+
if (!result.channelContractsMetaFile) {
|
|
171
|
+
result.channelContractsMetaFile = join(channelDir, 'pikku-channel-contracts-meta.gen.ts');
|
|
172
|
+
}
|
|
161
173
|
if (!result.channelsMapDeclarationFile) {
|
|
162
174
|
result.channelsMapDeclarationFile = join(channelDir, 'pikku-channels-map.gen.d.ts');
|
|
163
175
|
}
|
|
@@ -348,6 +360,12 @@ const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields
|
|
|
348
360
|
if (!result.cliWiringMetaJsonFile) {
|
|
349
361
|
result.cliWiringMetaJsonFile = join(cliDir, 'pikku-cli-wirings-meta.gen.json');
|
|
350
362
|
}
|
|
363
|
+
if (!result.cliContractsMetaJsonFile) {
|
|
364
|
+
result.cliContractsMetaJsonFile = join(cliDir, 'pikku-cli-contracts-meta.gen.json');
|
|
365
|
+
}
|
|
366
|
+
if (!result.cliContractsMetaFile) {
|
|
367
|
+
result.cliContractsMetaFile = join(cliDir, 'pikku-cli-contracts-meta.gen.ts');
|
|
368
|
+
}
|
|
351
369
|
if (!result.cliBootstrapFile) {
|
|
352
370
|
result.cliBootstrapFile = join(cliDir, 'pikku-cli-bootstrap.gen.ts');
|
|
353
371
|
}
|