@pikku/cli 0.12.91 → 0.12.92
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/LICENSE +106 -0
- package/console-app/assets/{index-CpkvmAGb.js → index-DDpIMCpy.js} +149 -149
- package/console-app/index.html +1 -1
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +6 -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 +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +15 -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 +21 -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 +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +35 -40
- package/dist/.pikku/function/pikku-functions.gen.js +1 -3
- 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/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-scenarios.gen.d.ts +2 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +2 -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.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 +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/schemas/register.gen.d.ts +4 -0
- package/dist/.pikku/scenarios/schemas/register.gen.js +4 -0
- 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 +5 -3
- package/dist/.pikku/schemas/schemas/FabricSecretsListOutput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/FabricSecretsRotateInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricSecretsRotateOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricSecretsSetOutput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.js +1 -1
- package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +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 +2 -8
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +1 -1
- 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 +6 -0
- package/dist/src/deploy/analyzer/analyzer.js +22 -5
- package/dist/src/deploy/build-pipeline.js +5 -1
- package/dist/src/fabric/fabric-commands.d.ts +37 -9
- package/dist/src/fabric/fabric-commands.js +12 -0
- package/dist/src/fabric/functions/domains-add.function.d.ts +4 -4
- package/dist/src/fabric/functions/secrets-list.function.d.ts +16 -4
- package/dist/src/fabric/functions/secrets-list.function.js +12 -10
- package/dist/src/fabric/functions/secrets-rotate.function.d.ts +24 -0
- package/dist/src/fabric/functions/secrets-rotate.function.js +36 -0
- package/dist/src/fabric/functions/secrets-set.function.d.ts +8 -4
- package/dist/src/fabric/functions/secrets-set.function.js +18 -7
- package/dist/src/fabric/lib/http.d.ts +7 -1
- package/dist/src/fabric/lib/sealed-box.d.ts +16 -0
- package/dist/src/fabric/lib/sealed-box.js +72 -0
- package/dist/src/functions/commands/dev.js +9 -0
- package/dist/src/functions/commands/pikku-command-bootstrap.js +1 -0
- package/dist/src/functions/commands/scenario-browser.d.ts +17 -2
- package/dist/src/functions/commands/scenario-browser.js +16 -8
- package/dist/src/functions/commands/scenario-environment.d.ts +32 -0
- package/dist/src/functions/commands/scenario-environment.js +61 -0
- package/dist/src/functions/commands/scenario.d.ts +6 -0
- package/dist/src/functions/commands/scenario.js +14 -9
- package/dist/src/functions/commands/skills.js +27 -61
- package/dist/src/functions/db/db-codegen.d.ts +6 -0
- package/dist/src/functions/db/db-codegen.js +9 -0
- package/dist/src/functions/db/db-migrator.js +14 -0
- package/dist/src/functions/db/local-db.d.ts +8 -0
- package/dist/src/functions/db/local-db.js +24 -2
- package/dist/src/functions/db/migration-identifiers.d.ts +58 -0
- package/dist/src/functions/db/migration-identifiers.js +262 -0
- package/dist/src/functions/db/migration-provenance.d.ts +35 -0
- package/dist/src/functions/db/migration-provenance.js +80 -0
- package/dist/src/functions/db/schema-sql.d.ts +43 -0
- package/dist/src/functions/db/schema-sql.js +135 -0
- package/dist/src/functions/db/sqlite/sqlite-kysely.js +22 -2
- package/dist/src/functions/wirings/functions/schemas.js +13 -1
- package/dist/src/functions/wirings/rpc/pikku-command-rpc.js +6 -1
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.d.ts +10 -0
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.js +102 -0
- package/dist/src/functions/wirings/scenarios/scenario-partition.d.ts +13 -0
- package/dist/src/functions/wirings/scenarios/scenario-partition.js +15 -2
- package/dist/src/functions/wirings/scenarios/scenario-schema-partition.d.ts +33 -0
- package/dist/src/functions/wirings/scenarios/scenario-schema-partition.js +49 -0
- package/dist/src/functions/workflows/all.workflow.js +2 -7
- package/dist/src/utils/file-writer.js +14 -2
- package/dist/src/utils/pikku-cli-config.d.ts +14 -0
- package/dist/src/utils/pikku-cli-config.js +34 -0
- package/dist/src/utils/remove-legacy-scaffold-file.d.ts +14 -0
- package/dist/src/utils/remove-legacy-scaffold-file.js +23 -0
- package/dist/src/utils/serialize-schemas.d.ts +3 -1
- package/dist/src/utils/serialize-schemas.js +39 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/dist/.pikku/schemas/schemas/PikkuScenarioFunctionsOutput.schema.json +0 -1
- package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.d.ts +0 -1
- package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.js +0 -31
- package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.d.ts +0 -10
- package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.js +0 -104
- package/skills/pikku-addon/SKILL.md +0 -243
- package/skills/pikku-addon/references/addon-package-manifest.md +0 -63
- package/skills/pikku-ai-agent/SKILL.md +0 -231
- package/skills/pikku-ai-vercel/SKILL.md +0 -82
- package/skills/pikku-ai-voice/SKILL.md +0 -88
- package/skills/pikku-audit/SKILL.md +0 -175
- package/skills/pikku-aws/SKILL.md +0 -111
- package/skills/pikku-backblaze/SKILL.md +0 -71
- package/skills/pikku-better-auth/SKILL.md +0 -298
- package/skills/pikku-cli/SKILL.md +0 -198
- package/skills/pikku-cli/references/complete-example.md +0 -82
- package/skills/pikku-concepts/SKILL.md +0 -250
- package/skills/pikku-concepts/references/concept-mapping.md +0 -556
- package/skills/pikku-concepts/references/packages.md +0 -29
- package/skills/pikku-config/SKILL.md +0 -212
- package/skills/pikku-cron/SKILL.md +0 -214
- package/skills/pikku-deploy-azure/SKILL.md +0 -71
- package/skills/pikku-deploy-cloudflare/SKILL.md +0 -82
- package/skills/pikku-deploy-express/SKILL.md +0 -86
- package/skills/pikku-deploy-fastify/SKILL.md +0 -73
- package/skills/pikku-deploy-lambda/SKILL.md +0 -110
- package/skills/pikku-deploy-nextjs/SKILL.md +0 -78
- package/skills/pikku-deploy-uws/SKILL.md +0 -88
- package/skills/pikku-deps/SKILL.md +0 -98
- package/skills/pikku-emails/SKILL.md +0 -163
- package/skills/pikku-fabric/SKILL.md +0 -318
- package/skills/pikku-fabric-debug/SKILL.md +0 -112
- package/skills/pikku-feature/SKILL.md +0 -258
- package/skills/pikku-gateway-slack/SKILL.md +0 -115
- package/skills/pikku-http/SKILL.md +0 -220
- package/skills/pikku-http/references/http-options.md +0 -55
- package/skills/pikku-i18n/SKILL.md +0 -137
- package/skills/pikku-info/SKILL.md +0 -100
- package/skills/pikku-jose/SKILL.md +0 -105
- package/skills/pikku-kysely/SKILL.md +0 -219
- package/skills/pikku-machine-auth/SKILL.md +0 -183
- package/skills/pikku-mcp/SKILL.md +0 -241
- package/skills/pikku-middleware/SKILL.md +0 -231
- package/skills/pikku-middleware/references/middleware-patterns.md +0 -61
- package/skills/pikku-mongodb/SKILL.md +0 -105
- package/skills/pikku-n8n-import/SKILL.md +0 -109
- package/skills/pikku-n8n-import/SPEC.md +0 -84
- package/skills/pikku-n8n-import/references/addon-mapping.md +0 -121
- package/skills/pikku-n8n-import/references/code-translation.md +0 -121
- package/skills/pikku-n8n-import/references/loops-and-control.md +0 -87
- package/skills/pikku-paraglide/SKILL.md +0 -117
- package/skills/pikku-permissions/SKILL.md +0 -192
- package/skills/pikku-pino/SKILL.md +0 -79
- package/skills/pikku-product-second-opinion/README.md +0 -43
- package/skills/pikku-product-second-opinion/SKILL.md +0 -158
- package/skills/pikku-product-second-opinion/example/sample-report.md +0 -90
- package/skills/pikku-product-second-opinion/references/report-template.md +0 -73
- package/skills/pikku-queue/SKILL.md +0 -240
- package/skills/pikku-react/SKILL.md +0 -212
- package/skills/pikku-react-query/SKILL.md +0 -242
- package/skills/pikku-realtime/SKILL.md +0 -236
- package/skills/pikku-realtime/references/other-routes.md +0 -23
- package/skills/pikku-redis/SKILL.md +0 -90
- package/skills/pikku-rpc/SKILL.md +0 -171
- package/skills/pikku-rtl/SKILL.md +0 -219
- package/skills/pikku-scenario/SKILL.md +0 -382
- package/skills/pikku-schedule/SKILL.md +0 -57
- package/skills/pikku-schema-ajv/SKILL.md +0 -62
- package/skills/pikku-schema-cfworker/SKILL.md +0 -63
- package/skills/pikku-security/SKILL.md +0 -108
- package/skills/pikku-services/SKILL.md +0 -248
- package/skills/pikku-services/references/audit-wire-service.md +0 -34
- package/skills/pikku-software-archaeology/README.md +0 -70
- package/skills/pikku-software-archaeology/SKILL.md +0 -186
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +0 -625
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +0 -49
- package/skills/pikku-software-archaeology/scripts/validate.mjs +0 -173
- package/skills/pikku-tag-middleware/SKILL.md +0 -13
- package/skills/pikku-template-clone/SKILL.md +0 -40
- package/skills/pikku-trigger/SKILL.md +0 -181
- package/skills/pikku-versioning/SKILL.md +0 -173
- package/skills/pikku-websocket/SKILL.md +0 -243
- package/skills/pikku-workflow/SKILL.md +0 -172
- package/skills/pikku-workflow/references/workflow-reference.md +0 -63
- package/skills/pikku-workflows-client/SKILL.md +0 -150
- package/skills/pikku-ws/SKILL.md +0 -47
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving `pikku scenario run <environment>` to the URLs the run targets.
|
|
3
|
+
*
|
|
4
|
+
* The configured environment is the base; `--api-url`/`--app-url` override it
|
|
5
|
+
* for one invocation, which is what makes a suite runnable against a target
|
|
6
|
+
* that only exists at run time (a freshly provisioned sandbox) without writing
|
|
7
|
+
* a pikku.config.json per deployment.
|
|
8
|
+
*/
|
|
9
|
+
const LOCAL_HOSTNAMES = new Set([
|
|
10
|
+
'localhost',
|
|
11
|
+
'127.0.0.1',
|
|
12
|
+
'0.0.0.0',
|
|
13
|
+
'::1',
|
|
14
|
+
'[::1]',
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* Absolute URLs only: the value is concatenated with paths (`${apiUrl}/rpc`)
|
|
18
|
+
* and parsed by the spawn path, so a relative one fails far from its cause.
|
|
19
|
+
*/
|
|
20
|
+
const assertAbsoluteUrl = (flag, value) => {
|
|
21
|
+
let url;
|
|
22
|
+
try {
|
|
23
|
+
url = new URL(value);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new Error(`${flag} '${value}' is not a valid absolute URL — pass one including the scheme, e.g. ${flag} https://sandbox-a1b2.example.com/api`);
|
|
27
|
+
}
|
|
28
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
29
|
+
throw new Error(`${flag} '${value}' must be an http(s) URL, not '${url.protocol.replace(':', '')}'.`);
|
|
30
|
+
}
|
|
31
|
+
return url;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Applied once, so actors, step env, the browser driver and the spawned server
|
|
35
|
+
* all see the same target. The environment is still looked up by name: the
|
|
36
|
+
* flags override fields on a configured environment, they do not invent one.
|
|
37
|
+
*/
|
|
38
|
+
export const resolveScenarioEnvironment = ({ environment, environments, apiUrl, appUrl, spawn = false, }) => {
|
|
39
|
+
const configured = environments[environment];
|
|
40
|
+
if (!configured) {
|
|
41
|
+
const known = Object.keys(environments);
|
|
42
|
+
throw new Error(`Unknown scenario environment '${environment}'. ` +
|
|
43
|
+
(known.length
|
|
44
|
+
? `Configured environments: ${known.join(', ')}`
|
|
45
|
+
: `Add scenarios.environments to pikku.config.json, e.g. { "${environment}": { "apiUrl": "https://app.example.com/api" } }`));
|
|
46
|
+
}
|
|
47
|
+
const resolved = { ...configured };
|
|
48
|
+
if (apiUrl !== undefined) {
|
|
49
|
+
const url = assertAbsoluteUrl('--api-url', apiUrl);
|
|
50
|
+
if (spawn && !LOCAL_HOSTNAMES.has(url.hostname)) {
|
|
51
|
+
throw new Error(`--spawn starts a server on this machine, but --api-url points at '${url.hostname}'. ` +
|
|
52
|
+
`Drop --spawn to run against an already-serving target, or point --api-url at localhost.`);
|
|
53
|
+
}
|
|
54
|
+
resolved.apiUrl = apiUrl;
|
|
55
|
+
}
|
|
56
|
+
if (appUrl !== undefined) {
|
|
57
|
+
assertAbsoluteUrl('--app-url', appUrl);
|
|
58
|
+
resolved.appUrl = appUrl;
|
|
59
|
+
}
|
|
60
|
+
return resolved;
|
|
61
|
+
};
|
|
@@ -10,6 +10,8 @@ export declare const scenarioRun: import("#pikku").PikkuFunctionConfig<{
|
|
|
10
10
|
spawn?: boolean;
|
|
11
11
|
keepAlive?: boolean;
|
|
12
12
|
trace?: boolean;
|
|
13
|
+
apiUrl?: string;
|
|
14
|
+
appUrl?: string;
|
|
13
15
|
}, void, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<{
|
|
14
16
|
environment: string;
|
|
15
17
|
flows?: string;
|
|
@@ -21,6 +23,8 @@ export declare const scenarioRun: import("#pikku").PikkuFunctionConfig<{
|
|
|
21
23
|
spawn?: boolean;
|
|
22
24
|
keepAlive?: boolean;
|
|
23
25
|
trace?: boolean;
|
|
26
|
+
apiUrl?: string;
|
|
27
|
+
appUrl?: string;
|
|
24
28
|
}, void, "rpc" | "session", import("#pikku").WiredServices> | import("#pikku").PikkuFunction<{
|
|
25
29
|
environment: string;
|
|
26
30
|
flows?: string;
|
|
@@ -32,4 +36,6 @@ export declare const scenarioRun: import("#pikku").PikkuFunctionConfig<{
|
|
|
32
36
|
spawn?: boolean;
|
|
33
37
|
keepAlive?: boolean;
|
|
34
38
|
trace?: boolean;
|
|
39
|
+
apiUrl?: string;
|
|
40
|
+
appUrl?: string;
|
|
35
41
|
}, void, "rpc" | "session", import("#pikku").WiredServices>, undefined, undefined>;
|
|
@@ -12,6 +12,7 @@ import { resolveScenarioBrowserProvider, scenarioBrowserLifecycle, } from './sce
|
|
|
12
12
|
import { resolveScenarioActors } from '../../utils/resolve-scenario-actors.js';
|
|
13
13
|
import { spawnDevServer } from '../../server/spawn-dev-server.js';
|
|
14
14
|
import { buildScenarioPlan } from './scenario-plan.js';
|
|
15
|
+
import { resolveScenarioEnvironment } from './scenario-environment.js';
|
|
15
16
|
const isScenario = (wf) => wf?.scenario === true;
|
|
16
17
|
const listScenarios = (state) => Object.entries(state.workflows?.meta ?? {})
|
|
17
18
|
.filter(([, wf]) => isScenario(wf))
|
|
@@ -83,16 +84,20 @@ export const scenarioList = pikkuSessionlessFunc({
|
|
|
83
84
|
},
|
|
84
85
|
});
|
|
85
86
|
export const scenarioRun = pikkuSessionlessFunc({
|
|
86
|
-
func: async ({ logger, config, getInspectorState, variables }, { environment, flows, features, tags, excludeTags, coverage, browser = true, spawn = false, keepAlive = false, trace = false, }) => {
|
|
87
|
+
func: async ({ logger, config, getInspectorState, variables }, { environment, flows, features, tags, excludeTags, coverage, browser = true, spawn = false, keepAlive = false, trace = false, apiUrl, appUrl, }) => {
|
|
87
88
|
const state = await getInspectorState(true, false, false, true);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
89
|
+
// Resolved once, so actors, step env, the browser driver and any spawned
|
|
90
|
+
// server all target the same place — including when the target only exists
|
|
91
|
+
// at run time and arrives through --api-url/--app-url.
|
|
92
|
+
const env = resolveScenarioEnvironment({
|
|
93
|
+
environment,
|
|
94
|
+
environments: config.scenarios?.environments ?? {},
|
|
95
|
+
apiUrl,
|
|
96
|
+
appUrl,
|
|
97
|
+
spawn,
|
|
98
|
+
});
|
|
99
|
+
if (apiUrl || appUrl) {
|
|
100
|
+
logger.info(`Overriding '${environment}': apiUrl ${env.apiUrl}${env.appUrl ? `, appUrl ${env.appUrl}` : ''}`);
|
|
96
101
|
}
|
|
97
102
|
if (spawn) {
|
|
98
103
|
const { hostname, port } = new URL(env.apiUrl);
|
|
@@ -1,49 +1,25 @@
|
|
|
1
|
-
import { readdir,
|
|
1
|
+
import { readdir, mkdir, writeFile } from 'fs/promises';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { dirname, join, sep } from 'path';
|
|
4
|
+
import { listSkillFiles, listSkillNames, readSkillFile } from '@pikku/skills';
|
|
5
5
|
import { pikkuSessionlessFunc } from '#pikku';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
7
|
+
* Write one skill's files out to `dest`, reading through @pikku/skills rather
|
|
8
|
+
* than off a directory we resolved ourselves. That package reads from its own
|
|
9
|
+
* `skills/` folder when there is one and from its embedded manifest otherwise,
|
|
10
|
+
* which is what makes this work inside the `bun --compile` binaries — they carry
|
|
11
|
+
* no filesystem copy, so the old "walk up to the @pikku/cli package root"
|
|
12
|
+
* lookup threw on every Homebrew install.
|
|
11
13
|
*/
|
|
12
|
-
async function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (existsSync(skills))
|
|
22
|
-
return skills;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
catch { }
|
|
26
|
-
}
|
|
27
|
-
const parent = dirname(dir);
|
|
28
|
-
if (parent === dir)
|
|
29
|
-
break;
|
|
30
|
-
dir = parent;
|
|
31
|
-
}
|
|
32
|
-
throw new Error('Could not locate bundled skills directory. Was @pikku/cli installed correctly?');
|
|
33
|
-
}
|
|
34
|
-
async function copyDir(src, dest) {
|
|
35
|
-
await mkdir(dest, { recursive: true });
|
|
36
|
-
const entries = await readdir(src, { withFileTypes: true });
|
|
37
|
-
for (const entry of entries) {
|
|
38
|
-
const s = join(src, entry.name);
|
|
39
|
-
const d = join(dest, entry.name);
|
|
40
|
-
if (entry.isDirectory()) {
|
|
41
|
-
await copyDir(s, d);
|
|
42
|
-
}
|
|
43
|
-
else if (entry.isFile()) {
|
|
44
|
-
const content = await readFile(s);
|
|
45
|
-
await writeFile(d, content);
|
|
46
|
-
}
|
|
14
|
+
async function writeSkill(name, dest) {
|
|
15
|
+
for (const path of await listSkillFiles(name)) {
|
|
16
|
+
const contents = await readSkillFile(path);
|
|
17
|
+
if (contents === null)
|
|
18
|
+
continue;
|
|
19
|
+
// Paths are relative to the skills root, so drop the leading skill name.
|
|
20
|
+
const target = join(dest, ...path.split('/').slice(1));
|
|
21
|
+
await mkdir(dirname(target), { recursive: true });
|
|
22
|
+
await writeFile(target, contents, 'utf-8');
|
|
47
23
|
}
|
|
48
24
|
}
|
|
49
25
|
/**
|
|
@@ -82,17 +58,16 @@ function parseInstallGroups(frontmatter) {
|
|
|
82
58
|
}
|
|
83
59
|
return [];
|
|
84
60
|
}
|
|
85
|
-
async function readSkillInstallGroups(
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
61
|
+
async function readSkillInstallGroups(skillName) {
|
|
62
|
+
const content = await readSkillFile(`${skillName}/SKILL.md`);
|
|
63
|
+
if (content === null)
|
|
88
64
|
return [];
|
|
89
|
-
const content = await readFile(skillPath, 'utf-8');
|
|
90
65
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
91
66
|
if (!match)
|
|
92
67
|
return [];
|
|
93
68
|
return parseInstallGroups(match[1]);
|
|
94
69
|
}
|
|
95
|
-
async function getWantedSkills(
|
|
70
|
+
async function getWantedSkills(allEntries, { only, core = false, fabric = false, }) {
|
|
96
71
|
const wanted = new Set();
|
|
97
72
|
if (only) {
|
|
98
73
|
for (const name of only
|
|
@@ -108,7 +83,7 @@ async function getWantedSkills(skillsDir, allEntries, { only, core = false, fabr
|
|
|
108
83
|
];
|
|
109
84
|
if (requestedGroups.length > 0) {
|
|
110
85
|
for (const name of allEntries) {
|
|
111
|
-
const groups = await readSkillInstallGroups(
|
|
86
|
+
const groups = await readSkillInstallGroups(name);
|
|
112
87
|
if (requestedGroups.some((group) => groups.includes(group))) {
|
|
113
88
|
wanted.add(name);
|
|
114
89
|
}
|
|
@@ -118,11 +93,7 @@ async function getWantedSkills(skillsDir, allEntries, { only, core = false, fabr
|
|
|
118
93
|
}
|
|
119
94
|
export const pikkuSkillsList = pikkuSessionlessFunc({
|
|
120
95
|
func: async ({ logger }) => {
|
|
121
|
-
const
|
|
122
|
-
const entries = (await readdir(skillsDir, { withFileTypes: true }))
|
|
123
|
-
.filter((e) => e.isDirectory())
|
|
124
|
-
.map((e) => e.name)
|
|
125
|
-
.sort();
|
|
96
|
+
const entries = await listSkillNames();
|
|
126
97
|
const installed = new Set();
|
|
127
98
|
for (const relative of Object.values(AGENT_SKILL_DIRS)) {
|
|
128
99
|
const dir = join(process.cwd(), relative);
|
|
@@ -154,12 +125,8 @@ export const pikkuSkillsInstall = pikkuSessionlessFunc({
|
|
|
154
125
|
process.exitCode = 1;
|
|
155
126
|
return;
|
|
156
127
|
}
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
.filter((e) => e.isDirectory())
|
|
160
|
-
.map((e) => e.name)
|
|
161
|
-
.sort();
|
|
162
|
-
const wanted = await getWantedSkills(skillsDir, allEntries, {
|
|
128
|
+
const allEntries = await listSkillNames();
|
|
129
|
+
const wanted = await getWantedSkills(allEntries, {
|
|
163
130
|
only,
|
|
164
131
|
core,
|
|
165
132
|
fabric,
|
|
@@ -175,13 +142,12 @@ export const pikkuSkillsInstall = pikkuSessionlessFunc({
|
|
|
175
142
|
let installed = 0;
|
|
176
143
|
let skipped = 0;
|
|
177
144
|
for (const name of wanted) {
|
|
178
|
-
const src = join(skillsDir, name);
|
|
179
145
|
const dest = join(installRoot, name);
|
|
180
146
|
if (existsSync(dest) && !update) {
|
|
181
147
|
skipped++;
|
|
182
148
|
continue;
|
|
183
149
|
}
|
|
184
|
-
await
|
|
150
|
+
await writeSkill(name, dest);
|
|
185
151
|
installed++;
|
|
186
152
|
}
|
|
187
153
|
const destLabel = `${AGENT_SKILL_DIRS[agent].split(sep).join('/')}/`;
|
|
@@ -19,6 +19,12 @@ export interface CodegenOptions {
|
|
|
19
19
|
rootDir?: string;
|
|
20
20
|
/** DB dialect — drives real-type-aware date typing. Defaults to 'sqlite'. */
|
|
21
21
|
dialect?: Dialect;
|
|
22
|
+
/**
|
|
23
|
+
* Where the project's migrations live. Only used to attribute each table in
|
|
24
|
+
* `schemaJsonFile` to whoever declared it; omitting it makes every table read
|
|
25
|
+
* as app-owned, which is what a project with no generated migrations is.
|
|
26
|
+
*/
|
|
27
|
+
migrationsDir?: string;
|
|
22
28
|
}
|
|
23
29
|
export interface CodegenResult {
|
|
24
30
|
outFile: string;
|
|
@@ -2,6 +2,7 @@ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import { loadAnnotations, nameSuggestsKind, } from './annotation-parser.js';
|
|
4
4
|
import { ZOD_FORMATS } from './zod-codegen.js';
|
|
5
|
+
import { bareUnquotedTableName, readMigrationProvenance, } from './migration-provenance.js';
|
|
5
6
|
import { ErrorCode } from '@pikku/inspector';
|
|
6
7
|
/**
|
|
7
8
|
* The column kind implied by the *real* DB type, dialect-aware. Postgres has
|
|
@@ -504,8 +505,16 @@ export async function generateSchemaTypes(introspector, options) {
|
|
|
504
505
|
let schemaJsonBody = null;
|
|
505
506
|
if (options.schemaJsonFile) {
|
|
506
507
|
const fkMap = await introspector.getAllForeignKeys();
|
|
508
|
+
// Attribution comes from the migrations rather than from `schemaSources()`:
|
|
509
|
+
// the JSON is written by `db migrate`, which has no reason to load the
|
|
510
|
+
// project's Better Auth config, and the generated migrations already record
|
|
511
|
+
// both the source and its prose. See `readMigrationProvenance`.
|
|
512
|
+
const provenance = options.migrationsDir
|
|
513
|
+
? readMigrationProvenance(options.migrationsDir)
|
|
514
|
+
: new Map();
|
|
507
515
|
const jsonTables = tables.map((t) => ({
|
|
508
516
|
name: t.name,
|
|
517
|
+
...(provenance.get(bareUnquotedTableName(t.name)) ?? { source: 'app' }),
|
|
509
518
|
columns: t.columns.map((c) => {
|
|
510
519
|
const fk = fkMap.get(t.name)?.find((f) => f.column === c.name);
|
|
511
520
|
return {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { assertSnakeCaseIdentifiers } from './migration-identifiers.js';
|
|
4
5
|
/**
|
|
5
6
|
* The migrator's own bookkeeping table, which belongs to no dialect and to no
|
|
6
7
|
* project's schema.
|
|
@@ -75,7 +76,20 @@ function assertNoDrift(applied, migrationsDir) {
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Every migration on disk, read once.
|
|
81
|
+
*
|
|
82
|
+
* The identifier check reads all of them and not just the pending ones, so that
|
|
83
|
+
* whether a migration is rejected depends on the files alone. A camelCase
|
|
84
|
+
* column that had already been applied somewhere would otherwise pass on that
|
|
85
|
+
* machine and fail on a fresh checkout, which is the opposite of deterministic.
|
|
86
|
+
*/
|
|
87
|
+
const readMigrations = (migrationsDir) => migrationFiles(migrationsDir).map((name) => ({
|
|
88
|
+
name,
|
|
89
|
+
sql: readFileSync(join(migrationsDir, name), 'utf8'),
|
|
90
|
+
}));
|
|
78
91
|
export async function migrate(executor, migrationsDir) {
|
|
92
|
+
assertSnakeCaseIdentifiers(readMigrations(migrationsDir));
|
|
79
93
|
await executor.ensureTrackingTable();
|
|
80
94
|
const applied = await executor.getApplied();
|
|
81
95
|
assertNoDrift(applied, migrationsDir);
|
|
@@ -307,6 +307,14 @@ export interface GenerateResult {
|
|
|
307
307
|
* Partially covered writes the delta, because re-emitting the whole schema
|
|
308
308
|
* would fail on the tables that already exist.
|
|
309
309
|
*
|
|
310
|
+
* The delta is itself two different things, and only one of them is a diff. A
|
|
311
|
+
* table the migrations already have but a column short is a genuine alteration,
|
|
312
|
+
* so it becomes `ALTER TABLE … ADD COLUMN`. A table they do not have at all —
|
|
313
|
+
* what enabling a Better Auth plugin or upgrading an addon produces — has
|
|
314
|
+
* nothing to diff against, so it is lifted verbatim out of the source's own SQL
|
|
315
|
+
* for the same reason the first-time case is: the column map it would otherwise
|
|
316
|
+
* be rendered from knows nothing about keys, constraints or indexes.
|
|
317
|
+
*
|
|
310
318
|
* Migrations are written one file per source, numbered in dependency order, so
|
|
311
319
|
* a project can review and apply them independently.
|
|
312
320
|
*/
|
|
@@ -10,6 +10,7 @@ import { loadAuthOptions, getAuthMigrations } from './better-auth-schema.js';
|
|
|
10
10
|
import { generateSchemaTypes } from './db-codegen.js';
|
|
11
11
|
import { generateZodTypes } from './zod-codegen.js';
|
|
12
12
|
import { createCoercionPlugin } from './coercion-plugin.js';
|
|
13
|
+
import { tableCreationSql } from './schema-sql.js';
|
|
13
14
|
import { SqliteMigrationExecutor } from './sqlite/sqlite-migrator.js';
|
|
14
15
|
import { SqliteIntrospector } from './sqlite/sqlite-introspector.js';
|
|
15
16
|
import { createSqliteKysely } from './sqlite/sqlite-kysely.js';
|
|
@@ -190,6 +191,7 @@ export async function migrateAndCodegen(resolved, options = {}) {
|
|
|
190
191
|
camelCase: resolved.camelCase,
|
|
191
192
|
rootDir: resolved.rootDir,
|
|
192
193
|
dialect: 'sqlite',
|
|
194
|
+
migrationsDir: resolved.migrationsDir,
|
|
193
195
|
});
|
|
194
196
|
}
|
|
195
197
|
finally {
|
|
@@ -216,6 +218,7 @@ export async function migrateAndCodegen(resolved, options = {}) {
|
|
|
216
218
|
camelCase: resolved.camelCase,
|
|
217
219
|
rootDir: resolved.rootDir,
|
|
218
220
|
dialect: 'postgres',
|
|
221
|
+
migrationsDir: resolved.migrationsDir,
|
|
219
222
|
});
|
|
220
223
|
}
|
|
221
224
|
finally {
|
|
@@ -983,6 +986,14 @@ function addColumnStatements(table, columns) {
|
|
|
983
986
|
* Partially covered writes the delta, because re-emitting the whole schema
|
|
984
987
|
* would fail on the tables that already exist.
|
|
985
988
|
*
|
|
989
|
+
* The delta is itself two different things, and only one of them is a diff. A
|
|
990
|
+
* table the migrations already have but a column short is a genuine alteration,
|
|
991
|
+
* so it becomes `ALTER TABLE … ADD COLUMN`. A table they do not have at all —
|
|
992
|
+
* what enabling a Better Auth plugin or upgrading an addon produces — has
|
|
993
|
+
* nothing to diff against, so it is lifted verbatim out of the source's own SQL
|
|
994
|
+
* for the same reason the first-time case is: the column map it would otherwise
|
|
995
|
+
* be rendered from knows nothing about keys, constraints or indexes.
|
|
996
|
+
*
|
|
986
997
|
* Migrations are written one file per source, numbered in dependency order, so
|
|
987
998
|
* a project can review and apply them independently.
|
|
988
999
|
*/
|
|
@@ -1009,9 +1020,20 @@ export async function generateMigrations(resolved, rootDir, srcDirectories, logg
|
|
|
1009
1020
|
else {
|
|
1010
1021
|
const statements = [];
|
|
1011
1022
|
for (const table of missingTables) {
|
|
1023
|
+
// A wholly new table is the first-time case in miniature: nothing to
|
|
1024
|
+
// diff against, and the source's own SQL already says exactly how to
|
|
1025
|
+
// build it. Rendering the column map instead would drop the primary
|
|
1026
|
+
// key, the foreign keys and the indexes — a table that applies cleanly
|
|
1027
|
+
// and is permanently wrong.
|
|
1028
|
+
const own = tableCreationSql(source.desired.sql, table);
|
|
1029
|
+
if (own.length > 0) {
|
|
1030
|
+
statements.push(own.join('\n\n'));
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1012
1033
|
const columns = source.desired.tables.get(table);
|
|
1013
|
-
statements.push(`-- REVIEW: ${table} is new
|
|
1014
|
-
`--
|
|
1034
|
+
statements.push(`-- REVIEW: ${table} is new, and its CREATE TABLE could not be found in the\n` +
|
|
1035
|
+
`-- source's own SQL. The column list below carries no indexes, constraints\n` +
|
|
1036
|
+
`-- or foreign keys — copy the real statement over it before applying.\n` +
|
|
1015
1037
|
`CREATE TABLE ${table} (\n` +
|
|
1016
1038
|
[...(columns?.values() ?? [])]
|
|
1017
1039
|
.map((c) => ` ${c.name} ${c.type}${c.notNull ? ' NOT NULL' : ''}`)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catching camelCase columns at migrate time, before they can half-work.
|
|
3
|
+
*
|
|
4
|
+
* Pikku's Kysely runs with `CamelCasePlugin`, so `priceCents` in TypeScript is
|
|
5
|
+
* `price_cents` in SQL and nothing else. A migration that declares the column
|
|
6
|
+
* *as* `priceCents` is not merely unconventional — it is broken in the one way
|
|
7
|
+
* that hides itself: `.selectAll()` compiles to `SELECT *` and never names an
|
|
8
|
+
* identifier, so the table reads back perfectly, while the first query that
|
|
9
|
+
* names the column (`.select(['animal.priceCents'])`) asks for `price_cents`
|
|
10
|
+
* and gets `no such column`. The usual conclusion is that the plugin is broken,
|
|
11
|
+
* and the usual response is a raw `sql` template or a retreat to `.selectAll()`
|
|
12
|
+
* — both of which keep the real cause alive.
|
|
13
|
+
*
|
|
14
|
+
* There is no exception to escape. Even the Better Auth schema `db generate`
|
|
15
|
+
* writes is snake_case (`email_verified`, `user_id`), because Better Auth is
|
|
16
|
+
* handed the app's own Kysely and its camelCase field names compile the same
|
|
17
|
+
* way everyone else's do.
|
|
18
|
+
*/
|
|
19
|
+
/** A camelCase identifier a migration declares, and what it should have said. */
|
|
20
|
+
export interface CamelCaseIdentifier {
|
|
21
|
+
file: string;
|
|
22
|
+
table: string;
|
|
23
|
+
/** `null` when the table name itself is the offender. */
|
|
24
|
+
column: string | null;
|
|
25
|
+
suggestion: string;
|
|
26
|
+
}
|
|
27
|
+
export declare class CamelCaseIdentifierError extends Error {
|
|
28
|
+
readonly offenders: CamelCaseIdentifier[];
|
|
29
|
+
constructor(offenders: CamelCaseIdentifier[]);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Strip comments so a word in prose can never be read as an identifier.
|
|
33
|
+
*
|
|
34
|
+
* `splitStatements` already skips over comments when it looks for a boundary,
|
|
35
|
+
* but it hands back the statement with them still in it, and `-- the userId
|
|
36
|
+
* column` would otherwise be flagged. Quoting rules are honoured for the same
|
|
37
|
+
* reason they are there: `'a -- b'` is a string, not a comment.
|
|
38
|
+
*/
|
|
39
|
+
export declare function stripSqlComments(sql: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Every camelCase identifier one migration file declares.
|
|
42
|
+
*
|
|
43
|
+
* Only declarations are read — a `CREATE TABLE` column list and an `ALTER TABLE
|
|
44
|
+
* … ADD COLUMN`. Everywhere else an identifier merely refers to something that
|
|
45
|
+
* was declared somewhere, and reporting those would name the same mistake once
|
|
46
|
+
* per reference while adding nothing to the fix.
|
|
47
|
+
*/
|
|
48
|
+
export declare function findCamelCaseIdentifiers(file: string, sql: string): CamelCaseIdentifier[];
|
|
49
|
+
/**
|
|
50
|
+
* Bail unless every migration on disk is snake_case throughout.
|
|
51
|
+
*
|
|
52
|
+
* Reports all of them at once: a camelCase column is rarely alone, and a
|
|
53
|
+
* one-at-a-time failure turns a single edit into one migrate run per column.
|
|
54
|
+
*/
|
|
55
|
+
export declare function assertSnakeCaseIdentifiers(migrations: Array<{
|
|
56
|
+
name: string;
|
|
57
|
+
sql: string;
|
|
58
|
+
}>): void;
|