@akanjs/cli 2.4.0 → 2.4.1-rc.0

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.
Files changed (59) hide show
  1. package/agent.command-h4afc69n.js +26 -0
  2. package/application.command-4ctkfdan.js +165 -0
  3. package/applicationBuildRunner-esa1kj7v.js +284 -0
  4. package/applicationReleasePackager-brvth6rs.js +245 -0
  5. package/capacitorApp-y0h6cgft.js +58 -0
  6. package/cloud.command-rpztn4fh.js +94 -0
  7. package/commandManifest.json +1 -0
  8. package/context.command-nqbtak4f.js +82 -0
  9. package/dependencyScanner-m4x5maek.js +9 -0
  10. package/guideline.command-ya0dh44f.js +356 -0
  11. package/incrementalBuilder.proc.js +89 -17394
  12. package/index-1xdrsbry.js +1447 -0
  13. package/index-45aj5ry0.js +990 -0
  14. package/index-5vvwc0cz.js +559 -0
  15. package/index-61keag0s.js +40 -0
  16. package/index-6pz1j0zj.js +62 -0
  17. package/index-73pr2cmy.js +534 -0
  18. package/index-76rn3g2c.js +76 -0
  19. package/index-85msc0wg.js +161 -0
  20. package/index-8pkbzj26.js +840 -0
  21. package/index-8rc0bm04.js +514 -0
  22. package/index-9sp6fsc5.js +1884 -0
  23. package/index-a6sbyy0b.js +2769 -0
  24. package/index-b0brjbp3.js +83 -0
  25. package/index-fgc8r6dj.js +33 -0
  26. package/index-h6ca6qg0.js +2777 -0
  27. package/index-hdqztm58.js +758 -0
  28. package/index-hwzpw9c1.js +202 -0
  29. package/index-pmm9e2jf.js +120 -0
  30. package/index-qaq13qk3.js +80 -0
  31. package/index-qhtr07v8.js +1072 -0
  32. package/index-r24hmh0q.js +4 -0
  33. package/index-ss469dec.js +11 -0
  34. package/index-swf4bmbg.js +25 -0
  35. package/index-w7fyqjrw.js +462 -0
  36. package/index-wnp7hwq7.js +193 -0
  37. package/index-wq8jwx8z.js +224 -0
  38. package/index-x53a5nya.js +301 -0
  39. package/index-xmc2w32q.js +4359 -0
  40. package/index-y3hdhy4p.js +229 -0
  41. package/index.js +54 -23340
  42. package/library.command-r15zdqvp.js +33 -0
  43. package/localRegistry.command-5tcahs3f.js +178 -0
  44. package/module.command-qrj3kmyz.js +54 -0
  45. package/package.command-r8sq5kzp.js +43 -0
  46. package/package.json +2 -2
  47. package/page.command-c6xdx0xm.js +24 -0
  48. package/primitive.command-pv9ssmtf.js +62 -0
  49. package/quality.command-es67wvdp.js +809 -0
  50. package/repair.command-677675vw.js +67 -0
  51. package/routeSourceValidator-wbhmbwpj.js +132 -0
  52. package/scalar.command-kabkd6wd.js +35 -0
  53. package/templates/facetIndex/index.ts +1 -1
  54. package/typeChecker-kravn7ns.js +8 -0
  55. package/typecheck.proc.js +4 -194
  56. package/workflow.command-64r6cw0w.js +108 -0
  57. package/workspace.command-vrws0rgx.js +435 -0
  58. package/README.ko.md +0 -72
  59. package/README.md +0 -85
@@ -0,0 +1,534 @@
1
+ // @bun
2
+ import {
3
+ ModuleScript
4
+ } from "./index-8pkbzj26.js";
5
+ import {
6
+ addFieldUiPolicyForType,
7
+ coerceFieldDefault,
8
+ compactDiagnostics,
9
+ createPrimitiveWriteReport,
10
+ ensureBaseTypeImport,
11
+ ensureConstantTypeImport,
12
+ ensureEnumImport,
13
+ fieldExpression,
14
+ generatedFilesForSync,
15
+ hasClassMethod,
16
+ hasConstantInputField,
17
+ hasDictionaryModelField,
18
+ hasSignalFactoryEntry,
19
+ hasSourceParseErrors,
20
+ insertClassMethod,
21
+ insertDictionaryEnum,
22
+ insertDictionaryModelField,
23
+ insertEnumClass,
24
+ insertIntoObject,
25
+ insertLightProjectionField,
26
+ insertSignalFactoryEntry,
27
+ insertTemplateField,
28
+ lowerlize,
29
+ moduleComponentName,
30
+ moduleSourcePaths,
31
+ nextActionsForTarget,
32
+ normalizeFieldType,
33
+ parseValues,
34
+ sourceFile,
35
+ validationCommandsForTarget,
36
+ viaBuilderParameterName
37
+ } from "./index-h6ca6qg0.js";
38
+ import {
39
+ script
40
+ } from "./index-hdqztm58.js";
41
+ import {
42
+ AppExecutor,
43
+ LibExecutor,
44
+ ModuleExecutor
45
+ } from "./index-a6sbyy0b.js";
46
+
47
+ // pkgs/@akanjs/cli/primitive/primitive.script.ts
48
+ import { capitalize } from "akanjs/common";
49
+ class PrimitiveScript extends script("primitive", [ModuleScript]) {
50
+ async resolveSys(workspace, target) {
51
+ if (!target)
52
+ return null;
53
+ const [apps, libs] = await workspace.getSyss();
54
+ if (apps.includes(target))
55
+ return AppExecutor.from(workspace, target);
56
+ if (libs.includes(target))
57
+ return LibExecutor.from(workspace, target);
58
+ return null;
59
+ }
60
+ async createUi(workspace, input) {
61
+ const sys = await this.resolveSys(workspace, input.app);
62
+ if (!sys || !input.module) {
63
+ return createPrimitiveWriteReport({
64
+ command: "create-ui",
65
+ changedFiles: [],
66
+ generatedFiles: [],
67
+ validationCommands: [],
68
+ diagnostics: compactDiagnostics([
69
+ !sys && {
70
+ severity: "error",
71
+ code: "primitive-target-missing",
72
+ message: "Target app or library was not found."
73
+ },
74
+ !input.module && {
75
+ severity: "error",
76
+ code: "primitive-input-missing",
77
+ message: "Module is required.",
78
+ input: "module"
79
+ }
80
+ ]),
81
+ nextActions: []
82
+ });
83
+ }
84
+ const mod = ModuleExecutor.from(sys, input.module);
85
+ if (input.surface === "view")
86
+ return await this.moduleScript.createView(mod);
87
+ if (input.surface === "unit")
88
+ return await this.moduleScript.createUnit(mod);
89
+ return await this.moduleScript.createTemplate(mod);
90
+ }
91
+ async addField(workspace, input) {
92
+ return await this.addFieldToSources(workspace, input, { enumValues: null });
93
+ }
94
+ async addEnumField(workspace, input) {
95
+ const values = parseValues(input.values);
96
+ return await this.addFieldToSources(workspace, { ...input, type: `${capitalize(input.field ?? "")}` }, { enumValues: values });
97
+ }
98
+ async addFieldToSources(workspace, input, { enumValues }) {
99
+ const sys = await this.resolveSys(workspace, input.app);
100
+ const ambiguousNumberTypes = new Set(["number", "numeric"]);
101
+ const normalizedType = input.type ? normalizeFieldType(input.type) : null;
102
+ const diagnostics = compactDiagnostics([
103
+ !sys && { severity: "error", code: "primitive-target-missing", message: "Target app or library was not found." },
104
+ !input.module && {
105
+ severity: "error",
106
+ code: "primitive-input-missing",
107
+ message: "Module is required.",
108
+ input: "module"
109
+ },
110
+ !input.field && {
111
+ severity: "error",
112
+ code: "primitive-input-missing",
113
+ message: "Field is required.",
114
+ input: "field"
115
+ },
116
+ !input.type && {
117
+ severity: "error",
118
+ code: "primitive-input-missing",
119
+ message: "Type is required.",
120
+ input: "type"
121
+ },
122
+ enumValues && enumValues.length === 0 ? { severity: "error", code: "primitive-input-missing", message: "Enum values are required.", input: "values" } : null,
123
+ input.type && ambiguousNumberTypes.has(input.type.toLowerCase()) ? {
124
+ severity: "error",
125
+ code: "primitive-field-type-unsupported",
126
+ message: `Field type "${input.type}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`,
127
+ input: "type"
128
+ } : null,
129
+ input.type && input.type.toLowerCase() === "upload" ? {
130
+ severity: "error",
131
+ code: "primitive-field-type-upload-misuse",
132
+ 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.",
133
+ input: "type"
134
+ } : null
135
+ ]);
136
+ if (!sys || !input.module || !input.field || !input.type || diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
137
+ return createPrimitiveWriteReport({
138
+ command: enumValues ? "add-enum-field" : "add-field",
139
+ changedFiles: [],
140
+ generatedFiles: [],
141
+ validationCommands: [],
142
+ diagnostics,
143
+ nextActions: []
144
+ });
145
+ }
146
+ const moduleClassName = moduleComponentName(input.module);
147
+ const inputClassName = `${moduleClassName}Input`;
148
+ const paths = moduleSourcePaths(input.module);
149
+ const constantPath = paths.constant;
150
+ const dictionaryPath = paths.dictionary;
151
+ const templatePath = paths.template;
152
+ const changedFiles = [];
153
+ const generatedFiles = generatedFilesForSync(sys);
154
+ const [hasConstant, hasDictionary] = await Promise.all([sys.exists(constantPath), sys.exists(dictionaryPath)]);
155
+ if (!hasConstant) {
156
+ diagnostics.push({
157
+ severity: "error",
158
+ code: "primitive-source-missing",
159
+ message: `Constant source file was not found: ${constantPath}.`
160
+ });
161
+ }
162
+ if (!hasDictionary) {
163
+ diagnostics.push({
164
+ severity: "error",
165
+ code: "primitive-source-missing",
166
+ message: `Dictionary source file was not found: ${dictionaryPath}.`
167
+ });
168
+ }
169
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
170
+ return createPrimitiveWriteReport({
171
+ command: enumValues ? "add-enum-field" : "add-field",
172
+ changedFiles,
173
+ generatedFiles,
174
+ validationCommands: validationCommandsForTarget(sys.name),
175
+ diagnostics,
176
+ nextActions: nextActionsForTarget(sys.name)
177
+ });
178
+ }
179
+ let constantContent = await sys.readFile(constantPath);
180
+ let dictionaryContent = await sys.readFile(dictionaryPath);
181
+ const fieldBuilderName = viaBuilderParameterName(constantContent, inputClassName) ?? "field";
182
+ if (hasConstantInputField(constantContent, inputClassName, input.field)) {
183
+ diagnostics.push({
184
+ severity: "error",
185
+ code: "primitive-field-exists",
186
+ input: "field",
187
+ message: `Field "${input.field}" already exists in ${constantPath}.`
188
+ });
189
+ }
190
+ if (enumValues) {
191
+ const enumClassName = `${moduleClassName}${capitalize(input.field)}`;
192
+ const enumName = `${lowerlize(moduleClassName)}${capitalize(input.field)}`;
193
+ constantContent = insertEnumClass(ensureEnumImport(constantContent), enumClassName, enumName, enumValues);
194
+ dictionaryContent = ensureConstantTypeImport(dictionaryContent, `./${input.module}.constant`, enumClassName);
195
+ input.type = enumClassName;
196
+ const enumDictionary = insertDictionaryEnum(dictionaryContent, enumClassName, enumName, enumValues);
197
+ if (!enumDictionary) {
198
+ diagnostics.push({
199
+ severity: "error",
200
+ code: "primitive-dictionary-shape-unsupported",
201
+ message: `Could not find a safe enum insertion point in ${dictionaryPath}.`
202
+ });
203
+ } else {
204
+ dictionaryContent = enumDictionary;
205
+ }
206
+ }
207
+ if (!enumValues && normalizedType) {
208
+ input.type = normalizedType;
209
+ const defaultCoercion = coerceFieldDefault(input.type, input.defaultValue);
210
+ if (defaultCoercion.diagnostic)
211
+ diagnostics.push(defaultCoercion.diagnostic);
212
+ constantContent = ensureBaseTypeImport(constantContent, input.type);
213
+ }
214
+ if (enumValues) {
215
+ const defaultCoercion = coerceFieldDefault("enum", input.defaultValue, { enumValues });
216
+ if (defaultCoercion.diagnostic)
217
+ diagnostics.push(defaultCoercion.diagnostic);
218
+ }
219
+ const nextConstantContent = insertIntoObject(constantContent, inputClassName, `${input.field}: ${fieldExpression(input.type, input.defaultValue, { enumValues, builderName: fieldBuilderName })},`);
220
+ const nextConstantContentWithLight = nextConstantContent && input.includeInLight ? insertLightProjectionField(nextConstantContent, moduleClassName, input.field) : nextConstantContent;
221
+ const nextDictionaryContent = insertDictionaryModelField(dictionaryContent, moduleClassName, input.field);
222
+ if (nextConstantContentWithLight && (input.type === "Int" || input.type === "Float") && new RegExp(`\\b${input.field}\\s*:\\s*field\\(${input.type}, \\{ default: "`, "m").test(nextConstantContentWithLight)) {
223
+ diagnostics.push({
224
+ severity: "error",
225
+ code: "primitive-default-value-invalid",
226
+ input: "default",
227
+ failureScope: "source-change",
228
+ message: `Generated ${input.type} default for "${input.field}" would be a string literal; refusing to write source.`
229
+ });
230
+ }
231
+ if (nextConstantContentWithLight && (input.type === "Int" || input.type === "Float") && !new RegExp(`import \\{[^}]*\\b${input.type}\\b[^}]*\\} from "akanjs/base";`).test(nextConstantContentWithLight)) {
232
+ diagnostics.push({
233
+ severity: "error",
234
+ code: "primitive-base-type-import-missing",
235
+ failureScope: "source-change",
236
+ message: `Generated source for ${input.field} requires ${input.type} import from "akanjs/base".`
237
+ });
238
+ }
239
+ if (!nextConstantContent) {
240
+ diagnostics.push({
241
+ severity: "error",
242
+ code: "primitive-constant-shape-unsupported",
243
+ message: `Could not find ${inputClassName} object shape in ${constantPath}.`
244
+ });
245
+ }
246
+ if (nextConstantContent && input.includeInLight && !nextConstantContentWithLight) {
247
+ diagnostics.push({
248
+ severity: "warning",
249
+ code: "primitive-light-projection-shape-unsupported",
250
+ failureScope: "source-change",
251
+ message: `Could not find a safe Light${moduleClassName} projection insertion point in ${constantPath}.`
252
+ });
253
+ }
254
+ if (!nextDictionaryContent) {
255
+ diagnostics.push({
256
+ severity: "error",
257
+ code: "primitive-dictionary-shape-unsupported",
258
+ message: `Could not find ${moduleClassName} dictionary model shape in ${dictionaryPath}.`
259
+ });
260
+ }
261
+ if (nextConstantContentWithLight && !hasConstantInputField(nextConstantContentWithLight, inputClassName, input.field)) {
262
+ diagnostics.push({
263
+ severity: "error",
264
+ code: "primitive-post-edit-constant-verify-failed",
265
+ failureScope: "source-change",
266
+ message: `Edited ${constantPath} did not contain "${input.field}" in ${inputClassName}.`
267
+ });
268
+ }
269
+ if (nextDictionaryContent && !hasDictionaryModelField(nextDictionaryContent, moduleClassName, input.field)) {
270
+ diagnostics.push({
271
+ severity: "error",
272
+ code: "primitive-post-edit-dictionary-verify-failed",
273
+ failureScope: "source-change",
274
+ message: `Edited ${dictionaryPath} did not contain "${input.field}" in .model<${moduleClassName}>.`
275
+ });
276
+ }
277
+ let nextTemplateContent;
278
+ if (input.surfaces?.includes("template")) {
279
+ const policy = addFieldUiPolicyForType(enumValues ? "enum" : input.type);
280
+ const hasTemplate = await sys.exists(templatePath);
281
+ if (!hasTemplate) {
282
+ diagnostics.push({
283
+ severity: "warning",
284
+ code: "primitive-template-missing",
285
+ failureScope: "source-change",
286
+ message: `Template source file was not found: ${templatePath}. Auto-edit skipped; add the field to the Template form manually if this module renders one.`
287
+ });
288
+ } else if (!policy.autoTemplateSupported || policy.component === "Field.ToggleSelect") {
289
+ diagnostics.push({
290
+ severity: "warning",
291
+ code: "primitive-template-component-manual",
292
+ failureScope: "source-change",
293
+ message: `Template auto insertion for ${policy.component} is not supported because option binding must be confirmed. Candidate position: inside Layout.Template near existing Field components in ${templatePath}.`
294
+ });
295
+ } else {
296
+ const templateContent = await sys.readFile(templatePath);
297
+ nextTemplateContent = insertTemplateField({
298
+ content: templateContent,
299
+ moduleName: input.module,
300
+ moduleClassName,
301
+ fieldName: input.field,
302
+ component: policy.component
303
+ });
304
+ if (!nextTemplateContent) {
305
+ diagnostics.push({
306
+ severity: "warning",
307
+ code: "primitive-template-shape-unsupported",
308
+ failureScope: "source-change",
309
+ message: `Could not find a safe Template insertion point in ${templatePath}. Expected generated ${input.module}Form hook and Layout.Template closing tag; candidate position is the existing field list before </Layout.Template>.`
310
+ });
311
+ }
312
+ }
313
+ }
314
+ if (!diagnostics.some((diagnostic) => diagnostic.severity === "error") && nextConstantContentWithLight && nextDictionaryContent) {
315
+ await sys.writeFile(constantPath, nextConstantContentWithLight);
316
+ await sys.writeFile(dictionaryPath, nextDictionaryContent);
317
+ changedFiles.push(sourceFile(sys, constantPath, "modify", "Field source shape was updated."), sourceFile(sys, dictionaryPath, "modify", "Field dictionary labels were updated."));
318
+ if (nextTemplateContent !== undefined && nextTemplateContent !== null) {
319
+ await sys.writeFile(templatePath, nextTemplateContent);
320
+ changedFiles.push(sourceFile(sys, templatePath, "modify", "Template field surface was updated."));
321
+ }
322
+ }
323
+ return createPrimitiveWriteReport({
324
+ command: enumValues ? "add-enum-field" : "add-field",
325
+ changedFiles,
326
+ generatedFiles,
327
+ validationCommands: validationCommandsForTarget(sys.name),
328
+ diagnostics,
329
+ nextActions: nextActionsForTarget(sys.name)
330
+ });
331
+ }
332
+ async addMutation(workspace, input) {
333
+ const moduleClassName = input.module ? moduleComponentName(input.module) : "";
334
+ const serviceRef = lowerlize(moduleClassName);
335
+ const name = input.mutation;
336
+ return await this.#writeServiceSignalEntry(workspace, {
337
+ command: "add-mutation",
338
+ app: input.app,
339
+ module: input.module,
340
+ entryKind: "mutation",
341
+ entryName: name,
342
+ requiredInput: "mutation",
343
+ serviceMethod: name ? {
344
+ name,
345
+ block: [
346
+ ` async ${name}() {`,
347
+ ` // TODO(service): implement ${moduleClassName}Service.${name}`,
348
+ ` return true;`,
349
+ ` }`
350
+ ].join(`
351
+ `)
352
+ } : null,
353
+ signal: name ? {
354
+ className: `${moduleClassName}Endpoint`,
355
+ entryLine: [
356
+ `${name}: mutation(Boolean)`,
357
+ ` .exec(async function () {`,
358
+ ` return await this.${serviceRef}Service.${name}();`,
359
+ ` }),`
360
+ ].join(`
361
+ `),
362
+ param: { mode: "destructure", name: "mutation" }
363
+ } : null
364
+ });
365
+ }
366
+ async addSlice(workspace, input) {
367
+ const moduleClassName = input.module ? moduleComponentName(input.module) : "";
368
+ const serviceRef = lowerlize(moduleClassName);
369
+ const name = input.slice;
370
+ const queryName = name ? `query${capitalize(name)}` : null;
371
+ return await this.#writeServiceSignalEntry(workspace, {
372
+ command: "add-slice",
373
+ app: input.app,
374
+ module: input.module,
375
+ entryKind: "slice",
376
+ entryName: name,
377
+ requiredInput: "slice",
378
+ serviceMethod: name && queryName ? {
379
+ name: queryName,
380
+ block: [
381
+ ` ${queryName}() {`,
382
+ ` // TODO(service): return a QueryOf for the ${name} slice`,
383
+ ` return {};`,
384
+ ` }`
385
+ ].join(`
386
+ `)
387
+ } : null,
388
+ signal: name && queryName ? {
389
+ className: `${moduleClassName}Slice`,
390
+ entryLine: [
391
+ `${name}: init()`,
392
+ ` .exec(function () {`,
393
+ ` return this.${serviceRef}Service.${queryName}();`,
394
+ ` }),`
395
+ ].join(`
396
+ `),
397
+ param: { mode: "positional", name: "init" }
398
+ } : null
399
+ });
400
+ }
401
+ async#writeServiceSignalEntry(workspace, spec) {
402
+ const sys = await this.resolveSys(workspace, spec.app);
403
+ const diagnostics = compactDiagnostics([
404
+ !sys && { severity: "error", code: "primitive-target-missing", message: "Target app or library was not found." },
405
+ !spec.module && {
406
+ severity: "error",
407
+ code: "primitive-input-missing",
408
+ message: "Module is required.",
409
+ input: "module"
410
+ },
411
+ !spec.entryName && {
412
+ severity: "error",
413
+ code: "primitive-input-missing",
414
+ message: `${capitalize(spec.requiredInput)} name is required.`,
415
+ input: spec.requiredInput
416
+ }
417
+ ]);
418
+ if (!sys || !spec.module || !spec.entryName || !spec.serviceMethod || !spec.signal) {
419
+ return createPrimitiveWriteReport({
420
+ command: spec.command,
421
+ changedFiles: [],
422
+ generatedFiles: [],
423
+ validationCommands: [],
424
+ diagnostics,
425
+ nextActions: []
426
+ });
427
+ }
428
+ const paths = moduleSourcePaths(spec.module);
429
+ const servicePath = paths.service;
430
+ const signalPath = paths.signal;
431
+ const changedFiles = [];
432
+ const generatedFiles = generatedFilesForSync(sys);
433
+ const [hasServiceFile, hasSignalFile] = await Promise.all([sys.exists(servicePath), sys.exists(signalPath)]);
434
+ if (!hasServiceFile) {
435
+ diagnostics.push({
436
+ severity: "error",
437
+ code: "primitive-source-missing",
438
+ message: `Service source file was not found: ${servicePath}.`
439
+ });
440
+ }
441
+ if (!hasSignalFile) {
442
+ diagnostics.push({
443
+ severity: "error",
444
+ code: "primitive-source-missing",
445
+ message: `Signal source file was not found: ${signalPath}.`
446
+ });
447
+ }
448
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
449
+ return createPrimitiveWriteReport({
450
+ command: spec.command,
451
+ changedFiles,
452
+ generatedFiles,
453
+ validationCommands: validationCommandsForTarget(sys.name),
454
+ diagnostics,
455
+ nextActions: nextActionsForTarget(sys.name)
456
+ });
457
+ }
458
+ const serviceClassName = `${moduleComponentName(spec.module)}Service`;
459
+ const serviceContent = await sys.readFile(servicePath);
460
+ const signalContent = await sys.readFile(signalPath);
461
+ const serviceMethodExists = hasClassMethod(serviceContent, serviceClassName, spec.serviceMethod.name);
462
+ if (serviceMethodExists) {
463
+ diagnostics.push({
464
+ severity: "error",
465
+ code: "primitive-service-method-exists",
466
+ input: spec.requiredInput,
467
+ message: `Method "${spec.serviceMethod.name}" already exists in ${serviceClassName}.`
468
+ });
469
+ }
470
+ if (hasSignalFactoryEntry(signalContent, spec.signal.className, spec.entryName)) {
471
+ diagnostics.push({
472
+ severity: "error",
473
+ code: "primitive-signal-entry-exists",
474
+ input: spec.requiredInput,
475
+ message: `Entry "${spec.entryName}" already exists in ${spec.signal.className}.`
476
+ });
477
+ }
478
+ const nextServiceContent = serviceMethodExists ? serviceContent : insertClassMethod(serviceContent, serviceClassName, spec.serviceMethod.block);
479
+ const nextSignalContent = insertSignalFactoryEntry(signalContent, spec.signal.className, spec.entryName, spec.signal.entryLine, spec.signal.param);
480
+ if (!nextServiceContent) {
481
+ diagnostics.push({
482
+ severity: "error",
483
+ code: "primitive-service-shape-unsupported",
484
+ message: `Could not find ${serviceClassName} class body in ${servicePath}.`
485
+ });
486
+ }
487
+ if (!nextSignalContent) {
488
+ diagnostics.push({
489
+ severity: "error",
490
+ code: "primitive-signal-shape-unsupported",
491
+ 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.`
492
+ });
493
+ }
494
+ if (nextServiceContent && hasSourceParseErrors(nextServiceContent, "service.ts")) {
495
+ diagnostics.push({
496
+ severity: "error",
497
+ code: "primitive-post-edit-service-parse-failed",
498
+ failureScope: "source-change",
499
+ message: `Edited ${servicePath} did not parse cleanly; refusing to write source.`
500
+ });
501
+ }
502
+ if (nextSignalContent && hasSourceParseErrors(nextSignalContent, "signal.ts")) {
503
+ diagnostics.push({
504
+ severity: "error",
505
+ code: "primitive-post-edit-signal-parse-failed",
506
+ failureScope: "source-change",
507
+ message: `Edited ${signalPath} did not parse cleanly; refusing to write source.`
508
+ });
509
+ }
510
+ if (nextSignalContent && !hasSignalFactoryEntry(nextSignalContent, spec.signal.className, spec.entryName)) {
511
+ diagnostics.push({
512
+ severity: "error",
513
+ code: "primitive-post-edit-signal-verify-failed",
514
+ failureScope: "source-change",
515
+ message: `Edited ${signalPath} did not contain entry "${spec.entryName}" in ${spec.signal.className}.`
516
+ });
517
+ }
518
+ if (!diagnostics.some((diagnostic) => diagnostic.severity === "error") && nextServiceContent && nextSignalContent) {
519
+ await sys.writeFile(servicePath, nextServiceContent);
520
+ await sys.writeFile(signalPath, nextSignalContent);
521
+ changedFiles.push(sourceFile(sys, servicePath, "modify", `Service ${spec.entryKind} was added.`), sourceFile(sys, signalPath, "modify", `Signal ${spec.entryKind} was added.`));
522
+ }
523
+ return createPrimitiveWriteReport({
524
+ command: spec.command,
525
+ changedFiles,
526
+ generatedFiles,
527
+ validationCommands: validationCommandsForTarget(sys.name),
528
+ diagnostics,
529
+ nextActions: nextActionsForTarget(sys.name)
530
+ });
531
+ }
532
+ }
533
+
534
+ export { PrimitiveScript };
@@ -0,0 +1,76 @@
1
+ // @bun
2
+ // pkgs/@akanjs/devkit/mobile/mobileTarget.ts
3
+ var getMobileTargets = async (app) => {
4
+ const config = await app.getConfig();
5
+ return Object.entries(config.mobile.targets).map(([name, target]) => ({ name, config: target }));
6
+ };
7
+ var getMobileTargetChoices = async (app) => {
8
+ const config = await app.getConfig();
9
+ const targetNames = Object.keys(config.mobile.targets);
10
+ if (targetNames.length > 0)
11
+ return targetNames;
12
+ const basePaths = [...config.basePaths];
13
+ return basePaths;
14
+ };
15
+ var resolveMobileTargetByBasePath = (targets, basePath) => {
16
+ const normalizedBasePath = basePath.replace(/^\/+|\/+$/g, "");
17
+ const byBasePath = targets.find((target) => target.config.basePath?.replace(/^\/+|\/+$/g, "") === normalizedBasePath);
18
+ if (byBasePath)
19
+ return byBasePath;
20
+ const [template] = targets;
21
+ if (!template)
22
+ return;
23
+ return {
24
+ name: normalizedBasePath,
25
+ config: {
26
+ ...template.config,
27
+ name: normalizedBasePath,
28
+ basePath: normalizedBasePath
29
+ }
30
+ };
31
+ };
32
+ var resolveMobileTargets = async (app, selection) => {
33
+ const config = await app.getConfig();
34
+ const targets = await getMobileTargets(app);
35
+ if (targets.length === 0)
36
+ throw new Error(`No mobile targets configured for ${app.name}`);
37
+ if (!selection && targets.length === 1)
38
+ return targets;
39
+ if (!selection) {
40
+ const choices2 = await getMobileTargetChoices(app);
41
+ if (choices2.length === 1)
42
+ return resolveMobileTargets(app, choices2[0]);
43
+ throw new Error(`Multiple mobile targets found for ${app.name}. Pass --target <${choices2.join("|")}|all>.`);
44
+ }
45
+ if (selection === "all") {
46
+ if (Object.keys(config.mobile.targets).length > 1)
47
+ return targets;
48
+ const basePaths = [...config.basePaths];
49
+ if (basePaths.length > 1) {
50
+ return basePaths.flatMap((basePath) => {
51
+ const resolved = resolveMobileTargetByBasePath(targets, basePath);
52
+ return resolved ? [resolved] : [];
53
+ });
54
+ }
55
+ return targets;
56
+ }
57
+ const target = targets.find((candidate) => candidate.name === selection);
58
+ if (target)
59
+ return [target];
60
+ const basePathTarget = resolveMobileTargetByBasePath(targets, selection);
61
+ if (basePathTarget && config.basePaths.has(selection.replace(/^\/+|\/+$/g, "")))
62
+ return [basePathTarget];
63
+ const choices = await getMobileTargetChoices(app);
64
+ throw new Error(`Mobile target '${selection}' was not found. Available: ${choices.join(", ")}`);
65
+ };
66
+ var resolveMobilePath = (target, pathname) => {
67
+ const basePath = target.basePath?.replace(/^\/+|\/+$/g, "");
68
+ const normalizedPath = `/${pathname.replace(/^\/+/, "")}`;
69
+ if (!basePath)
70
+ return normalizedPath;
71
+ if (normalizedPath === `/${basePath}` || normalizedPath.startsWith(`/${basePath}/`))
72
+ return normalizedPath;
73
+ return `/${basePath}${normalizedPath === "/" ? "" : normalizedPath}`;
74
+ };
75
+ var targetHtmlFilename = (target) => target.basePath?.replace(/^\/+|\/+$/g, "") ? `${target.basePath.replace(/^\/+|\/+$/g, "")}.html` : "index.html";
76
+ export { getMobileTargets, getMobileTargetChoices, resolveMobileTargets, resolveMobilePath, targetHtmlFilename };