@akanjs/cli 2.3.11-rc.3 → 2.3.11-rc.5
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.
|
@@ -1710,7 +1710,8 @@ var appRootAllowedFiles = new Set([
|
|
|
1710
1710
|
"main.ts",
|
|
1711
1711
|
"package.json",
|
|
1712
1712
|
"server.ts",
|
|
1713
|
-
"tsconfig.json"
|
|
1713
|
+
"tsconfig.json",
|
|
1714
|
+
"tsconfig.tsbuildinfo"
|
|
1714
1715
|
]);
|
|
1715
1716
|
var appRootAllowedDirs = new Set([
|
|
1716
1717
|
".akan",
|
|
@@ -4170,6 +4171,7 @@ var BACKEND_RESTART_DEBOUNCE_MS = 120;
|
|
|
4170
4171
|
var BACKEND_GRACEFUL_TIMEOUT_MS = 3000;
|
|
4171
4172
|
var BACKEND_RECOVERY_BASE_DELAY_MS = 1000;
|
|
4172
4173
|
var BACKEND_RECOVERY_MAX_DELAY_MS = 30000;
|
|
4174
|
+
var BACKEND_STDERR_TAIL_LIMIT = 40;
|
|
4173
4175
|
var BUILDER_READY_TIMEOUT_MS = 150000;
|
|
4174
4176
|
var BUILDER_START_MAX_ATTEMPTS = 3;
|
|
4175
4177
|
var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
@@ -4372,6 +4374,7 @@ class AkanAppHost {
|
|
|
4372
4374
|
#pendingRestartReason = null;
|
|
4373
4375
|
#backendStartStatus = null;
|
|
4374
4376
|
#backendBuildStatusGeneration = 0;
|
|
4377
|
+
#backendStderrTail = [];
|
|
4375
4378
|
#lastGoodFrontend = {};
|
|
4376
4379
|
#buildStatusByPhase = new Map;
|
|
4377
4380
|
#pendingBuildStatusReplay = [];
|
|
@@ -4423,6 +4426,7 @@ class AkanAppHost {
|
|
|
4423
4426
|
this.#backendStartStatus = startStatus;
|
|
4424
4427
|
this.#setBackendLifecycleState("starting");
|
|
4425
4428
|
this.#backendReady = false;
|
|
4429
|
+
this.#backendStderrTail = [];
|
|
4426
4430
|
const backend = Bun.spawn(["bun", `apps/${this.app.name}/main.ts`], {
|
|
4427
4431
|
cwd: this.app.workspace.workspaceRoot,
|
|
4428
4432
|
stdio: this.withInk ? ["ignore", "pipe", "pipe"] : ["inherit", "inherit", "inherit"],
|
|
@@ -4456,6 +4460,37 @@ class AkanAppHost {
|
|
|
4456
4460
|
});
|
|
4457
4461
|
this.#backend = backend;
|
|
4458
4462
|
this.logger.verbose(`backend spawned pid=${backend.pid}`);
|
|
4463
|
+
if (this.withInk) {
|
|
4464
|
+
this.#forwardBackendStream(backend.stderr, "stderr");
|
|
4465
|
+
this.#forwardBackendStream(backend.stdout, "stdout");
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
#recordBackendStderr(chunk) {
|
|
4469
|
+
const lines = chunk.split(/\r?\n/).filter((line) => line.length > 0);
|
|
4470
|
+
if (lines.length === 0)
|
|
4471
|
+
return;
|
|
4472
|
+
this.#backendStderrTail.push(...lines);
|
|
4473
|
+
if (this.#backendStderrTail.length > BACKEND_STDERR_TAIL_LIMIT) {
|
|
4474
|
+
this.#backendStderrTail.splice(0, this.#backendStderrTail.length - BACKEND_STDERR_TAIL_LIMIT);
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
async#forwardBackendStream(stream, kind) {
|
|
4478
|
+
if (!stream)
|
|
4479
|
+
return;
|
|
4480
|
+
const decoder = new TextDecoder;
|
|
4481
|
+
try {
|
|
4482
|
+
for await (const chunk of stream) {
|
|
4483
|
+
const text = decoder.decode(chunk, { stream: true });
|
|
4484
|
+
if (!text.trim())
|
|
4485
|
+
continue;
|
|
4486
|
+
if (kind === "stderr") {
|
|
4487
|
+
this.#recordBackendStderr(text);
|
|
4488
|
+
this.logger.warn(`[backend] ${text.trimEnd()}`);
|
|
4489
|
+
} else {
|
|
4490
|
+
this.logger.verbose(`[backend] ${text.trimEnd()}`);
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4493
|
+
} catch {}
|
|
4459
4494
|
}
|
|
4460
4495
|
#nextBackendBuildStatusGeneration(generation) {
|
|
4461
4496
|
if (typeof generation === "number") {
|
|
@@ -4580,6 +4615,11 @@ class AkanAppHost {
|
|
|
4580
4615
|
});
|
|
4581
4616
|
this.#sendOrQueueBuildStatus(failureStatus);
|
|
4582
4617
|
this.logger.warn(`[backend-recovery] backend exited unexpectedly (${reason}); restarting in ${delay}ms (attempt ${this.#backendRecoveryAttempts})`);
|
|
4618
|
+
if (this.#backendStderrTail.length > 0) {
|
|
4619
|
+
this.logger.warn(`[backend-recovery] recent backend stderr:
|
|
4620
|
+
${this.#backendStderrTail.join(`
|
|
4621
|
+
`)}`);
|
|
4622
|
+
}
|
|
4583
4623
|
this.#backendRecoveryTimer = setTimeout(() => {
|
|
4584
4624
|
this.#backendRecoveryTimer = null;
|
|
4585
4625
|
if (this.#backend)
|
|
@@ -5945,6 +5985,117 @@ ${values.map((value) => ` ${value}: t([${JSON.stringify(titleize(value))}, ${
|
|
|
5945
5985
|
return `${content.slice(0, insertBeforeIndex)}${enumBlock}${content.slice(insertBeforeIndex)}`;
|
|
5946
5986
|
};
|
|
5947
5987
|
var parseValues = (value) => value?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
|
|
5988
|
+
var hasSourceParseErrors = (content, fileName = "source.ts") => hasParseDiagnostics(sourceFileFor(fileName, content));
|
|
5989
|
+
var findClassDeclaration = (source, className) => {
|
|
5990
|
+
let found = null;
|
|
5991
|
+
const visit = (node) => {
|
|
5992
|
+
if (found)
|
|
5993
|
+
return;
|
|
5994
|
+
if (ts4.isClassDeclaration(node) && node.name?.text === className) {
|
|
5995
|
+
found = node;
|
|
5996
|
+
return;
|
|
5997
|
+
}
|
|
5998
|
+
ts4.forEachChild(node, visit);
|
|
5999
|
+
};
|
|
6000
|
+
ts4.forEachChild(source, visit);
|
|
6001
|
+
return found;
|
|
6002
|
+
};
|
|
6003
|
+
var firstHeritageCall = (node) => {
|
|
6004
|
+
const expression = (node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [])[0]?.expression;
|
|
6005
|
+
return expression && ts4.isCallExpression(expression) ? expression : null;
|
|
6006
|
+
};
|
|
6007
|
+
var factoryArrowOf = (call) => {
|
|
6008
|
+
const arg = call.arguments.find((argument) => ts4.isArrowFunction(argument) || ts4.isFunctionExpression(argument));
|
|
6009
|
+
return arg && (ts4.isArrowFunction(arg) || ts4.isFunctionExpression(arg)) ? arg : null;
|
|
6010
|
+
};
|
|
6011
|
+
var hasClassMethod = (content, className, methodName) => {
|
|
6012
|
+
const node = findClassDeclaration(sourceFileFor("service.ts", content), className);
|
|
6013
|
+
return Boolean(node?.members.some((member) => ts4.isMethodDeclaration(member) && nodeName(member.name) === methodName));
|
|
6014
|
+
};
|
|
6015
|
+
var insertClassMethod = (content, className, methodBlock) => {
|
|
6016
|
+
const source = sourceFileFor("service.ts", content);
|
|
6017
|
+
const node = findClassDeclaration(source, className);
|
|
6018
|
+
if (!node)
|
|
6019
|
+
return null;
|
|
6020
|
+
const closeBrace = node.getEnd() - 1;
|
|
6021
|
+
if (content[closeBrace] !== "}")
|
|
6022
|
+
return null;
|
|
6023
|
+
const lead = content.slice(0, closeBrace).endsWith(`
|
|
6024
|
+
`) ? "" : `
|
|
6025
|
+
`;
|
|
6026
|
+
return spliceText(content, closeBrace, closeBrace, `${lead}${methodBlock}
|
|
6027
|
+
`);
|
|
6028
|
+
};
|
|
6029
|
+
var arrowParamInnerRegion = (content, source, arrow) => {
|
|
6030
|
+
if (arrow.parameters.length > 0) {
|
|
6031
|
+
return {
|
|
6032
|
+
start: arrow.parameters[0].getStart(source),
|
|
6033
|
+
end: arrow.parameters[arrow.parameters.length - 1].getEnd()
|
|
6034
|
+
};
|
|
6035
|
+
}
|
|
6036
|
+
const open = content.indexOf("(", arrow.getStart(source));
|
|
6037
|
+
if (open < 0)
|
|
6038
|
+
return null;
|
|
6039
|
+
const close = content.indexOf(")", open);
|
|
6040
|
+
if (close < 0)
|
|
6041
|
+
return null;
|
|
6042
|
+
return { start: open + 1, end: close };
|
|
6043
|
+
};
|
|
6044
|
+
var factoryParamEdit = (content, source, arrow, plan) => {
|
|
6045
|
+
if (arrow.parameters.length > 1)
|
|
6046
|
+
return null;
|
|
6047
|
+
const region = arrowParamInnerRegion(content, source, arrow);
|
|
6048
|
+
if (!region)
|
|
6049
|
+
return null;
|
|
6050
|
+
if (arrow.parameters.length === 0) {
|
|
6051
|
+
return { ...region, text: plan.mode === "destructure" ? `{ ${plan.name} }` : plan.name };
|
|
6052
|
+
}
|
|
6053
|
+
const param = arrow.parameters[0];
|
|
6054
|
+
if (plan.mode === "positional")
|
|
6055
|
+
return nodeName(param.name) === plan.name ? "unchanged" : null;
|
|
6056
|
+
if (!ts4.isObjectBindingPattern(param.name))
|
|
6057
|
+
return null;
|
|
6058
|
+
const names = param.name.elements.map((element) => ts4.isIdentifier(element.name) ? element.name.text : null);
|
|
6059
|
+
if (names.some((name) => name === null))
|
|
6060
|
+
return null;
|
|
6061
|
+
if (names.includes(plan.name))
|
|
6062
|
+
return "unchanged";
|
|
6063
|
+
return {
|
|
6064
|
+
start: param.getStart(source),
|
|
6065
|
+
end: param.getEnd(),
|
|
6066
|
+
text: `{ ${[...names, plan.name].join(", ")} }`
|
|
6067
|
+
};
|
|
6068
|
+
};
|
|
6069
|
+
var factoryObjectOf = (source, className) => {
|
|
6070
|
+
const node = findClassDeclaration(source, className);
|
|
6071
|
+
const call = node ? firstHeritageCall(node) : null;
|
|
6072
|
+
const arrow = call ? factoryArrowOf(call) : null;
|
|
6073
|
+
const object = arrow ? firstObjectReturnedByArrow(arrow) : null;
|
|
6074
|
+
return arrow && object ? { arrow, object } : null;
|
|
6075
|
+
};
|
|
6076
|
+
var hasSignalFactoryEntry = (content, className, entryName) => {
|
|
6077
|
+
const located = factoryObjectOf(sourceFileFor("signal.ts", content), className);
|
|
6078
|
+
return Boolean(located?.object.properties.some((property) => propertyName(property) === entryName));
|
|
6079
|
+
};
|
|
6080
|
+
var insertSignalFactoryEntry = (content, className, entryName, entryLine, param) => {
|
|
6081
|
+
const source = sourceFileFor("signal.ts", content);
|
|
6082
|
+
const located = factoryObjectOf(source, className);
|
|
6083
|
+
if (!located)
|
|
6084
|
+
return null;
|
|
6085
|
+
const locator = locatedObject(source, located.object);
|
|
6086
|
+
if (locator.fields.some((field) => field.name === entryName))
|
|
6087
|
+
return content;
|
|
6088
|
+
const paramEdit = factoryParamEdit(content, source, located.arrow, param);
|
|
6089
|
+
if (paramEdit === null)
|
|
6090
|
+
return null;
|
|
6091
|
+
const withEntry = insertOrderedFieldLine(content, locator, entryName, entryLine, {
|
|
6092
|
+
fieldIndent: " ",
|
|
6093
|
+
closingIndent: ""
|
|
6094
|
+
});
|
|
6095
|
+
if (withEntry === content)
|
|
6096
|
+
return null;
|
|
6097
|
+
return paramEdit === "unchanged" ? withEntry : spliceText(withEntry, paramEdit.start, paramEdit.end, paramEdit.text);
|
|
6098
|
+
};
|
|
5948
6099
|
|
|
5949
6100
|
// pkgs/@akanjs/devkit/workflow/moduleIndex.ts
|
|
5950
6101
|
var indexFileKinds = [
|
|
@@ -6662,7 +6813,9 @@ var createWorkflowStepRegistry = ({
|
|
|
6662
6813
|
createScalar,
|
|
6663
6814
|
createUi,
|
|
6664
6815
|
addField,
|
|
6665
|
-
addEnumField
|
|
6816
|
+
addEnumField,
|
|
6817
|
+
addMutation,
|
|
6818
|
+
addSlice
|
|
6666
6819
|
}) => {
|
|
6667
6820
|
const inspect = async () => {
|
|
6668
6821
|
return;
|
|
@@ -6738,7 +6891,19 @@ var createWorkflowStepRegistry = ({
|
|
|
6738
6891
|
defaultValue: workflowStringInput(plan.inputs.default)
|
|
6739
6892
|
})),
|
|
6740
6893
|
[workflowStepKey("add-enum-field", "update-dictionary")]: inspect,
|
|
6741
|
-
[workflowStepKey("add-enum-field", "update-option")]: inspect
|
|
6894
|
+
[workflowStepKey("add-enum-field", "update-option")]: inspect,
|
|
6895
|
+
[workflowStepKey("add-mutation", "update-service")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addMutation({
|
|
6896
|
+
app: workflowStringInput(plan.inputs.app),
|
|
6897
|
+
module: workflowStringInput(plan.inputs.module),
|
|
6898
|
+
mutation: workflowStringInput(plan.inputs.mutation)
|
|
6899
|
+
})),
|
|
6900
|
+
[workflowStepKey("add-mutation", "update-signal")]: inspect,
|
|
6901
|
+
[workflowStepKey("add-slice", "update-service-query")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addSlice({
|
|
6902
|
+
app: workflowStringInput(plan.inputs.app),
|
|
6903
|
+
module: workflowStringInput(plan.inputs.module),
|
|
6904
|
+
slice: workflowStringInput(plan.inputs.slice)
|
|
6905
|
+
})),
|
|
6906
|
+
[workflowStepKey("add-slice", "update-signal-slice")]: inspect
|
|
6742
6907
|
};
|
|
6743
6908
|
};
|
|
6744
6909
|
class WorkflowExecutor {
|
|
@@ -12807,11 +12972,15 @@ async function resolveSignalTestPreloadPath(target) {
|
|
|
12807
12972
|
addResolvedPackageCandidate(path39.dirname(Bun.main));
|
|
12808
12973
|
addResolvedPackageCandidate(import.meta.dir);
|
|
12809
12974
|
candidates.push(path39.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(process.cwd(), "node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(process.cwd(), "pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(path39.dirname(Bun.main), "../../akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.resolve(import.meta.dir, "../../akanjs", SIGNAL_TEST_PRELOAD_PATH));
|
|
12810
|
-
|
|
12975
|
+
const uniqueCandidates = [...new Set(candidates)];
|
|
12976
|
+
for (const candidate of uniqueCandidates) {
|
|
12811
12977
|
if (await Bun.file(candidate).exists())
|
|
12812
12978
|
return candidate;
|
|
12813
12979
|
}
|
|
12814
|
-
throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}
|
|
12980
|
+
throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}.
|
|
12981
|
+
Probed paths:
|
|
12982
|
+
${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
|
|
12983
|
+
`)}`);
|
|
12815
12984
|
}
|
|
12816
12985
|
// pkgs/@akanjs/devkit/builder.ts
|
|
12817
12986
|
import { existsSync as existsSync2 } from "fs";
|
package/index.js
CHANGED
|
@@ -1708,7 +1708,8 @@ var appRootAllowedFiles = new Set([
|
|
|
1708
1708
|
"main.ts",
|
|
1709
1709
|
"package.json",
|
|
1710
1710
|
"server.ts",
|
|
1711
|
-
"tsconfig.json"
|
|
1711
|
+
"tsconfig.json",
|
|
1712
|
+
"tsconfig.tsbuildinfo"
|
|
1712
1713
|
]);
|
|
1713
1714
|
var appRootAllowedDirs = new Set([
|
|
1714
1715
|
".akan",
|
|
@@ -4168,6 +4169,7 @@ var BACKEND_RESTART_DEBOUNCE_MS = 120;
|
|
|
4168
4169
|
var BACKEND_GRACEFUL_TIMEOUT_MS = 3000;
|
|
4169
4170
|
var BACKEND_RECOVERY_BASE_DELAY_MS = 1000;
|
|
4170
4171
|
var BACKEND_RECOVERY_MAX_DELAY_MS = 30000;
|
|
4172
|
+
var BACKEND_STDERR_TAIL_LIMIT = 40;
|
|
4171
4173
|
var BUILDER_READY_TIMEOUT_MS = 150000;
|
|
4172
4174
|
var BUILDER_START_MAX_ATTEMPTS = 3;
|
|
4173
4175
|
var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
@@ -4370,6 +4372,7 @@ class AkanAppHost {
|
|
|
4370
4372
|
#pendingRestartReason = null;
|
|
4371
4373
|
#backendStartStatus = null;
|
|
4372
4374
|
#backendBuildStatusGeneration = 0;
|
|
4375
|
+
#backendStderrTail = [];
|
|
4373
4376
|
#lastGoodFrontend = {};
|
|
4374
4377
|
#buildStatusByPhase = new Map;
|
|
4375
4378
|
#pendingBuildStatusReplay = [];
|
|
@@ -4421,6 +4424,7 @@ class AkanAppHost {
|
|
|
4421
4424
|
this.#backendStartStatus = startStatus;
|
|
4422
4425
|
this.#setBackendLifecycleState("starting");
|
|
4423
4426
|
this.#backendReady = false;
|
|
4427
|
+
this.#backendStderrTail = [];
|
|
4424
4428
|
const backend = Bun.spawn(["bun", `apps/${this.app.name}/main.ts`], {
|
|
4425
4429
|
cwd: this.app.workspace.workspaceRoot,
|
|
4426
4430
|
stdio: this.withInk ? ["ignore", "pipe", "pipe"] : ["inherit", "inherit", "inherit"],
|
|
@@ -4454,6 +4458,37 @@ class AkanAppHost {
|
|
|
4454
4458
|
});
|
|
4455
4459
|
this.#backend = backend;
|
|
4456
4460
|
this.logger.verbose(`backend spawned pid=${backend.pid}`);
|
|
4461
|
+
if (this.withInk) {
|
|
4462
|
+
this.#forwardBackendStream(backend.stderr, "stderr");
|
|
4463
|
+
this.#forwardBackendStream(backend.stdout, "stdout");
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
#recordBackendStderr(chunk) {
|
|
4467
|
+
const lines = chunk.split(/\r?\n/).filter((line) => line.length > 0);
|
|
4468
|
+
if (lines.length === 0)
|
|
4469
|
+
return;
|
|
4470
|
+
this.#backendStderrTail.push(...lines);
|
|
4471
|
+
if (this.#backendStderrTail.length > BACKEND_STDERR_TAIL_LIMIT) {
|
|
4472
|
+
this.#backendStderrTail.splice(0, this.#backendStderrTail.length - BACKEND_STDERR_TAIL_LIMIT);
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
async#forwardBackendStream(stream, kind) {
|
|
4476
|
+
if (!stream)
|
|
4477
|
+
return;
|
|
4478
|
+
const decoder = new TextDecoder;
|
|
4479
|
+
try {
|
|
4480
|
+
for await (const chunk of stream) {
|
|
4481
|
+
const text = decoder.decode(chunk, { stream: true });
|
|
4482
|
+
if (!text.trim())
|
|
4483
|
+
continue;
|
|
4484
|
+
if (kind === "stderr") {
|
|
4485
|
+
this.#recordBackendStderr(text);
|
|
4486
|
+
this.logger.warn(`[backend] ${text.trimEnd()}`);
|
|
4487
|
+
} else {
|
|
4488
|
+
this.logger.verbose(`[backend] ${text.trimEnd()}`);
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
} catch {}
|
|
4457
4492
|
}
|
|
4458
4493
|
#nextBackendBuildStatusGeneration(generation) {
|
|
4459
4494
|
if (typeof generation === "number") {
|
|
@@ -4578,6 +4613,11 @@ class AkanAppHost {
|
|
|
4578
4613
|
});
|
|
4579
4614
|
this.#sendOrQueueBuildStatus(failureStatus);
|
|
4580
4615
|
this.logger.warn(`[backend-recovery] backend exited unexpectedly (${reason}); restarting in ${delay}ms (attempt ${this.#backendRecoveryAttempts})`);
|
|
4616
|
+
if (this.#backendStderrTail.length > 0) {
|
|
4617
|
+
this.logger.warn(`[backend-recovery] recent backend stderr:
|
|
4618
|
+
${this.#backendStderrTail.join(`
|
|
4619
|
+
`)}`);
|
|
4620
|
+
}
|
|
4581
4621
|
this.#backendRecoveryTimer = setTimeout(() => {
|
|
4582
4622
|
this.#backendRecoveryTimer = null;
|
|
4583
4623
|
if (this.#backend)
|
|
@@ -5943,6 +5983,117 @@ ${values.map((value) => ` ${value}: t([${JSON.stringify(titleize(value))}, ${
|
|
|
5943
5983
|
return `${content.slice(0, insertBeforeIndex)}${enumBlock}${content.slice(insertBeforeIndex)}`;
|
|
5944
5984
|
};
|
|
5945
5985
|
var parseValues = (value) => value?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
|
|
5986
|
+
var hasSourceParseErrors = (content, fileName = "source.ts") => hasParseDiagnostics(sourceFileFor(fileName, content));
|
|
5987
|
+
var findClassDeclaration = (source, className) => {
|
|
5988
|
+
let found = null;
|
|
5989
|
+
const visit = (node) => {
|
|
5990
|
+
if (found)
|
|
5991
|
+
return;
|
|
5992
|
+
if (ts4.isClassDeclaration(node) && node.name?.text === className) {
|
|
5993
|
+
found = node;
|
|
5994
|
+
return;
|
|
5995
|
+
}
|
|
5996
|
+
ts4.forEachChild(node, visit);
|
|
5997
|
+
};
|
|
5998
|
+
ts4.forEachChild(source, visit);
|
|
5999
|
+
return found;
|
|
6000
|
+
};
|
|
6001
|
+
var firstHeritageCall = (node) => {
|
|
6002
|
+
const expression = (node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [])[0]?.expression;
|
|
6003
|
+
return expression && ts4.isCallExpression(expression) ? expression : null;
|
|
6004
|
+
};
|
|
6005
|
+
var factoryArrowOf = (call) => {
|
|
6006
|
+
const arg = call.arguments.find((argument) => ts4.isArrowFunction(argument) || ts4.isFunctionExpression(argument));
|
|
6007
|
+
return arg && (ts4.isArrowFunction(arg) || ts4.isFunctionExpression(arg)) ? arg : null;
|
|
6008
|
+
};
|
|
6009
|
+
var hasClassMethod = (content, className, methodName) => {
|
|
6010
|
+
const node = findClassDeclaration(sourceFileFor("service.ts", content), className);
|
|
6011
|
+
return Boolean(node?.members.some((member) => ts4.isMethodDeclaration(member) && nodeName(member.name) === methodName));
|
|
6012
|
+
};
|
|
6013
|
+
var insertClassMethod = (content, className, methodBlock) => {
|
|
6014
|
+
const source = sourceFileFor("service.ts", content);
|
|
6015
|
+
const node = findClassDeclaration(source, className);
|
|
6016
|
+
if (!node)
|
|
6017
|
+
return null;
|
|
6018
|
+
const closeBrace = node.getEnd() - 1;
|
|
6019
|
+
if (content[closeBrace] !== "}")
|
|
6020
|
+
return null;
|
|
6021
|
+
const lead = content.slice(0, closeBrace).endsWith(`
|
|
6022
|
+
`) ? "" : `
|
|
6023
|
+
`;
|
|
6024
|
+
return spliceText(content, closeBrace, closeBrace, `${lead}${methodBlock}
|
|
6025
|
+
`);
|
|
6026
|
+
};
|
|
6027
|
+
var arrowParamInnerRegion = (content, source, arrow) => {
|
|
6028
|
+
if (arrow.parameters.length > 0) {
|
|
6029
|
+
return {
|
|
6030
|
+
start: arrow.parameters[0].getStart(source),
|
|
6031
|
+
end: arrow.parameters[arrow.parameters.length - 1].getEnd()
|
|
6032
|
+
};
|
|
6033
|
+
}
|
|
6034
|
+
const open = content.indexOf("(", arrow.getStart(source));
|
|
6035
|
+
if (open < 0)
|
|
6036
|
+
return null;
|
|
6037
|
+
const close = content.indexOf(")", open);
|
|
6038
|
+
if (close < 0)
|
|
6039
|
+
return null;
|
|
6040
|
+
return { start: open + 1, end: close };
|
|
6041
|
+
};
|
|
6042
|
+
var factoryParamEdit = (content, source, arrow, plan) => {
|
|
6043
|
+
if (arrow.parameters.length > 1)
|
|
6044
|
+
return null;
|
|
6045
|
+
const region = arrowParamInnerRegion(content, source, arrow);
|
|
6046
|
+
if (!region)
|
|
6047
|
+
return null;
|
|
6048
|
+
if (arrow.parameters.length === 0) {
|
|
6049
|
+
return { ...region, text: plan.mode === "destructure" ? `{ ${plan.name} }` : plan.name };
|
|
6050
|
+
}
|
|
6051
|
+
const param = arrow.parameters[0];
|
|
6052
|
+
if (plan.mode === "positional")
|
|
6053
|
+
return nodeName(param.name) === plan.name ? "unchanged" : null;
|
|
6054
|
+
if (!ts4.isObjectBindingPattern(param.name))
|
|
6055
|
+
return null;
|
|
6056
|
+
const names = param.name.elements.map((element) => ts4.isIdentifier(element.name) ? element.name.text : null);
|
|
6057
|
+
if (names.some((name) => name === null))
|
|
6058
|
+
return null;
|
|
6059
|
+
if (names.includes(plan.name))
|
|
6060
|
+
return "unchanged";
|
|
6061
|
+
return {
|
|
6062
|
+
start: param.getStart(source),
|
|
6063
|
+
end: param.getEnd(),
|
|
6064
|
+
text: `{ ${[...names, plan.name].join(", ")} }`
|
|
6065
|
+
};
|
|
6066
|
+
};
|
|
6067
|
+
var factoryObjectOf = (source, className) => {
|
|
6068
|
+
const node = findClassDeclaration(source, className);
|
|
6069
|
+
const call = node ? firstHeritageCall(node) : null;
|
|
6070
|
+
const arrow = call ? factoryArrowOf(call) : null;
|
|
6071
|
+
const object = arrow ? firstObjectReturnedByArrow(arrow) : null;
|
|
6072
|
+
return arrow && object ? { arrow, object } : null;
|
|
6073
|
+
};
|
|
6074
|
+
var hasSignalFactoryEntry = (content, className, entryName) => {
|
|
6075
|
+
const located = factoryObjectOf(sourceFileFor("signal.ts", content), className);
|
|
6076
|
+
return Boolean(located?.object.properties.some((property) => propertyName(property) === entryName));
|
|
6077
|
+
};
|
|
6078
|
+
var insertSignalFactoryEntry = (content, className, entryName, entryLine, param) => {
|
|
6079
|
+
const source = sourceFileFor("signal.ts", content);
|
|
6080
|
+
const located = factoryObjectOf(source, className);
|
|
6081
|
+
if (!located)
|
|
6082
|
+
return null;
|
|
6083
|
+
const locator = locatedObject(source, located.object);
|
|
6084
|
+
if (locator.fields.some((field) => field.name === entryName))
|
|
6085
|
+
return content;
|
|
6086
|
+
const paramEdit = factoryParamEdit(content, source, located.arrow, param);
|
|
6087
|
+
if (paramEdit === null)
|
|
6088
|
+
return null;
|
|
6089
|
+
const withEntry = insertOrderedFieldLine(content, locator, entryName, entryLine, {
|
|
6090
|
+
fieldIndent: " ",
|
|
6091
|
+
closingIndent: ""
|
|
6092
|
+
});
|
|
6093
|
+
if (withEntry === content)
|
|
6094
|
+
return null;
|
|
6095
|
+
return paramEdit === "unchanged" ? withEntry : spliceText(withEntry, paramEdit.start, paramEdit.end, paramEdit.text);
|
|
6096
|
+
};
|
|
5946
6097
|
|
|
5947
6098
|
// pkgs/@akanjs/devkit/workflow/moduleIndex.ts
|
|
5948
6099
|
var indexFileKinds = [
|
|
@@ -6660,7 +6811,9 @@ var createWorkflowStepRegistry = ({
|
|
|
6660
6811
|
createScalar,
|
|
6661
6812
|
createUi,
|
|
6662
6813
|
addField,
|
|
6663
|
-
addEnumField
|
|
6814
|
+
addEnumField,
|
|
6815
|
+
addMutation,
|
|
6816
|
+
addSlice
|
|
6664
6817
|
}) => {
|
|
6665
6818
|
const inspect = async () => {
|
|
6666
6819
|
return;
|
|
@@ -6736,7 +6889,19 @@ var createWorkflowStepRegistry = ({
|
|
|
6736
6889
|
defaultValue: workflowStringInput(plan.inputs.default)
|
|
6737
6890
|
})),
|
|
6738
6891
|
[workflowStepKey("add-enum-field", "update-dictionary")]: inspect,
|
|
6739
|
-
[workflowStepKey("add-enum-field", "update-option")]: inspect
|
|
6892
|
+
[workflowStepKey("add-enum-field", "update-option")]: inspect,
|
|
6893
|
+
[workflowStepKey("add-mutation", "update-service")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addMutation({
|
|
6894
|
+
app: workflowStringInput(plan.inputs.app),
|
|
6895
|
+
module: workflowStringInput(plan.inputs.module),
|
|
6896
|
+
mutation: workflowStringInput(plan.inputs.mutation)
|
|
6897
|
+
})),
|
|
6898
|
+
[workflowStepKey("add-mutation", "update-signal")]: inspect,
|
|
6899
|
+
[workflowStepKey("add-slice", "update-service-query")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addSlice({
|
|
6900
|
+
app: workflowStringInput(plan.inputs.app),
|
|
6901
|
+
module: workflowStringInput(plan.inputs.module),
|
|
6902
|
+
slice: workflowStringInput(plan.inputs.slice)
|
|
6903
|
+
})),
|
|
6904
|
+
[workflowStepKey("add-slice", "update-signal-slice")]: inspect
|
|
6740
6905
|
};
|
|
6741
6906
|
};
|
|
6742
6907
|
class WorkflowExecutor {
|
|
@@ -12805,11 +12970,15 @@ async function resolveSignalTestPreloadPath(target) {
|
|
|
12805
12970
|
addResolvedPackageCandidate(path39.dirname(Bun.main));
|
|
12806
12971
|
addResolvedPackageCandidate(import.meta.dir);
|
|
12807
12972
|
candidates.push(path39.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(process.cwd(), "node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(process.cwd(), "pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(path39.dirname(Bun.main), "../../akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.resolve(import.meta.dir, "../../akanjs", SIGNAL_TEST_PRELOAD_PATH));
|
|
12808
|
-
|
|
12973
|
+
const uniqueCandidates = [...new Set(candidates)];
|
|
12974
|
+
for (const candidate of uniqueCandidates) {
|
|
12809
12975
|
if (await Bun.file(candidate).exists())
|
|
12810
12976
|
return candidate;
|
|
12811
12977
|
}
|
|
12812
|
-
throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}
|
|
12978
|
+
throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}.
|
|
12979
|
+
Probed paths:
|
|
12980
|
+
${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
|
|
12981
|
+
`)}`);
|
|
12813
12982
|
}
|
|
12814
12983
|
// pkgs/@akanjs/devkit/builder.ts
|
|
12815
12984
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -15860,19 +16029,12 @@ var targetPaths = {
|
|
|
15860
16029
|
"agents-md": "AGENTS.md",
|
|
15861
16030
|
claude: "CLAUDE.md"
|
|
15862
16031
|
};
|
|
15863
|
-
var
|
|
15864
|
-
|
|
15865
|
-
|
|
15866
|
-
claude: "Akan Claude Rules"
|
|
15867
|
-
};
|
|
15868
|
-
var renderAgentRules = async (workspace, target) => {
|
|
16032
|
+
var AGENT_BLOCK_START = "<!-- akan:agent:start -->";
|
|
16033
|
+
var AGENT_BLOCK_END = "<!-- akan:agent:end -->";
|
|
16034
|
+
var renderManagedBlock = async (workspace) => {
|
|
15869
16035
|
const context = await AkanContextAnalyzer.analyze(workspace);
|
|
15870
16036
|
const frameworkGuide = await Prompter.getInstruction("framework");
|
|
15871
|
-
return
|
|
15872
|
-
|
|
15873
|
-
This file is generated by \`akan agent install ${target}\`.
|
|
15874
|
-
|
|
15875
|
-
## Workspace
|
|
16037
|
+
return `## Workspace
|
|
15876
16038
|
|
|
15877
16039
|
- Repo: ${context.repoName}
|
|
15878
16040
|
- Apps: ${context.apps.map((app) => app.name).join(", ") || "none"}
|
|
@@ -15909,20 +16071,75 @@ ${context.validationCommands.map((command3) => `- \`${command3}\``).join(`
|
|
|
15909
16071
|
|
|
15910
16072
|
## Framework Guide
|
|
15911
16073
|
|
|
15912
|
-
${frameworkGuide.trim()}
|
|
16074
|
+
${frameworkGuide.trim()}`;
|
|
16075
|
+
};
|
|
16076
|
+
var upsertManagedBlock = (existing, block) => {
|
|
16077
|
+
const managed = `${AGENT_BLOCK_START}
|
|
16078
|
+
${block}
|
|
16079
|
+
${AGENT_BLOCK_END}`;
|
|
16080
|
+
const startIndex = existing.indexOf(AGENT_BLOCK_START);
|
|
16081
|
+
const endIndex = existing.indexOf(AGENT_BLOCK_END);
|
|
16082
|
+
if (startIndex >= 0 && endIndex > startIndex) {
|
|
16083
|
+
return `${existing.slice(0, startIndex)}${managed}${existing.slice(endIndex + AGENT_BLOCK_END.length)}`;
|
|
16084
|
+
}
|
|
16085
|
+
return `${existing.replace(/\s*$/, "")}
|
|
16086
|
+
|
|
16087
|
+
${managed}
|
|
16088
|
+
`;
|
|
16089
|
+
};
|
|
16090
|
+
var renderAgentsMd = async (workspace, existing) => {
|
|
16091
|
+
const block = await renderManagedBlock(workspace);
|
|
16092
|
+
if (existing?.trim())
|
|
16093
|
+
return upsertManagedBlock(existing, block);
|
|
16094
|
+
const context = await AkanContextAnalyzer.analyze(workspace);
|
|
16095
|
+
return `# ${context.repoName} Agent Guide
|
|
16096
|
+
|
|
16097
|
+
This file is the single source of truth for coding agents in this workspace. Claude Code reads it through
|
|
16098
|
+
\`CLAUDE.md\` (\`@AGENTS.md\`) and Cursor through \`.cursor/rules/akan.mdc\`. The section between the
|
|
16099
|
+
\`akan:agent\` markers is regenerated by \`akan agent install\`; edit anything outside the markers freely.
|
|
16100
|
+
|
|
16101
|
+
${AGENT_BLOCK_START}
|
|
16102
|
+
${block}
|
|
16103
|
+
${AGENT_BLOCK_END}
|
|
15913
16104
|
`;
|
|
15914
16105
|
};
|
|
16106
|
+
var renderClaudeMd = async (workspace) => {
|
|
16107
|
+
const context = await AkanContextAnalyzer.analyze(workspace);
|
|
16108
|
+
return `# ${context.repoName} \u2014 Claude Code Guide
|
|
16109
|
+
|
|
16110
|
+
@AGENTS.md
|
|
16111
|
+
`;
|
|
16112
|
+
};
|
|
16113
|
+
var renderCursorRule = () => `---
|
|
16114
|
+
description: Akan workspace agent guide
|
|
16115
|
+
alwaysApply: true
|
|
16116
|
+
---
|
|
16117
|
+
|
|
16118
|
+
Follow the workspace agent guide, which is the single source of truth for Akan conventions,
|
|
16119
|
+
generated-file rules, and the MCP workflow policy.
|
|
16120
|
+
|
|
16121
|
+
@AGENTS.md
|
|
16122
|
+
`;
|
|
16123
|
+
var renderTarget = async (workspace, target, existing) => {
|
|
16124
|
+
if (target === "agents-md")
|
|
16125
|
+
return await renderAgentsMd(workspace, existing);
|
|
16126
|
+
if (target === "claude")
|
|
16127
|
+
return await renderClaudeMd(workspace);
|
|
16128
|
+
return renderCursorRule();
|
|
16129
|
+
};
|
|
15915
16130
|
|
|
15916
16131
|
class AgentRunner extends runner("agent") {
|
|
15917
16132
|
async install(workspace, targets, { force = false } = {}) {
|
|
15918
16133
|
const written = [];
|
|
15919
16134
|
for (const target of targets) {
|
|
15920
16135
|
const filePath = targetPaths[target];
|
|
15921
|
-
|
|
16136
|
+
const exists2 = await workspace.exists(filePath);
|
|
16137
|
+
if (exists2 && !force && target !== "agents-md") {
|
|
15922
16138
|
throw new Error(`${filePath} already exists. Re-run with --force to overwrite it.`);
|
|
15923
16139
|
}
|
|
15924
|
-
const
|
|
15925
|
-
await workspace
|
|
16140
|
+
const existing = exists2 ? await workspace.readFile(filePath) : null;
|
|
16141
|
+
const content = await renderTarget(workspace, target, existing);
|
|
16142
|
+
await workspace.writeFile(filePath, content, { overwrite: true });
|
|
15926
16143
|
written.push(filePath);
|
|
15927
16144
|
}
|
|
15928
16145
|
return written;
|
|
@@ -17997,8 +18214,18 @@ var addMutationWorkflowSpec = {
|
|
|
17997
18214
|
}
|
|
17998
18215
|
],
|
|
17999
18216
|
predictedChanges: [
|
|
18000
|
-
{
|
|
18001
|
-
|
|
18217
|
+
{
|
|
18218
|
+
target: "*/lib/<module>/<module>.service.ts",
|
|
18219
|
+
action: "modify",
|
|
18220
|
+
applyScope: "auto",
|
|
18221
|
+
reason: "Service mutation method stub is added."
|
|
18222
|
+
},
|
|
18223
|
+
{
|
|
18224
|
+
target: "*/lib/<module>/<module>.signal.ts",
|
|
18225
|
+
action: "modify",
|
|
18226
|
+
applyScope: "auto",
|
|
18227
|
+
reason: "Signal endpoint mutation is added."
|
|
18228
|
+
},
|
|
18002
18229
|
{ target: "*/lib/srv.ts", action: "sync", reason: "Generated service barrel may change after sync." },
|
|
18003
18230
|
{ target: "*/lib/sig.ts", action: "sync", reason: "Generated signal barrel may change after sync." }
|
|
18004
18231
|
],
|
|
@@ -18070,9 +18297,24 @@ var addSliceWorkflowSpec = {
|
|
|
18070
18297
|
}
|
|
18071
18298
|
],
|
|
18072
18299
|
predictedChanges: [
|
|
18073
|
-
{
|
|
18074
|
-
|
|
18075
|
-
|
|
18300
|
+
{
|
|
18301
|
+
target: "*/lib/<module>/<module>.service.ts",
|
|
18302
|
+
action: "modify",
|
|
18303
|
+
applyScope: "auto",
|
|
18304
|
+
reason: "Query helper method stub is added."
|
|
18305
|
+
},
|
|
18306
|
+
{
|
|
18307
|
+
target: "*/lib/<module>/<module>.signal.ts",
|
|
18308
|
+
action: "modify",
|
|
18309
|
+
applyScope: "auto",
|
|
18310
|
+
reason: "Signal slice entry is added."
|
|
18311
|
+
},
|
|
18312
|
+
{
|
|
18313
|
+
target: "*/lib/<module>/<Module>.Zone.tsx",
|
|
18314
|
+
action: "modify",
|
|
18315
|
+
applyScope: "manual-review",
|
|
18316
|
+
reason: "Zone may connect slice state."
|
|
18317
|
+
},
|
|
18076
18318
|
{ target: "*/lib/sig.ts", action: "sync", reason: "Generated signal barrel may change after sync." }
|
|
18077
18319
|
],
|
|
18078
18320
|
validation: [
|
|
@@ -19384,6 +19626,12 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
19384
19626
|
code: "primitive-field-type-unsupported",
|
|
19385
19627
|
message: `Field type "${input7.type}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`,
|
|
19386
19628
|
input: "type"
|
|
19629
|
+
} : null,
|
|
19630
|
+
input7.type && input7.type.toLowerCase() === "upload" ? {
|
|
19631
|
+
severity: "error",
|
|
19632
|
+
code: "primitive-field-type-upload-misuse",
|
|
19633
|
+
message: "Upload is not a model field type. Declare an image/file field as a relation to the File model (e.g. field(File)); Upload is only valid in a { fileUpload: true } signal body. Note the File model is provided by the shared file library.",
|
|
19634
|
+
input: "type"
|
|
19387
19635
|
} : null
|
|
19388
19636
|
]);
|
|
19389
19637
|
if (!sys3 || !input7.module || !input7.field || !input7.type || diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
@@ -19582,6 +19830,206 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
19582
19830
|
nextActions: nextActionsForTarget(sys3.name)
|
|
19583
19831
|
});
|
|
19584
19832
|
}
|
|
19833
|
+
async addMutation(workspace, input7) {
|
|
19834
|
+
const moduleClassName = input7.module ? moduleComponentName(input7.module) : "";
|
|
19835
|
+
const serviceRef = lowerlize(moduleClassName);
|
|
19836
|
+
const name = input7.mutation;
|
|
19837
|
+
return await this.#writeServiceSignalEntry(workspace, {
|
|
19838
|
+
command: "add-mutation",
|
|
19839
|
+
app: input7.app,
|
|
19840
|
+
module: input7.module,
|
|
19841
|
+
entryKind: "mutation",
|
|
19842
|
+
entryName: name,
|
|
19843
|
+
requiredInput: "mutation",
|
|
19844
|
+
serviceMethod: name ? {
|
|
19845
|
+
name,
|
|
19846
|
+
block: [
|
|
19847
|
+
` async ${name}() {`,
|
|
19848
|
+
` // TODO(service): implement ${moduleClassName}Service.${name}`,
|
|
19849
|
+
` return true;`,
|
|
19850
|
+
` }`
|
|
19851
|
+
].join(`
|
|
19852
|
+
`)
|
|
19853
|
+
} : null,
|
|
19854
|
+
signal: name ? {
|
|
19855
|
+
className: `${moduleClassName}Endpoint`,
|
|
19856
|
+
entryLine: [
|
|
19857
|
+
`${name}: mutation(Boolean)`,
|
|
19858
|
+
` .exec(async function () {`,
|
|
19859
|
+
` return await this.${serviceRef}Service.${name}();`,
|
|
19860
|
+
` }),`
|
|
19861
|
+
].join(`
|
|
19862
|
+
`),
|
|
19863
|
+
param: { mode: "destructure", name: "mutation" }
|
|
19864
|
+
} : null
|
|
19865
|
+
});
|
|
19866
|
+
}
|
|
19867
|
+
async addSlice(workspace, input7) {
|
|
19868
|
+
const moduleClassName = input7.module ? moduleComponentName(input7.module) : "";
|
|
19869
|
+
const serviceRef = lowerlize(moduleClassName);
|
|
19870
|
+
const name = input7.slice;
|
|
19871
|
+
const queryName = name ? `query${capitalize12(name)}` : null;
|
|
19872
|
+
return await this.#writeServiceSignalEntry(workspace, {
|
|
19873
|
+
command: "add-slice",
|
|
19874
|
+
app: input7.app,
|
|
19875
|
+
module: input7.module,
|
|
19876
|
+
entryKind: "slice",
|
|
19877
|
+
entryName: name,
|
|
19878
|
+
requiredInput: "slice",
|
|
19879
|
+
serviceMethod: name && queryName ? {
|
|
19880
|
+
name: queryName,
|
|
19881
|
+
block: [
|
|
19882
|
+
` ${queryName}() {`,
|
|
19883
|
+
` // TODO(service): return a QueryOf for the ${name} slice`,
|
|
19884
|
+
` return {};`,
|
|
19885
|
+
` }`
|
|
19886
|
+
].join(`
|
|
19887
|
+
`)
|
|
19888
|
+
} : null,
|
|
19889
|
+
signal: name && queryName ? {
|
|
19890
|
+
className: `${moduleClassName}Slice`,
|
|
19891
|
+
entryLine: [
|
|
19892
|
+
`${name}: init()`,
|
|
19893
|
+
` .exec(function () {`,
|
|
19894
|
+
` return this.${serviceRef}Service.${queryName}();`,
|
|
19895
|
+
` }),`
|
|
19896
|
+
].join(`
|
|
19897
|
+
`),
|
|
19898
|
+
param: { mode: "positional", name: "init" }
|
|
19899
|
+
} : null
|
|
19900
|
+
});
|
|
19901
|
+
}
|
|
19902
|
+
async#writeServiceSignalEntry(workspace, spec) {
|
|
19903
|
+
const sys3 = await this.resolveSys(workspace, spec.app);
|
|
19904
|
+
const diagnostics = compactDiagnostics([
|
|
19905
|
+
!sys3 && { severity: "error", code: "primitive-target-missing", message: "Target app or library was not found." },
|
|
19906
|
+
!spec.module && {
|
|
19907
|
+
severity: "error",
|
|
19908
|
+
code: "primitive-input-missing",
|
|
19909
|
+
message: "Module is required.",
|
|
19910
|
+
input: "module"
|
|
19911
|
+
},
|
|
19912
|
+
!spec.entryName && {
|
|
19913
|
+
severity: "error",
|
|
19914
|
+
code: "primitive-input-missing",
|
|
19915
|
+
message: `${capitalize12(spec.requiredInput)} name is required.`,
|
|
19916
|
+
input: spec.requiredInput
|
|
19917
|
+
}
|
|
19918
|
+
]);
|
|
19919
|
+
if (!sys3 || !spec.module || !spec.entryName || !spec.serviceMethod || !spec.signal) {
|
|
19920
|
+
return createPrimitiveWriteReport({
|
|
19921
|
+
command: spec.command,
|
|
19922
|
+
changedFiles: [],
|
|
19923
|
+
generatedFiles: [],
|
|
19924
|
+
validationCommands: [],
|
|
19925
|
+
diagnostics,
|
|
19926
|
+
nextActions: []
|
|
19927
|
+
});
|
|
19928
|
+
}
|
|
19929
|
+
const paths = moduleSourcePaths(spec.module);
|
|
19930
|
+
const servicePath = paths.service;
|
|
19931
|
+
const signalPath = paths.signal;
|
|
19932
|
+
const changedFiles = [];
|
|
19933
|
+
const generatedFiles2 = generatedFilesForSync(sys3);
|
|
19934
|
+
const [hasServiceFile, hasSignalFile] = await Promise.all([sys3.exists(servicePath), sys3.exists(signalPath)]);
|
|
19935
|
+
if (!hasServiceFile) {
|
|
19936
|
+
diagnostics.push({
|
|
19937
|
+
severity: "error",
|
|
19938
|
+
code: "primitive-source-missing",
|
|
19939
|
+
message: `Service source file was not found: ${servicePath}.`
|
|
19940
|
+
});
|
|
19941
|
+
}
|
|
19942
|
+
if (!hasSignalFile) {
|
|
19943
|
+
diagnostics.push({
|
|
19944
|
+
severity: "error",
|
|
19945
|
+
code: "primitive-source-missing",
|
|
19946
|
+
message: `Signal source file was not found: ${signalPath}.`
|
|
19947
|
+
});
|
|
19948
|
+
}
|
|
19949
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
19950
|
+
return createPrimitiveWriteReport({
|
|
19951
|
+
command: spec.command,
|
|
19952
|
+
changedFiles,
|
|
19953
|
+
generatedFiles: generatedFiles2,
|
|
19954
|
+
validationCommands: validationCommandsForTarget(sys3.name),
|
|
19955
|
+
diagnostics,
|
|
19956
|
+
nextActions: nextActionsForTarget(sys3.name)
|
|
19957
|
+
});
|
|
19958
|
+
}
|
|
19959
|
+
const serviceClassName = `${moduleComponentName(spec.module)}Service`;
|
|
19960
|
+
const serviceContent = await sys3.readFile(servicePath);
|
|
19961
|
+
const signalContent = await sys3.readFile(signalPath);
|
|
19962
|
+
const serviceMethodExists = hasClassMethod(serviceContent, serviceClassName, spec.serviceMethod.name);
|
|
19963
|
+
if (serviceMethodExists) {
|
|
19964
|
+
diagnostics.push({
|
|
19965
|
+
severity: "error",
|
|
19966
|
+
code: "primitive-service-method-exists",
|
|
19967
|
+
input: spec.requiredInput,
|
|
19968
|
+
message: `Method "${spec.serviceMethod.name}" already exists in ${serviceClassName}.`
|
|
19969
|
+
});
|
|
19970
|
+
}
|
|
19971
|
+
if (hasSignalFactoryEntry(signalContent, spec.signal.className, spec.entryName)) {
|
|
19972
|
+
diagnostics.push({
|
|
19973
|
+
severity: "error",
|
|
19974
|
+
code: "primitive-signal-entry-exists",
|
|
19975
|
+
input: spec.requiredInput,
|
|
19976
|
+
message: `Entry "${spec.entryName}" already exists in ${spec.signal.className}.`
|
|
19977
|
+
});
|
|
19978
|
+
}
|
|
19979
|
+
const nextServiceContent = serviceMethodExists ? serviceContent : insertClassMethod(serviceContent, serviceClassName, spec.serviceMethod.block);
|
|
19980
|
+
const nextSignalContent = insertSignalFactoryEntry(signalContent, spec.signal.className, spec.entryName, spec.signal.entryLine, spec.signal.param);
|
|
19981
|
+
if (!nextServiceContent) {
|
|
19982
|
+
diagnostics.push({
|
|
19983
|
+
severity: "error",
|
|
19984
|
+
code: "primitive-service-shape-unsupported",
|
|
19985
|
+
message: `Could not find ${serviceClassName} class body in ${servicePath}.`
|
|
19986
|
+
});
|
|
19987
|
+
}
|
|
19988
|
+
if (!nextSignalContent) {
|
|
19989
|
+
diagnostics.push({
|
|
19990
|
+
severity: "error",
|
|
19991
|
+
code: "primitive-signal-shape-unsupported",
|
|
19992
|
+
message: `Could not find a safe insertion point in ${spec.signal.className} within ${signalPath}. Ensure the class exists and its factory returns an object literal.`
|
|
19993
|
+
});
|
|
19994
|
+
}
|
|
19995
|
+
if (nextServiceContent && hasSourceParseErrors(nextServiceContent, "service.ts")) {
|
|
19996
|
+
diagnostics.push({
|
|
19997
|
+
severity: "error",
|
|
19998
|
+
code: "primitive-post-edit-service-parse-failed",
|
|
19999
|
+
failureScope: "source-change",
|
|
20000
|
+
message: `Edited ${servicePath} did not parse cleanly; refusing to write source.`
|
|
20001
|
+
});
|
|
20002
|
+
}
|
|
20003
|
+
if (nextSignalContent && hasSourceParseErrors(nextSignalContent, "signal.ts")) {
|
|
20004
|
+
diagnostics.push({
|
|
20005
|
+
severity: "error",
|
|
20006
|
+
code: "primitive-post-edit-signal-parse-failed",
|
|
20007
|
+
failureScope: "source-change",
|
|
20008
|
+
message: `Edited ${signalPath} did not parse cleanly; refusing to write source.`
|
|
20009
|
+
});
|
|
20010
|
+
}
|
|
20011
|
+
if (nextSignalContent && !hasSignalFactoryEntry(nextSignalContent, spec.signal.className, spec.entryName)) {
|
|
20012
|
+
diagnostics.push({
|
|
20013
|
+
severity: "error",
|
|
20014
|
+
code: "primitive-post-edit-signal-verify-failed",
|
|
20015
|
+
failureScope: "source-change",
|
|
20016
|
+
message: `Edited ${signalPath} did not contain entry "${spec.entryName}" in ${spec.signal.className}.`
|
|
20017
|
+
});
|
|
20018
|
+
}
|
|
20019
|
+
if (!diagnostics.some((diagnostic) => diagnostic.severity === "error") && nextServiceContent && nextSignalContent) {
|
|
20020
|
+
await sys3.writeFile(servicePath, nextServiceContent);
|
|
20021
|
+
await sys3.writeFile(signalPath, nextSignalContent);
|
|
20022
|
+
changedFiles.push(sourceFile(sys3, servicePath, "modify", `Service ${spec.entryKind} was added.`), sourceFile(sys3, signalPath, "modify", `Signal ${spec.entryKind} was added.`));
|
|
20023
|
+
}
|
|
20024
|
+
return createPrimitiveWriteReport({
|
|
20025
|
+
command: spec.command,
|
|
20026
|
+
changedFiles,
|
|
20027
|
+
generatedFiles: generatedFiles2,
|
|
20028
|
+
validationCommands: validationCommandsForTarget(sys3.name),
|
|
20029
|
+
diagnostics,
|
|
20030
|
+
nextActions: nextActionsForTarget(sys3.name)
|
|
20031
|
+
});
|
|
20032
|
+
}
|
|
19585
20033
|
}
|
|
19586
20034
|
|
|
19587
20035
|
// pkgs/@akanjs/cli/scalar/scalar.prompt.ts
|
|
@@ -19799,7 +20247,9 @@ var createCliWorkflowStepRegistry = (workspace) => createWorkflowStepRegistry({
|
|
|
19799
20247
|
createScalar: (sys3, scalar) => CommandContainer.get(ScalarScript).createScalar(sys3, scalar),
|
|
19800
20248
|
createUi: (input8) => CommandContainer.get(PrimitiveScript).createUi(workspace, input8),
|
|
19801
20249
|
addField: (input8) => CommandContainer.get(PrimitiveScript).addField(workspace, input8),
|
|
19802
|
-
addEnumField: (input8) => CommandContainer.get(PrimitiveScript).addEnumField(workspace, input8)
|
|
20250
|
+
addEnumField: (input8) => CommandContainer.get(PrimitiveScript).addEnumField(workspace, input8),
|
|
20251
|
+
addMutation: (input8) => CommandContainer.get(PrimitiveScript).addMutation(workspace, input8),
|
|
20252
|
+
addSlice: (input8) => CommandContainer.get(PrimitiveScript).addSlice(workspace, input8)
|
|
19803
20253
|
});
|
|
19804
20254
|
|
|
19805
20255
|
// pkgs/@akanjs/cli/context/context.runner.ts
|
|
@@ -20990,7 +21440,9 @@ class WorkflowScript extends script("workflow", [WorkflowRunner, ModuleScript, S
|
|
|
20990
21440
|
createScalar: (sys3, scalar) => this.scalarScript.createScalar(sys3, scalar),
|
|
20991
21441
|
createUi: (input8) => this.primitiveScript.createUi(workspace, input8),
|
|
20992
21442
|
addField: (input8) => this.primitiveScript.addField(workspace, input8),
|
|
20993
|
-
addEnumField: (input8) => this.primitiveScript.addEnumField(workspace, input8)
|
|
21443
|
+
addEnumField: (input8) => this.primitiveScript.addEnumField(workspace, input8),
|
|
21444
|
+
addMutation: (input8) => this.primitiveScript.addMutation(workspace, input8),
|
|
21445
|
+
addSlice: (input8) => this.primitiveScript.addSlice(workspace, input8)
|
|
20994
21446
|
})
|
|
20995
21447
|
}));
|
|
20996
21448
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/cli",
|
|
3
|
-
"version": "2.3.11-rc.
|
|
3
|
+
"version": "2.3.11-rc.5",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@langchain/openai": "^1.4.6",
|
|
35
35
|
"@tailwindcss/node": "^4.3.0",
|
|
36
36
|
"@trapezedev/project": "^7.1.4",
|
|
37
|
-
"akanjs": "2.3.11-rc.
|
|
37
|
+
"akanjs": "2.3.11-rc.5",
|
|
38
38
|
"chalk": "^5.6.2",
|
|
39
39
|
"commander": "^14.0.3",
|
|
40
40
|
"daisyui": "5.5.23",
|
|
@@ -1,38 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Akan
|
|
2
|
+
description: Akan workspace agent guide
|
|
3
3
|
alwaysApply: true
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Follow the workspace agent guide, which is the single source of truth for Akan conventions,
|
|
7
|
+
generated-file rules, and the MCP workflow policy.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
- Pages live under `apps/<app>/page`; index routes use `_index.tsx`, and nested layouts use `_layout.tsx`.
|
|
10
|
-
- Database domain modules live under `lib/<model>`.
|
|
11
|
-
- Service modules live under `lib/_<service>`.
|
|
12
|
-
- Scalar modules live under `lib/__scalar/<scalar>`.
|
|
13
|
-
- Module abstracts live beside module code as `<model>.abstract.md`, `<service>.abstract.md`, or
|
|
14
|
-
`<scalar>.abstract.md`. Read them before changing module behavior.
|
|
15
|
-
- Do not hand-edit generated Akan files such as `akan.app.json`, `client.ts`, `server.ts`, generated facet indexes,
|
|
16
|
-
`lib/cnst.ts`, `lib/dict.ts`, `lib/db.ts`, `lib/srv.ts`, `lib/st.ts`, `lib/sig.ts`, `lib/useClient.ts`, or
|
|
17
|
-
`lib/useServer.ts`.
|
|
18
|
-
- Prefer Akan MCP workflows before direct source edits: use `akan mcp --mode plan` for workflow discovery and
|
|
19
|
-
planning, then `akan mcp --mode apply` only for allowlisted apply, validation, and repair tools.
|
|
20
|
-
- If `plan_workflow` returns `planPath` or `next.tool=apply_workflow`, call `apply_workflow({ planPath })` before
|
|
21
|
-
editing source files directly.
|
|
22
|
-
- After `apply_workflow`, run `run_validation` with `validationTarget` when present; otherwise use `applyReportPath`.
|
|
23
|
-
- Direct source edits are denied when an allowlisted Akan workflow or repair tool can perform the change.
|
|
24
|
-
- Direct edits are fallback only after `list_workflows`/`explain_workflow` show no matching workflow, or after
|
|
25
|
-
`apply_workflow` reports unsupported/no-op/failed diagnostics that require manual action.
|
|
26
|
-
- For compound requests, split the request into workflows and apply each `planPath` in order, such as `create-module`
|
|
27
|
-
followed by `add-field`.
|
|
28
|
-
- If generated output is stale or broken, update the owning source file and run `akan repair generated` or
|
|
29
|
-
`akan sync <app-or-lib>` instead of patching generated files.
|
|
30
|
-
- For new domain behavior, inspect sibling `constant`, `dictionary`, `signal`, `document`, `service`, `store`, and UI
|
|
31
|
-
module files before changing shape.
|
|
32
|
-
- Update `*.abstract.md` when business invariants, workflows, or public behavior change. Do not update it for
|
|
33
|
-
formatting-only, import-only, or style-only changes.
|
|
34
|
-
- Respect server/client import boundaries. Use `akanjs/server` only in server-side code and `akanjs/client` only in
|
|
35
|
-
client/page runtime code.
|
|
36
|
-
- Treat `AKAN_PUBLIC_*` env vars as public values. Do not store secrets in them.
|
|
37
|
-
- Verify changes with the smallest relevant command: `akan lint <target>`, `akan test <target>`, or
|
|
38
|
-
`akan build <app-name>`.
|
|
9
|
+
@AGENTS.md
|
|
@@ -576,3 +576,13 @@ akan sync automatically generates APIs across all layers. Only write custom logi
|
|
|
576
576
|
| `insight[Query](args)`, `query[Query](args)` | Insight and raw query |
|
|
577
577
|
|
|
578
578
|
**Rule**: Define `Filter` with `.query()` conditions in `document.ts`. akan sync auto-generates all 10 query helper methods per filter. Write `Document` chain methods only for state transitions with validation.
|
|
579
|
+
|
|
580
|
+
## Generated Context
|
|
581
|
+
|
|
582
|
+
The section below is regenerated by `akan agent install` (run `bun run setup:agent`). It lists this
|
|
583
|
+
workspace's apps, generated files, validation commands, and framework guide. Edit anything outside the
|
|
584
|
+
markers freely; content between them is overwritten on the next install.
|
|
585
|
+
|
|
586
|
+
<!-- akan:agent:start -->
|
|
587
|
+
<!-- Populated by `akan agent install`. Run `bun run setup:agent` to refresh. -->
|
|
588
|
+
<!-- akan:agent:end -->
|