@akanjs/cli 2.3.11-rc.2 → 2.3.11-rc.4

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.
@@ -4170,6 +4170,7 @@ var BACKEND_RESTART_DEBOUNCE_MS = 120;
4170
4170
  var BACKEND_GRACEFUL_TIMEOUT_MS = 3000;
4171
4171
  var BACKEND_RECOVERY_BASE_DELAY_MS = 1000;
4172
4172
  var BACKEND_RECOVERY_MAX_DELAY_MS = 30000;
4173
+ var BACKEND_STDERR_TAIL_LIMIT = 40;
4173
4174
  var BUILDER_READY_TIMEOUT_MS = 150000;
4174
4175
  var BUILDER_START_MAX_ATTEMPTS = 3;
4175
4176
  var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
@@ -4372,6 +4373,7 @@ class AkanAppHost {
4372
4373
  #pendingRestartReason = null;
4373
4374
  #backendStartStatus = null;
4374
4375
  #backendBuildStatusGeneration = 0;
4376
+ #backendStderrTail = [];
4375
4377
  #lastGoodFrontend = {};
4376
4378
  #buildStatusByPhase = new Map;
4377
4379
  #pendingBuildStatusReplay = [];
@@ -4423,6 +4425,7 @@ class AkanAppHost {
4423
4425
  this.#backendStartStatus = startStatus;
4424
4426
  this.#setBackendLifecycleState("starting");
4425
4427
  this.#backendReady = false;
4428
+ this.#backendStderrTail = [];
4426
4429
  const backend = Bun.spawn(["bun", `apps/${this.app.name}/main.ts`], {
4427
4430
  cwd: this.app.workspace.workspaceRoot,
4428
4431
  stdio: this.withInk ? ["ignore", "pipe", "pipe"] : ["inherit", "inherit", "inherit"],
@@ -4456,6 +4459,37 @@ class AkanAppHost {
4456
4459
  });
4457
4460
  this.#backend = backend;
4458
4461
  this.logger.verbose(`backend spawned pid=${backend.pid}`);
4462
+ if (this.withInk) {
4463
+ this.#forwardBackendStream(backend.stderr, "stderr");
4464
+ this.#forwardBackendStream(backend.stdout, "stdout");
4465
+ }
4466
+ }
4467
+ #recordBackendStderr(chunk) {
4468
+ const lines = chunk.split(/\r?\n/).filter((line) => line.length > 0);
4469
+ if (lines.length === 0)
4470
+ return;
4471
+ this.#backendStderrTail.push(...lines);
4472
+ if (this.#backendStderrTail.length > BACKEND_STDERR_TAIL_LIMIT) {
4473
+ this.#backendStderrTail.splice(0, this.#backendStderrTail.length - BACKEND_STDERR_TAIL_LIMIT);
4474
+ }
4475
+ }
4476
+ async#forwardBackendStream(stream, kind) {
4477
+ if (!stream)
4478
+ return;
4479
+ const decoder = new TextDecoder;
4480
+ try {
4481
+ for await (const chunk of stream) {
4482
+ const text = decoder.decode(chunk, { stream: true });
4483
+ if (!text.trim())
4484
+ continue;
4485
+ if (kind === "stderr") {
4486
+ this.#recordBackendStderr(text);
4487
+ this.logger.warn(`[backend] ${text.trimEnd()}`);
4488
+ } else {
4489
+ this.logger.verbose(`[backend] ${text.trimEnd()}`);
4490
+ }
4491
+ }
4492
+ } catch {}
4459
4493
  }
4460
4494
  #nextBackendBuildStatusGeneration(generation) {
4461
4495
  if (typeof generation === "number") {
@@ -4580,6 +4614,11 @@ class AkanAppHost {
4580
4614
  });
4581
4615
  this.#sendOrQueueBuildStatus(failureStatus);
4582
4616
  this.logger.warn(`[backend-recovery] backend exited unexpectedly (${reason}); restarting in ${delay}ms (attempt ${this.#backendRecoveryAttempts})`);
4617
+ if (this.#backendStderrTail.length > 0) {
4618
+ this.logger.warn(`[backend-recovery] recent backend stderr:
4619
+ ${this.#backendStderrTail.join(`
4620
+ `)}`);
4621
+ }
4583
4622
  this.#backendRecoveryTimer = setTimeout(() => {
4584
4623
  this.#backendRecoveryTimer = null;
4585
4624
  if (this.#backend)
@@ -5945,6 +5984,117 @@ ${values.map((value) => ` ${value}: t([${JSON.stringify(titleize(value))}, ${
5945
5984
  return `${content.slice(0, insertBeforeIndex)}${enumBlock}${content.slice(insertBeforeIndex)}`;
5946
5985
  };
5947
5986
  var parseValues = (value) => value?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
5987
+ var hasSourceParseErrors = (content, fileName = "source.ts") => hasParseDiagnostics(sourceFileFor(fileName, content));
5988
+ var findClassDeclaration = (source, className) => {
5989
+ let found = null;
5990
+ const visit = (node) => {
5991
+ if (found)
5992
+ return;
5993
+ if (ts4.isClassDeclaration(node) && node.name?.text === className) {
5994
+ found = node;
5995
+ return;
5996
+ }
5997
+ ts4.forEachChild(node, visit);
5998
+ };
5999
+ ts4.forEachChild(source, visit);
6000
+ return found;
6001
+ };
6002
+ var firstHeritageCall = (node) => {
6003
+ const expression = (node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [])[0]?.expression;
6004
+ return expression && ts4.isCallExpression(expression) ? expression : null;
6005
+ };
6006
+ var factoryArrowOf = (call) => {
6007
+ const arg = call.arguments.find((argument) => ts4.isArrowFunction(argument) || ts4.isFunctionExpression(argument));
6008
+ return arg && (ts4.isArrowFunction(arg) || ts4.isFunctionExpression(arg)) ? arg : null;
6009
+ };
6010
+ var hasClassMethod = (content, className, methodName) => {
6011
+ const node = findClassDeclaration(sourceFileFor("service.ts", content), className);
6012
+ return Boolean(node?.members.some((member) => ts4.isMethodDeclaration(member) && nodeName(member.name) === methodName));
6013
+ };
6014
+ var insertClassMethod = (content, className, methodBlock) => {
6015
+ const source = sourceFileFor("service.ts", content);
6016
+ const node = findClassDeclaration(source, className);
6017
+ if (!node)
6018
+ return null;
6019
+ const closeBrace = node.getEnd() - 1;
6020
+ if (content[closeBrace] !== "}")
6021
+ return null;
6022
+ const lead = content.slice(0, closeBrace).endsWith(`
6023
+ `) ? "" : `
6024
+ `;
6025
+ return spliceText(content, closeBrace, closeBrace, `${lead}${methodBlock}
6026
+ `);
6027
+ };
6028
+ var arrowParamInnerRegion = (content, source, arrow) => {
6029
+ if (arrow.parameters.length > 0) {
6030
+ return {
6031
+ start: arrow.parameters[0].getStart(source),
6032
+ end: arrow.parameters[arrow.parameters.length - 1].getEnd()
6033
+ };
6034
+ }
6035
+ const open = content.indexOf("(", arrow.getStart(source));
6036
+ if (open < 0)
6037
+ return null;
6038
+ const close = content.indexOf(")", open);
6039
+ if (close < 0)
6040
+ return null;
6041
+ return { start: open + 1, end: close };
6042
+ };
6043
+ var factoryParamEdit = (content, source, arrow, plan) => {
6044
+ if (arrow.parameters.length > 1)
6045
+ return null;
6046
+ const region = arrowParamInnerRegion(content, source, arrow);
6047
+ if (!region)
6048
+ return null;
6049
+ if (arrow.parameters.length === 0) {
6050
+ return { ...region, text: plan.mode === "destructure" ? `{ ${plan.name} }` : plan.name };
6051
+ }
6052
+ const param = arrow.parameters[0];
6053
+ if (plan.mode === "positional")
6054
+ return nodeName(param.name) === plan.name ? "unchanged" : null;
6055
+ if (!ts4.isObjectBindingPattern(param.name))
6056
+ return null;
6057
+ const names = param.name.elements.map((element) => ts4.isIdentifier(element.name) ? element.name.text : null);
6058
+ if (names.some((name) => name === null))
6059
+ return null;
6060
+ if (names.includes(plan.name))
6061
+ return "unchanged";
6062
+ return {
6063
+ start: param.getStart(source),
6064
+ end: param.getEnd(),
6065
+ text: `{ ${[...names, plan.name].join(", ")} }`
6066
+ };
6067
+ };
6068
+ var factoryObjectOf = (source, className) => {
6069
+ const node = findClassDeclaration(source, className);
6070
+ const call = node ? firstHeritageCall(node) : null;
6071
+ const arrow = call ? factoryArrowOf(call) : null;
6072
+ const object = arrow ? firstObjectReturnedByArrow(arrow) : null;
6073
+ return arrow && object ? { arrow, object } : null;
6074
+ };
6075
+ var hasSignalFactoryEntry = (content, className, entryName) => {
6076
+ const located = factoryObjectOf(sourceFileFor("signal.ts", content), className);
6077
+ return Boolean(located?.object.properties.some((property) => propertyName(property) === entryName));
6078
+ };
6079
+ var insertSignalFactoryEntry = (content, className, entryName, entryLine, param) => {
6080
+ const source = sourceFileFor("signal.ts", content);
6081
+ const located = factoryObjectOf(source, className);
6082
+ if (!located)
6083
+ return null;
6084
+ const locator = locatedObject(source, located.object);
6085
+ if (locator.fields.some((field) => field.name === entryName))
6086
+ return content;
6087
+ const paramEdit = factoryParamEdit(content, source, located.arrow, param);
6088
+ if (paramEdit === null)
6089
+ return null;
6090
+ const withEntry = insertOrderedFieldLine(content, locator, entryName, entryLine, {
6091
+ fieldIndent: " ",
6092
+ closingIndent: ""
6093
+ });
6094
+ if (withEntry === content)
6095
+ return null;
6096
+ return paramEdit === "unchanged" ? withEntry : spliceText(withEntry, paramEdit.start, paramEdit.end, paramEdit.text);
6097
+ };
5948
6098
 
5949
6099
  // pkgs/@akanjs/devkit/workflow/moduleIndex.ts
5950
6100
  var indexFileKinds = [
@@ -6662,7 +6812,9 @@ var createWorkflowStepRegistry = ({
6662
6812
  createScalar,
6663
6813
  createUi,
6664
6814
  addField,
6665
- addEnumField
6815
+ addEnumField,
6816
+ addMutation,
6817
+ addSlice
6666
6818
  }) => {
6667
6819
  const inspect = async () => {
6668
6820
  return;
@@ -6738,7 +6890,19 @@ var createWorkflowStepRegistry = ({
6738
6890
  defaultValue: workflowStringInput(plan.inputs.default)
6739
6891
  })),
6740
6892
  [workflowStepKey("add-enum-field", "update-dictionary")]: inspect,
6741
- [workflowStepKey("add-enum-field", "update-option")]: inspect
6893
+ [workflowStepKey("add-enum-field", "update-option")]: inspect,
6894
+ [workflowStepKey("add-mutation", "update-service")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addMutation({
6895
+ app: workflowStringInput(plan.inputs.app),
6896
+ module: workflowStringInput(plan.inputs.module),
6897
+ mutation: workflowStringInput(plan.inputs.mutation)
6898
+ })),
6899
+ [workflowStepKey("add-mutation", "update-signal")]: inspect,
6900
+ [workflowStepKey("add-slice", "update-service-query")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addSlice({
6901
+ app: workflowStringInput(plan.inputs.app),
6902
+ module: workflowStringInput(plan.inputs.module),
6903
+ slice: workflowStringInput(plan.inputs.slice)
6904
+ })),
6905
+ [workflowStepKey("add-slice", "update-signal-slice")]: inspect
6742
6906
  };
6743
6907
  };
6744
6908
  class WorkflowExecutor {
@@ -12807,11 +12971,15 @@ async function resolveSignalTestPreloadPath(target) {
12807
12971
  addResolvedPackageCandidate(path39.dirname(Bun.main));
12808
12972
  addResolvedPackageCandidate(import.meta.dir);
12809
12973
  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
- for (const candidate of [...new Set(candidates)]) {
12974
+ const uniqueCandidates = [...new Set(candidates)];
12975
+ for (const candidate of uniqueCandidates) {
12811
12976
  if (await Bun.file(candidate).exists())
12812
12977
  return candidate;
12813
12978
  }
12814
- throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}`);
12979
+ throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}.
12980
+ Probed paths:
12981
+ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
12982
+ `)}`);
12815
12983
  }
12816
12984
  // pkgs/@akanjs/devkit/builder.ts
12817
12985
  import { existsSync as existsSync2 } from "fs";
@@ -15720,10 +15888,8 @@ function getConventionDescription(suffix, modelName) {
15720
15888
  }
15721
15889
  function getLibRootFile(file) {
15722
15890
  const segments = file.split("/");
15723
- if (segments[0] === "libs" && segments.length === 4 && segments[2] === "lib")
15891
+ if ((segments[0] === "libs" || segments[0] === "apps") && segments.length === 4 && segments[2] === "lib")
15724
15892
  return segments[3];
15725
- if (segments[0] === "apps" && segments.length === 5 && segments[2] === "lib")
15726
- return segments[4];
15727
15893
  return null;
15728
15894
  }
15729
15895
  function getModuleUiWarning(file) {
package/index.js CHANGED
@@ -4168,6 +4168,7 @@ var BACKEND_RESTART_DEBOUNCE_MS = 120;
4168
4168
  var BACKEND_GRACEFUL_TIMEOUT_MS = 3000;
4169
4169
  var BACKEND_RECOVERY_BASE_DELAY_MS = 1000;
4170
4170
  var BACKEND_RECOVERY_MAX_DELAY_MS = 30000;
4171
+ var BACKEND_STDERR_TAIL_LIMIT = 40;
4171
4172
  var BUILDER_READY_TIMEOUT_MS = 150000;
4172
4173
  var BUILDER_START_MAX_ATTEMPTS = 3;
4173
4174
  var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
@@ -4370,6 +4371,7 @@ class AkanAppHost {
4370
4371
  #pendingRestartReason = null;
4371
4372
  #backendStartStatus = null;
4372
4373
  #backendBuildStatusGeneration = 0;
4374
+ #backendStderrTail = [];
4373
4375
  #lastGoodFrontend = {};
4374
4376
  #buildStatusByPhase = new Map;
4375
4377
  #pendingBuildStatusReplay = [];
@@ -4421,6 +4423,7 @@ class AkanAppHost {
4421
4423
  this.#backendStartStatus = startStatus;
4422
4424
  this.#setBackendLifecycleState("starting");
4423
4425
  this.#backendReady = false;
4426
+ this.#backendStderrTail = [];
4424
4427
  const backend = Bun.spawn(["bun", `apps/${this.app.name}/main.ts`], {
4425
4428
  cwd: this.app.workspace.workspaceRoot,
4426
4429
  stdio: this.withInk ? ["ignore", "pipe", "pipe"] : ["inherit", "inherit", "inherit"],
@@ -4454,6 +4457,37 @@ class AkanAppHost {
4454
4457
  });
4455
4458
  this.#backend = backend;
4456
4459
  this.logger.verbose(`backend spawned pid=${backend.pid}`);
4460
+ if (this.withInk) {
4461
+ this.#forwardBackendStream(backend.stderr, "stderr");
4462
+ this.#forwardBackendStream(backend.stdout, "stdout");
4463
+ }
4464
+ }
4465
+ #recordBackendStderr(chunk) {
4466
+ const lines = chunk.split(/\r?\n/).filter((line) => line.length > 0);
4467
+ if (lines.length === 0)
4468
+ return;
4469
+ this.#backendStderrTail.push(...lines);
4470
+ if (this.#backendStderrTail.length > BACKEND_STDERR_TAIL_LIMIT) {
4471
+ this.#backendStderrTail.splice(0, this.#backendStderrTail.length - BACKEND_STDERR_TAIL_LIMIT);
4472
+ }
4473
+ }
4474
+ async#forwardBackendStream(stream, kind) {
4475
+ if (!stream)
4476
+ return;
4477
+ const decoder = new TextDecoder;
4478
+ try {
4479
+ for await (const chunk of stream) {
4480
+ const text = decoder.decode(chunk, { stream: true });
4481
+ if (!text.trim())
4482
+ continue;
4483
+ if (kind === "stderr") {
4484
+ this.#recordBackendStderr(text);
4485
+ this.logger.warn(`[backend] ${text.trimEnd()}`);
4486
+ } else {
4487
+ this.logger.verbose(`[backend] ${text.trimEnd()}`);
4488
+ }
4489
+ }
4490
+ } catch {}
4457
4491
  }
4458
4492
  #nextBackendBuildStatusGeneration(generation) {
4459
4493
  if (typeof generation === "number") {
@@ -4578,6 +4612,11 @@ class AkanAppHost {
4578
4612
  });
4579
4613
  this.#sendOrQueueBuildStatus(failureStatus);
4580
4614
  this.logger.warn(`[backend-recovery] backend exited unexpectedly (${reason}); restarting in ${delay}ms (attempt ${this.#backendRecoveryAttempts})`);
4615
+ if (this.#backendStderrTail.length > 0) {
4616
+ this.logger.warn(`[backend-recovery] recent backend stderr:
4617
+ ${this.#backendStderrTail.join(`
4618
+ `)}`);
4619
+ }
4581
4620
  this.#backendRecoveryTimer = setTimeout(() => {
4582
4621
  this.#backendRecoveryTimer = null;
4583
4622
  if (this.#backend)
@@ -5943,6 +5982,117 @@ ${values.map((value) => ` ${value}: t([${JSON.stringify(titleize(value))}, ${
5943
5982
  return `${content.slice(0, insertBeforeIndex)}${enumBlock}${content.slice(insertBeforeIndex)}`;
5944
5983
  };
5945
5984
  var parseValues = (value) => value?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
5985
+ var hasSourceParseErrors = (content, fileName = "source.ts") => hasParseDiagnostics(sourceFileFor(fileName, content));
5986
+ var findClassDeclaration = (source, className) => {
5987
+ let found = null;
5988
+ const visit = (node) => {
5989
+ if (found)
5990
+ return;
5991
+ if (ts4.isClassDeclaration(node) && node.name?.text === className) {
5992
+ found = node;
5993
+ return;
5994
+ }
5995
+ ts4.forEachChild(node, visit);
5996
+ };
5997
+ ts4.forEachChild(source, visit);
5998
+ return found;
5999
+ };
6000
+ var firstHeritageCall = (node) => {
6001
+ const expression = (node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [])[0]?.expression;
6002
+ return expression && ts4.isCallExpression(expression) ? expression : null;
6003
+ };
6004
+ var factoryArrowOf = (call) => {
6005
+ const arg = call.arguments.find((argument) => ts4.isArrowFunction(argument) || ts4.isFunctionExpression(argument));
6006
+ return arg && (ts4.isArrowFunction(arg) || ts4.isFunctionExpression(arg)) ? arg : null;
6007
+ };
6008
+ var hasClassMethod = (content, className, methodName) => {
6009
+ const node = findClassDeclaration(sourceFileFor("service.ts", content), className);
6010
+ return Boolean(node?.members.some((member) => ts4.isMethodDeclaration(member) && nodeName(member.name) === methodName));
6011
+ };
6012
+ var insertClassMethod = (content, className, methodBlock) => {
6013
+ const source = sourceFileFor("service.ts", content);
6014
+ const node = findClassDeclaration(source, className);
6015
+ if (!node)
6016
+ return null;
6017
+ const closeBrace = node.getEnd() - 1;
6018
+ if (content[closeBrace] !== "}")
6019
+ return null;
6020
+ const lead = content.slice(0, closeBrace).endsWith(`
6021
+ `) ? "" : `
6022
+ `;
6023
+ return spliceText(content, closeBrace, closeBrace, `${lead}${methodBlock}
6024
+ `);
6025
+ };
6026
+ var arrowParamInnerRegion = (content, source, arrow) => {
6027
+ if (arrow.parameters.length > 0) {
6028
+ return {
6029
+ start: arrow.parameters[0].getStart(source),
6030
+ end: arrow.parameters[arrow.parameters.length - 1].getEnd()
6031
+ };
6032
+ }
6033
+ const open = content.indexOf("(", arrow.getStart(source));
6034
+ if (open < 0)
6035
+ return null;
6036
+ const close = content.indexOf(")", open);
6037
+ if (close < 0)
6038
+ return null;
6039
+ return { start: open + 1, end: close };
6040
+ };
6041
+ var factoryParamEdit = (content, source, arrow, plan) => {
6042
+ if (arrow.parameters.length > 1)
6043
+ return null;
6044
+ const region = arrowParamInnerRegion(content, source, arrow);
6045
+ if (!region)
6046
+ return null;
6047
+ if (arrow.parameters.length === 0) {
6048
+ return { ...region, text: plan.mode === "destructure" ? `{ ${plan.name} }` : plan.name };
6049
+ }
6050
+ const param = arrow.parameters[0];
6051
+ if (plan.mode === "positional")
6052
+ return nodeName(param.name) === plan.name ? "unchanged" : null;
6053
+ if (!ts4.isObjectBindingPattern(param.name))
6054
+ return null;
6055
+ const names = param.name.elements.map((element) => ts4.isIdentifier(element.name) ? element.name.text : null);
6056
+ if (names.some((name) => name === null))
6057
+ return null;
6058
+ if (names.includes(plan.name))
6059
+ return "unchanged";
6060
+ return {
6061
+ start: param.getStart(source),
6062
+ end: param.getEnd(),
6063
+ text: `{ ${[...names, plan.name].join(", ")} }`
6064
+ };
6065
+ };
6066
+ var factoryObjectOf = (source, className) => {
6067
+ const node = findClassDeclaration(source, className);
6068
+ const call = node ? firstHeritageCall(node) : null;
6069
+ const arrow = call ? factoryArrowOf(call) : null;
6070
+ const object = arrow ? firstObjectReturnedByArrow(arrow) : null;
6071
+ return arrow && object ? { arrow, object } : null;
6072
+ };
6073
+ var hasSignalFactoryEntry = (content, className, entryName) => {
6074
+ const located = factoryObjectOf(sourceFileFor("signal.ts", content), className);
6075
+ return Boolean(located?.object.properties.some((property) => propertyName(property) === entryName));
6076
+ };
6077
+ var insertSignalFactoryEntry = (content, className, entryName, entryLine, param) => {
6078
+ const source = sourceFileFor("signal.ts", content);
6079
+ const located = factoryObjectOf(source, className);
6080
+ if (!located)
6081
+ return null;
6082
+ const locator = locatedObject(source, located.object);
6083
+ if (locator.fields.some((field) => field.name === entryName))
6084
+ return content;
6085
+ const paramEdit = factoryParamEdit(content, source, located.arrow, param);
6086
+ if (paramEdit === null)
6087
+ return null;
6088
+ const withEntry = insertOrderedFieldLine(content, locator, entryName, entryLine, {
6089
+ fieldIndent: " ",
6090
+ closingIndent: ""
6091
+ });
6092
+ if (withEntry === content)
6093
+ return null;
6094
+ return paramEdit === "unchanged" ? withEntry : spliceText(withEntry, paramEdit.start, paramEdit.end, paramEdit.text);
6095
+ };
5946
6096
 
5947
6097
  // pkgs/@akanjs/devkit/workflow/moduleIndex.ts
5948
6098
  var indexFileKinds = [
@@ -6660,7 +6810,9 @@ var createWorkflowStepRegistry = ({
6660
6810
  createScalar,
6661
6811
  createUi,
6662
6812
  addField,
6663
- addEnumField
6813
+ addEnumField,
6814
+ addMutation,
6815
+ addSlice
6664
6816
  }) => {
6665
6817
  const inspect = async () => {
6666
6818
  return;
@@ -6736,7 +6888,19 @@ var createWorkflowStepRegistry = ({
6736
6888
  defaultValue: workflowStringInput(plan.inputs.default)
6737
6889
  })),
6738
6890
  [workflowStepKey("add-enum-field", "update-dictionary")]: inspect,
6739
- [workflowStepKey("add-enum-field", "update-option")]: inspect
6891
+ [workflowStepKey("add-enum-field", "update-option")]: inspect,
6892
+ [workflowStepKey("add-mutation", "update-service")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addMutation({
6893
+ app: workflowStringInput(plan.inputs.app),
6894
+ module: workflowStringInput(plan.inputs.module),
6895
+ mutation: workflowStringInput(plan.inputs.mutation)
6896
+ })),
6897
+ [workflowStepKey("add-mutation", "update-signal")]: inspect,
6898
+ [workflowStepKey("add-slice", "update-service-query")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addSlice({
6899
+ app: workflowStringInput(plan.inputs.app),
6900
+ module: workflowStringInput(plan.inputs.module),
6901
+ slice: workflowStringInput(plan.inputs.slice)
6902
+ })),
6903
+ [workflowStepKey("add-slice", "update-signal-slice")]: inspect
6740
6904
  };
6741
6905
  };
6742
6906
  class WorkflowExecutor {
@@ -12805,11 +12969,15 @@ async function resolveSignalTestPreloadPath(target) {
12805
12969
  addResolvedPackageCandidate(path39.dirname(Bun.main));
12806
12970
  addResolvedPackageCandidate(import.meta.dir);
12807
12971
  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
- for (const candidate of [...new Set(candidates)]) {
12972
+ const uniqueCandidates = [...new Set(candidates)];
12973
+ for (const candidate of uniqueCandidates) {
12809
12974
  if (await Bun.file(candidate).exists())
12810
12975
  return candidate;
12811
12976
  }
12812
- throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}`);
12977
+ throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}.
12978
+ Probed paths:
12979
+ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
12980
+ `)}`);
12813
12981
  }
12814
12982
  // pkgs/@akanjs/devkit/builder.ts
12815
12983
  import { existsSync as existsSync2 } from "fs";
@@ -15718,10 +15886,8 @@ function getConventionDescription(suffix, modelName) {
15718
15886
  }
15719
15887
  function getLibRootFile(file) {
15720
15888
  const segments = file.split("/");
15721
- if (segments[0] === "libs" && segments.length === 4 && segments[2] === "lib")
15889
+ if ((segments[0] === "libs" || segments[0] === "apps") && segments.length === 4 && segments[2] === "lib")
15722
15890
  return segments[3];
15723
- if (segments[0] === "apps" && segments.length === 5 && segments[2] === "lib")
15724
- return segments[4];
15725
15891
  return null;
15726
15892
  }
15727
15893
  function getModuleUiWarning(file) {
@@ -17999,8 +18165,18 @@ var addMutationWorkflowSpec = {
17999
18165
  }
18000
18166
  ],
18001
18167
  predictedChanges: [
18002
- { target: "*/lib/<module>/<module>.service.ts", action: "modify", reason: "Service mutation may be added." },
18003
- { target: "*/lib/<module>/<module>.signal.ts", action: "modify", reason: "Signal mutation may be added." },
18168
+ {
18169
+ target: "*/lib/<module>/<module>.service.ts",
18170
+ action: "modify",
18171
+ applyScope: "auto",
18172
+ reason: "Service mutation method stub is added."
18173
+ },
18174
+ {
18175
+ target: "*/lib/<module>/<module>.signal.ts",
18176
+ action: "modify",
18177
+ applyScope: "auto",
18178
+ reason: "Signal endpoint mutation is added."
18179
+ },
18004
18180
  { target: "*/lib/srv.ts", action: "sync", reason: "Generated service barrel may change after sync." },
18005
18181
  { target: "*/lib/sig.ts", action: "sync", reason: "Generated signal barrel may change after sync." }
18006
18182
  ],
@@ -18072,9 +18248,24 @@ var addSliceWorkflowSpec = {
18072
18248
  }
18073
18249
  ],
18074
18250
  predictedChanges: [
18075
- { target: "*/lib/<module>/<module>.service.ts", action: "modify", reason: "Query helper may be added." },
18076
- { target: "*/lib/<module>/<module>.signal.ts", action: "modify", reason: "Signal slice may be added." },
18077
- { target: "*/lib/<module>/<Module>.Zone.tsx", action: "modify", reason: "Zone may connect slice state." },
18251
+ {
18252
+ target: "*/lib/<module>/<module>.service.ts",
18253
+ action: "modify",
18254
+ applyScope: "auto",
18255
+ reason: "Query helper method stub is added."
18256
+ },
18257
+ {
18258
+ target: "*/lib/<module>/<module>.signal.ts",
18259
+ action: "modify",
18260
+ applyScope: "auto",
18261
+ reason: "Signal slice entry is added."
18262
+ },
18263
+ {
18264
+ target: "*/lib/<module>/<Module>.Zone.tsx",
18265
+ action: "modify",
18266
+ applyScope: "manual-review",
18267
+ reason: "Zone may connect slice state."
18268
+ },
18078
18269
  { target: "*/lib/sig.ts", action: "sync", reason: "Generated signal barrel may change after sync." }
18079
18270
  ],
18080
18271
  validation: [
@@ -19386,6 +19577,12 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
19386
19577
  code: "primitive-field-type-unsupported",
19387
19578
  message: `Field type "${input7.type}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`,
19388
19579
  input: "type"
19580
+ } : null,
19581
+ input7.type && input7.type.toLowerCase() === "upload" ? {
19582
+ severity: "error",
19583
+ code: "primitive-field-type-upload-misuse",
19584
+ 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.",
19585
+ input: "type"
19389
19586
  } : null
19390
19587
  ]);
19391
19588
  if (!sys3 || !input7.module || !input7.field || !input7.type || diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
@@ -19584,6 +19781,206 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
19584
19781
  nextActions: nextActionsForTarget(sys3.name)
19585
19782
  });
19586
19783
  }
19784
+ async addMutation(workspace, input7) {
19785
+ const moduleClassName = input7.module ? moduleComponentName(input7.module) : "";
19786
+ const serviceRef = lowerlize(moduleClassName);
19787
+ const name = input7.mutation;
19788
+ return await this.#writeServiceSignalEntry(workspace, {
19789
+ command: "add-mutation",
19790
+ app: input7.app,
19791
+ module: input7.module,
19792
+ entryKind: "mutation",
19793
+ entryName: name,
19794
+ requiredInput: "mutation",
19795
+ serviceMethod: name ? {
19796
+ name,
19797
+ block: [
19798
+ ` async ${name}() {`,
19799
+ ` // TODO(service): implement ${moduleClassName}Service.${name}`,
19800
+ ` return true;`,
19801
+ ` }`
19802
+ ].join(`
19803
+ `)
19804
+ } : null,
19805
+ signal: name ? {
19806
+ className: `${moduleClassName}Endpoint`,
19807
+ entryLine: [
19808
+ `${name}: mutation(Boolean)`,
19809
+ ` .exec(async function () {`,
19810
+ ` return await this.${serviceRef}Service.${name}();`,
19811
+ ` }),`
19812
+ ].join(`
19813
+ `),
19814
+ param: { mode: "destructure", name: "mutation" }
19815
+ } : null
19816
+ });
19817
+ }
19818
+ async addSlice(workspace, input7) {
19819
+ const moduleClassName = input7.module ? moduleComponentName(input7.module) : "";
19820
+ const serviceRef = lowerlize(moduleClassName);
19821
+ const name = input7.slice;
19822
+ const queryName = name ? `query${capitalize12(name)}` : null;
19823
+ return await this.#writeServiceSignalEntry(workspace, {
19824
+ command: "add-slice",
19825
+ app: input7.app,
19826
+ module: input7.module,
19827
+ entryKind: "slice",
19828
+ entryName: name,
19829
+ requiredInput: "slice",
19830
+ serviceMethod: name && queryName ? {
19831
+ name: queryName,
19832
+ block: [
19833
+ ` ${queryName}() {`,
19834
+ ` // TODO(service): return a QueryOf for the ${name} slice`,
19835
+ ` return {};`,
19836
+ ` }`
19837
+ ].join(`
19838
+ `)
19839
+ } : null,
19840
+ signal: name && queryName ? {
19841
+ className: `${moduleClassName}Slice`,
19842
+ entryLine: [
19843
+ `${name}: init()`,
19844
+ ` .exec(function () {`,
19845
+ ` return this.${serviceRef}Service.${queryName}();`,
19846
+ ` }),`
19847
+ ].join(`
19848
+ `),
19849
+ param: { mode: "positional", name: "init" }
19850
+ } : null
19851
+ });
19852
+ }
19853
+ async#writeServiceSignalEntry(workspace, spec) {
19854
+ const sys3 = await this.resolveSys(workspace, spec.app);
19855
+ const diagnostics = compactDiagnostics([
19856
+ !sys3 && { severity: "error", code: "primitive-target-missing", message: "Target app or library was not found." },
19857
+ !spec.module && {
19858
+ severity: "error",
19859
+ code: "primitive-input-missing",
19860
+ message: "Module is required.",
19861
+ input: "module"
19862
+ },
19863
+ !spec.entryName && {
19864
+ severity: "error",
19865
+ code: "primitive-input-missing",
19866
+ message: `${capitalize12(spec.requiredInput)} name is required.`,
19867
+ input: spec.requiredInput
19868
+ }
19869
+ ]);
19870
+ if (!sys3 || !spec.module || !spec.entryName || !spec.serviceMethod || !spec.signal) {
19871
+ return createPrimitiveWriteReport({
19872
+ command: spec.command,
19873
+ changedFiles: [],
19874
+ generatedFiles: [],
19875
+ validationCommands: [],
19876
+ diagnostics,
19877
+ nextActions: []
19878
+ });
19879
+ }
19880
+ const paths = moduleSourcePaths(spec.module);
19881
+ const servicePath = paths.service;
19882
+ const signalPath = paths.signal;
19883
+ const changedFiles = [];
19884
+ const generatedFiles2 = generatedFilesForSync(sys3);
19885
+ const [hasServiceFile, hasSignalFile] = await Promise.all([sys3.exists(servicePath), sys3.exists(signalPath)]);
19886
+ if (!hasServiceFile) {
19887
+ diagnostics.push({
19888
+ severity: "error",
19889
+ code: "primitive-source-missing",
19890
+ message: `Service source file was not found: ${servicePath}.`
19891
+ });
19892
+ }
19893
+ if (!hasSignalFile) {
19894
+ diagnostics.push({
19895
+ severity: "error",
19896
+ code: "primitive-source-missing",
19897
+ message: `Signal source file was not found: ${signalPath}.`
19898
+ });
19899
+ }
19900
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
19901
+ return createPrimitiveWriteReport({
19902
+ command: spec.command,
19903
+ changedFiles,
19904
+ generatedFiles: generatedFiles2,
19905
+ validationCommands: validationCommandsForTarget(sys3.name),
19906
+ diagnostics,
19907
+ nextActions: nextActionsForTarget(sys3.name)
19908
+ });
19909
+ }
19910
+ const serviceClassName = `${moduleComponentName(spec.module)}Service`;
19911
+ const serviceContent = await sys3.readFile(servicePath);
19912
+ const signalContent = await sys3.readFile(signalPath);
19913
+ const serviceMethodExists = hasClassMethod(serviceContent, serviceClassName, spec.serviceMethod.name);
19914
+ if (serviceMethodExists) {
19915
+ diagnostics.push({
19916
+ severity: "error",
19917
+ code: "primitive-service-method-exists",
19918
+ input: spec.requiredInput,
19919
+ message: `Method "${spec.serviceMethod.name}" already exists in ${serviceClassName}.`
19920
+ });
19921
+ }
19922
+ if (hasSignalFactoryEntry(signalContent, spec.signal.className, spec.entryName)) {
19923
+ diagnostics.push({
19924
+ severity: "error",
19925
+ code: "primitive-signal-entry-exists",
19926
+ input: spec.requiredInput,
19927
+ message: `Entry "${spec.entryName}" already exists in ${spec.signal.className}.`
19928
+ });
19929
+ }
19930
+ const nextServiceContent = serviceMethodExists ? serviceContent : insertClassMethod(serviceContent, serviceClassName, spec.serviceMethod.block);
19931
+ const nextSignalContent = insertSignalFactoryEntry(signalContent, spec.signal.className, spec.entryName, spec.signal.entryLine, spec.signal.param);
19932
+ if (!nextServiceContent) {
19933
+ diagnostics.push({
19934
+ severity: "error",
19935
+ code: "primitive-service-shape-unsupported",
19936
+ message: `Could not find ${serviceClassName} class body in ${servicePath}.`
19937
+ });
19938
+ }
19939
+ if (!nextSignalContent) {
19940
+ diagnostics.push({
19941
+ severity: "error",
19942
+ code: "primitive-signal-shape-unsupported",
19943
+ 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.`
19944
+ });
19945
+ }
19946
+ if (nextServiceContent && hasSourceParseErrors(nextServiceContent, "service.ts")) {
19947
+ diagnostics.push({
19948
+ severity: "error",
19949
+ code: "primitive-post-edit-service-parse-failed",
19950
+ failureScope: "source-change",
19951
+ message: `Edited ${servicePath} did not parse cleanly; refusing to write source.`
19952
+ });
19953
+ }
19954
+ if (nextSignalContent && hasSourceParseErrors(nextSignalContent, "signal.ts")) {
19955
+ diagnostics.push({
19956
+ severity: "error",
19957
+ code: "primitive-post-edit-signal-parse-failed",
19958
+ failureScope: "source-change",
19959
+ message: `Edited ${signalPath} did not parse cleanly; refusing to write source.`
19960
+ });
19961
+ }
19962
+ if (nextSignalContent && !hasSignalFactoryEntry(nextSignalContent, spec.signal.className, spec.entryName)) {
19963
+ diagnostics.push({
19964
+ severity: "error",
19965
+ code: "primitive-post-edit-signal-verify-failed",
19966
+ failureScope: "source-change",
19967
+ message: `Edited ${signalPath} did not contain entry "${spec.entryName}" in ${spec.signal.className}.`
19968
+ });
19969
+ }
19970
+ if (!diagnostics.some((diagnostic) => diagnostic.severity === "error") && nextServiceContent && nextSignalContent) {
19971
+ await sys3.writeFile(servicePath, nextServiceContent);
19972
+ await sys3.writeFile(signalPath, nextSignalContent);
19973
+ changedFiles.push(sourceFile(sys3, servicePath, "modify", `Service ${spec.entryKind} was added.`), sourceFile(sys3, signalPath, "modify", `Signal ${spec.entryKind} was added.`));
19974
+ }
19975
+ return createPrimitiveWriteReport({
19976
+ command: spec.command,
19977
+ changedFiles,
19978
+ generatedFiles: generatedFiles2,
19979
+ validationCommands: validationCommandsForTarget(sys3.name),
19980
+ diagnostics,
19981
+ nextActions: nextActionsForTarget(sys3.name)
19982
+ });
19983
+ }
19587
19984
  }
19588
19985
 
19589
19986
  // pkgs/@akanjs/cli/scalar/scalar.prompt.ts
@@ -19801,7 +20198,9 @@ var createCliWorkflowStepRegistry = (workspace) => createWorkflowStepRegistry({
19801
20198
  createScalar: (sys3, scalar) => CommandContainer.get(ScalarScript).createScalar(sys3, scalar),
19802
20199
  createUi: (input8) => CommandContainer.get(PrimitiveScript).createUi(workspace, input8),
19803
20200
  addField: (input8) => CommandContainer.get(PrimitiveScript).addField(workspace, input8),
19804
- addEnumField: (input8) => CommandContainer.get(PrimitiveScript).addEnumField(workspace, input8)
20201
+ addEnumField: (input8) => CommandContainer.get(PrimitiveScript).addEnumField(workspace, input8),
20202
+ addMutation: (input8) => CommandContainer.get(PrimitiveScript).addMutation(workspace, input8),
20203
+ addSlice: (input8) => CommandContainer.get(PrimitiveScript).addSlice(workspace, input8)
19805
20204
  });
19806
20205
 
19807
20206
  // pkgs/@akanjs/cli/context/context.runner.ts
@@ -20992,7 +21391,9 @@ class WorkflowScript extends script("workflow", [WorkflowRunner, ModuleScript, S
20992
21391
  createScalar: (sys3, scalar) => this.scalarScript.createScalar(sys3, scalar),
20993
21392
  createUi: (input8) => this.primitiveScript.createUi(workspace, input8),
20994
21393
  addField: (input8) => this.primitiveScript.addField(workspace, input8),
20995
- addEnumField: (input8) => this.primitiveScript.addEnumField(workspace, input8)
21394
+ addEnumField: (input8) => this.primitiveScript.addEnumField(workspace, input8),
21395
+ addMutation: (input8) => this.primitiveScript.addMutation(workspace, input8),
21396
+ addSlice: (input8) => this.primitiveScript.addSlice(workspace, input8)
20996
21397
  })
20997
21398
  }));
20998
21399
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/cli",
3
- "version": "2.3.11-rc.2",
3
+ "version": "2.3.11-rc.4",
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.2",
37
+ "akanjs": "2.3.11-rc.4",
38
38
  "chalk": "^5.6.2",
39
39
  "commander": "^14.0.3",
40
40
  "daisyui": "5.5.23",
@@ -118,4 +118,5 @@ libs/*/client.ts
118
118
  libs/*/server.ts
119
119
  libs/*/index.ts
120
120
  **/.akan
121
- **/bun.lock
121
+ **/bun.lock
122
+ **/tsconfig.tsbuildinfo