@akanjs/cli 2.3.11-rc.3 → 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";
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";
@@ -17997,8 +18165,18 @@ var addMutationWorkflowSpec = {
17997
18165
  }
17998
18166
  ],
17999
18167
  predictedChanges: [
18000
- { target: "*/lib/<module>/<module>.service.ts", action: "modify", reason: "Service mutation may be added." },
18001
- { 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
+ },
18002
18180
  { target: "*/lib/srv.ts", action: "sync", reason: "Generated service barrel may change after sync." },
18003
18181
  { target: "*/lib/sig.ts", action: "sync", reason: "Generated signal barrel may change after sync." }
18004
18182
  ],
@@ -18070,9 +18248,24 @@ var addSliceWorkflowSpec = {
18070
18248
  }
18071
18249
  ],
18072
18250
  predictedChanges: [
18073
- { target: "*/lib/<module>/<module>.service.ts", action: "modify", reason: "Query helper may be added." },
18074
- { target: "*/lib/<module>/<module>.signal.ts", action: "modify", reason: "Signal slice may be added." },
18075
- { 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
+ },
18076
18269
  { target: "*/lib/sig.ts", action: "sync", reason: "Generated signal barrel may change after sync." }
18077
18270
  ],
18078
18271
  validation: [
@@ -19384,6 +19577,12 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
19384
19577
  code: "primitive-field-type-unsupported",
19385
19578
  message: `Field type "${input7.type}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`,
19386
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"
19387
19586
  } : null
19388
19587
  ]);
19389
19588
  if (!sys3 || !input7.module || !input7.field || !input7.type || diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
@@ -19582,6 +19781,206 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
19582
19781
  nextActions: nextActionsForTarget(sys3.name)
19583
19782
  });
19584
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
+ }
19585
19984
  }
19586
19985
 
19587
19986
  // pkgs/@akanjs/cli/scalar/scalar.prompt.ts
@@ -19799,7 +20198,9 @@ var createCliWorkflowStepRegistry = (workspace) => createWorkflowStepRegistry({
19799
20198
  createScalar: (sys3, scalar) => CommandContainer.get(ScalarScript).createScalar(sys3, scalar),
19800
20199
  createUi: (input8) => CommandContainer.get(PrimitiveScript).createUi(workspace, input8),
19801
20200
  addField: (input8) => CommandContainer.get(PrimitiveScript).addField(workspace, input8),
19802
- 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)
19803
20204
  });
19804
20205
 
19805
20206
  // pkgs/@akanjs/cli/context/context.runner.ts
@@ -20990,7 +21391,9 @@ class WorkflowScript extends script("workflow", [WorkflowRunner, ModuleScript, S
20990
21391
  createScalar: (sys3, scalar) => this.scalarScript.createScalar(sys3, scalar),
20991
21392
  createUi: (input8) => this.primitiveScript.createUi(workspace, input8),
20992
21393
  addField: (input8) => this.primitiveScript.addField(workspace, input8),
20993
- 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)
20994
21397
  })
20995
21398
  }));
20996
21399
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/cli",
3
- "version": "2.3.11-rc.3",
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.3",
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