@jskit-ai/jskit-cli 0.2.74 → 0.2.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/jskit-cli",
3
- "version": "0.2.74",
3
+ "version": "0.2.76",
4
4
  "description": "Bundle and package orchestration CLI for JSKIT apps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -20,9 +20,9 @@
20
20
  "test": "node --test"
21
21
  },
22
22
  "dependencies": {
23
- "@jskit-ai/jskit-catalog": "0.1.73",
24
- "@jskit-ai/kernel": "0.1.65",
25
- "@jskit-ai/shell-web": "0.1.64"
23
+ "@jskit-ai/jskit-catalog": "0.1.75",
24
+ "@jskit-ai/kernel": "0.1.67",
25
+ "@jskit-ai/shell-web": "0.1.66"
26
26
  },
27
27
  "engines": {
28
28
  "node": "20.x"
@@ -2,6 +2,10 @@ import path from "node:path";
2
2
  import { pathToFileURL } from "node:url";
3
3
  import { access, readdir, readFile } from "node:fs/promises";
4
4
  import { buildCrudFieldContractMap } from "@jskit-ai/kernel/shared/support/crudFieldContract";
5
+ import {
6
+ discoverPlacementTopologyFromApp,
7
+ discoverShellOutletTargetsFromApp
8
+ } from "@jskit-ai/kernel/server/support";
5
9
  import {
6
10
  buildAppCommandOptionMeta,
7
11
  listAppCommandDefinitions
@@ -370,6 +374,37 @@ async function discoverPlacementTargets(appRoot) {
370
374
  return uniqueSorted(placementValues);
371
375
  }
372
376
 
377
+ async function discoverSemanticPlacementTargets(appRoot) {
378
+ try {
379
+ const topology = await discoverPlacementTopologyFromApp({ appRoot });
380
+ return uniqueSorted(
381
+ (Array.isArray(topology?.placements) ? topology.placements : [])
382
+ .map((placement) => normalizeText(placement?.id))
383
+ .filter(Boolean)
384
+ );
385
+ } catch {
386
+ const topologyPath = path.join(appRoot, "src", "placementTopology.js");
387
+ if (!(await pathExists(topologyPath))) {
388
+ return [];
389
+ }
390
+ const source = await readFile(topologyPath, "utf8");
391
+ return uniqueSorted(extractMatches(source, [/\bid\s*:\s*["']([^"':]+(?:\.[^"':]+)+)["']/g]));
392
+ }
393
+ }
394
+
395
+ async function discoverConcretePlacementTargets(appRoot) {
396
+ try {
397
+ const discovered = await discoverShellOutletTargetsFromApp({ appRoot });
398
+ return uniqueSorted(
399
+ (Array.isArray(discovered?.targets) ? discovered.targets : [])
400
+ .map((target) => normalizeText(target?.id || target?.target))
401
+ .filter(Boolean)
402
+ );
403
+ } catch {
404
+ return discoverPlacementTargets(appRoot);
405
+ }
406
+ }
407
+
373
408
  async function discoverComponentTokens(appRoot) {
374
409
  const tokens = [];
375
410
  for (const filePath of [
@@ -391,7 +426,6 @@ async function discoverComponentTokens(appRoot) {
391
426
  const source = await readFile(filePath, "utf8");
392
427
  tokens.push(...extractMatches(source, [
393
428
  /\bcomponentToken\s*:\s*["']([^"']+)["']/g,
394
- /\bdefault-link-component-token\s*=\s*["']([^"']+)["']/g,
395
429
  /registerMainClientComponent\(\s*["']([^"']+)["']/g
396
430
  ]));
397
431
  }
@@ -590,9 +624,11 @@ async function completeOptionValue({
590
624
 
591
625
  if (normalizedOptionName === "resource-file") {
592
626
  suggestions = await discoverResourceFiles(appRoot);
593
- } else if (["link-placement", "placement", "target"].includes(normalizedOptionName)) {
594
- suggestions = await discoverPlacementTargets(appRoot);
595
- } else if (normalizedOptionName === "link-component-token") {
627
+ } else if (["link-placement", "placement"].includes(normalizedOptionName)) {
628
+ suggestions = await discoverSemanticPlacementTargets(appRoot);
629
+ } else if (normalizedOptionName === "target") {
630
+ suggestions = await discoverConcretePlacementTargets(appRoot);
631
+ } else if (normalizedOptionName === "link-renderer") {
596
632
  suggestions = await discoverComponentTokens(appRoot);
597
633
  } else if (normalizedOptionName === "surface") {
598
634
  suggestions = await discoverSurfaces(appRoot);
@@ -113,6 +113,13 @@ async function applyTextMutations(
113
113
  const previous = await readFileBufferIfExists(absoluteFile);
114
114
  const previousContent = previous.exists ? previous.buffer.toString("utf8") : "";
115
115
  const mutationId = String(mutation?.id || "").trim() || "append-text";
116
+ const interpolatedSkipChecks = normalizeSkipChecks(mutation?.skipIfContains)
117
+ .map((entry) => interpolateOptionValue(entry, options, packageEntry.packageId, `${mutationId}.skipIfContains`))
118
+ .filter((entry) => String(entry || "").trim().length > 0);
119
+ if (interpolatedSkipChecks.some((pattern) => previousContent.includes(String(pattern)))) {
120
+ continue;
121
+ }
122
+
116
123
  const resolvedSnippet = interpolateOptionValue(snippet, options, packageEntry.packageId, mutationId);
117
124
  const templateContextReplacements = await resolveTemplateContextReplacementsForMutation({
118
125
  packageEntry,
@@ -126,8 +133,7 @@ async function applyTextMutations(
126
133
  const renderedSnippet = templateContextReplacements
127
134
  ? applyTemplateContextReplacements(resolvedSnippet, templateContextReplacements)
128
135
  : resolvedSnippet;
129
- const skipChecks = normalizeSkipChecks(mutation?.skipIfContains)
130
- .map((entry) => interpolateOptionValue(entry, options, packageEntry.packageId, `${mutationId}.skipIfContains`))
136
+ const skipChecks = interpolatedSkipChecks
131
137
  .map((entry) => {
132
138
  if (!templateContextReplacements) {
133
139
  return entry;
@@ -3,7 +3,11 @@ import {
3
3
  ensureObject
4
4
  } from "../../shared/collectionUtils.js";
5
5
  import {
6
- normalizeShellOutletTargetId
6
+ normalizePlacementKind,
7
+ normalizePlacementOwnerId,
8
+ normalizePlacementTopologyDefinition,
9
+ normalizeShellOutletTargetId,
10
+ resolvePlacementTargetReference
7
11
  } from "@jskit-ai/kernel/shared/support/shellLayoutTargets";
8
12
 
9
13
  function normalizePlacementOutlets(value) {
@@ -41,22 +45,27 @@ function normalizePlacementContributions(value) {
41
45
  for (const entry of ensureArray(value)) {
42
46
  const record = ensureObject(entry);
43
47
  const id = String(record.id || "").trim();
44
- const target = normalizeShellOutletTargetId(record.target);
45
- if (!id || !target) {
48
+ const targetReference = resolvePlacementTargetReference(record.target);
49
+ if (!id || !targetReference?.id) {
46
50
  continue;
47
51
  }
48
52
 
49
53
  const surfaces = [...new Set(ensureArray(record.surfaces).map((item) => String(item || "").trim()).filter(Boolean))];
54
+ const kind = normalizePlacementKind(record.kind) || (String(record.componentToken || "").trim() ? "component" : "link");
50
55
  const componentToken = String(record.componentToken || "").trim();
51
56
  const when = String(record.when || "").trim();
52
57
  const description = String(record.description || "").trim();
53
58
  const source = String(record.source || "").trim();
59
+ const owner = normalizePlacementOwnerId(record.owner);
54
60
  const parsedOrder = Number(record.order);
55
61
  const order = Number.isFinite(parsedOrder) ? Math.trunc(parsedOrder) : null;
56
62
  contributions.push(
57
63
  Object.freeze({
58
64
  id,
59
- target,
65
+ target: targetReference.id,
66
+ targetType: targetReference.type,
67
+ owner,
68
+ kind,
60
69
  surfaces: Object.freeze(surfaces),
61
70
  order,
62
71
  componentToken,
@@ -83,7 +92,12 @@ function normalizePlacementContributions(value) {
83
92
  );
84
93
  }
85
94
 
95
+ function normalizePlacementTopology(value, { context = "package placement topology" } = {}) {
96
+ return normalizePlacementTopologyDefinition(value, { context }).placements;
97
+ }
98
+
86
99
  export {
87
100
  normalizePlacementContributions,
88
- normalizePlacementOutlets
101
+ normalizePlacementOutlets,
102
+ normalizePlacementTopology
89
103
  };
@@ -1825,8 +1825,12 @@ function createHealthCommands(ctx = {}) {
1825
1825
  return false;
1826
1826
  }
1827
1827
 
1828
- function isSharedListFiltersImportSource(sourcePath = "") {
1829
- return /(^|\/)shared\/[^/'"]*ListFilters(?:\.[A-Za-z0-9]+)?$/u.test(String(sourcePath || "").trim());
1828
+ function isListFiltersImportSource(sourcePath = "") {
1829
+ const normalizedSource = String(sourcePath || "").trim();
1830
+ return (
1831
+ /(^|\/)shared\/[^/'"]*ListFilters(?:\.[A-Za-z0-9]+)?$/u.test(normalizedSource) ||
1832
+ /^\.\/listFilters\.[A-Za-z0-9]+$/u.test(normalizedSource)
1833
+ );
1830
1834
  }
1831
1835
 
1832
1836
  function findCallSites(sourceText = "", calleeName = "") {
@@ -1872,22 +1876,22 @@ function createHealthCommands(ctx = {}) {
1872
1876
 
1873
1877
  if (!firstArgument || firstArgument.startsWith("{")) {
1874
1878
  issues.push(
1875
- `${relativePath}:${lineNumber}: [filters:shared-definition] do not inline structured filter definitions in ${calleeName}(...). Put them in packages/<crud>/src/shared/<crud>ListFilters.js and import that shared module.`
1879
+ `${relativePath}:${lineNumber}: [filters:shared-definition] do not inline structured filter definitions in ${calleeName}(...). Put them in listFilters.js or packages/<crud>/src/shared/<crud>ListFilters.js and import that module.`
1876
1880
  );
1877
1881
  continue;
1878
1882
  }
1879
1883
 
1880
1884
  if (!/^[A-Za-z_$][\w$]*$/u.test(firstArgument)) {
1881
1885
  issues.push(
1882
- `${relativePath}:${lineNumber}: [filters:shared-definition] ${calleeName}(...) must receive a definitions symbol imported from a CRUD shared *ListFilters module, not an ad-hoc expression.`
1886
+ `${relativePath}:${lineNumber}: [filters:shared-definition] ${calleeName}(...) must receive a definitions symbol imported from listFilters.js or a CRUD shared *ListFilters module, not an ad-hoc expression.`
1883
1887
  );
1884
1888
  continue;
1885
1889
  }
1886
1890
 
1887
1891
  const importSource = importBindings.get(firstArgument) || "";
1888
- if (!isSharedListFiltersImportSource(importSource)) {
1892
+ if (!isListFiltersImportSource(importSource)) {
1889
1893
  issues.push(
1890
- `${relativePath}:${lineNumber}: [filters:shared-definition] ${calleeName}(${firstArgument}, ...) must use definitions imported from a CRUD shared *ListFilters module. Found ${importSource ? `import source "${importSource}"` : "a local symbol"} instead.`
1894
+ `${relativePath}:${lineNumber}: [filters:shared-definition] ${calleeName}(${firstArgument}, ...) must use definitions imported from listFilters.js or a CRUD shared *ListFilters module. Found ${importSource ? `import source "${importSource}"` : "a local symbol"} instead.`
1891
1895
  );
1892
1896
  }
1893
1897
  }