@jskit-ai/ui-generator 0.1.15 → 0.1.16

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.
@@ -1,7 +1,7 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/ui-generator",
4
- version: "0.1.15",
4
+ version: "0.1.16",
5
5
  kind: "generator",
6
6
  description: "Create non-CRUD pages, reusable UI elements, and subpage hosts.",
7
7
  options: {
@@ -274,7 +274,7 @@ export default Object.freeze({
274
274
  mutations: {
275
275
  dependencies: {
276
276
  runtime: {
277
- "@jskit-ai/users-web": "0.1.47"
277
+ "@jskit-ai/users-web": "0.1.48"
278
278
  },
279
279
  dev: {}
280
280
  },
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@jskit-ai/ui-generator",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
7
7
  },
8
8
  "dependencies": {
9
- "@jskit-ai/kernel": "0.1.32"
9
+ "@jskit-ai/kernel": "0.1.33"
10
10
  },
11
11
  "exports": {
12
12
  "./server/buildTemplateContext": "./src/server/buildTemplateContext.js"
@@ -141,6 +141,11 @@ async function runGeneratorSubcommand({
141
141
  const targetFilePath = resolvePathWithinApp(appRoot, targetFile, {
142
142
  context: "ui-generator outlet"
143
143
  });
144
+ if (!targetFilePath.relativePath.toLowerCase().endsWith(".vue")) {
145
+ throw new Error(
146
+ `ui-generator outlet target file must be an existing Vue SFC (.vue): ${targetFilePath.relativePath}.`
147
+ );
148
+ }
144
149
 
145
150
  let source = "";
146
151
  try {
@@ -11,7 +11,7 @@ import {
11
11
  } from "./support.js";
12
12
  import {
13
13
  resolvePageTargetDetails,
14
- renderPlainPageSource,
14
+ renderPlainPageSource
15
15
  } from "./pageSupport.js";
16
16
 
17
17
  function renderPageLinkPlacementBlock({
@@ -86,14 +86,6 @@ async function runGeneratorSubcommand({
86
86
  );
87
87
  }
88
88
 
89
- if (!pageAlreadyExisted || forceOverwrite) {
90
- if (dryRun !== true) {
91
- await mkdir(path.dirname(pageFilePath), { recursive: true });
92
- await writeFile(pageFilePath, renderPlainPageSource(pageLabel), "utf8");
93
- }
94
- touchedFiles.add(pageRelativePath);
95
- }
96
-
97
89
  const placementContext = await buildUiPageTemplateContext({
98
90
  appRoot: pageTarget.appRoot,
99
91
  targetFile,
@@ -114,6 +106,15 @@ async function runGeneratorSubcommand({
114
106
  surface: pageTarget.surfaceId
115
107
  })
116
108
  );
109
+
110
+ if (!pageAlreadyExisted || forceOverwrite) {
111
+ if (dryRun !== true) {
112
+ await mkdir(path.dirname(pageFilePath), { recursive: true });
113
+ await writeFile(pageFilePath, renderPlainPageSource(pageLabel), "utf8");
114
+ }
115
+ touchedFiles.add(pageRelativePath);
116
+ }
117
+
117
118
  if (placementApplied.changed) {
118
119
  if (dryRun !== true) {
119
120
  await writeFile(placementPath.absolutePath, placementApplied.content, "utf8");
@@ -122,20 +123,11 @@ async function runGeneratorSubcommand({
122
123
  }
123
124
 
124
125
  const touchedFileList = [...touchedFiles].sort((left, right) => left.localeCompare(right));
125
- const summaryParts = [];
126
- if (!pageAlreadyExisted) {
127
- summaryParts.push(`Generated UI page "${pageTarget.routeUrlSuffix}" at ${pageRelativePath}.`);
128
- } else if (forceOverwrite) {
129
- summaryParts.push(`Regenerated UI page "${pageTarget.routeUrlSuffix}" at ${pageRelativePath}.`);
130
- } else if (placementApplied.changed) {
131
- summaryParts.push(`Updated page link placement for existing UI page "${pageTarget.routeUrlSuffix}".`);
132
- } else {
133
- summaryParts.push(`UI page "${pageTarget.routeUrlSuffix}" is already up to date.`);
134
- }
135
-
136
126
  return {
137
127
  touchedFiles: touchedFileList,
138
- summary: summaryParts.join(" ")
128
+ summary: !pageAlreadyExisted
129
+ ? `Generated UI page "${pageTarget.routeUrlSuffix}" at ${pageRelativePath}.`
130
+ : `Regenerated UI page "${pageTarget.routeUrlSuffix}" at ${pageRelativePath}.`
139
131
  };
140
132
  }
141
133
 
@@ -259,6 +259,32 @@ test("ui-generator outlet supports explicit target host:position", async () => {
259
259
  });
260
260
  });
261
261
 
262
+ test("ui-generator outlet rejects non-vue target files without changing them", async () => {
263
+ await withTempApp(async (appRoot) => {
264
+ const targetFile = "src/pages/w/[workspaceSlug]/admin/practice/vets/_components/VetAddEditFormFields.js";
265
+ const targetPath = path.join(appRoot, targetFile);
266
+ const originalSource = "export const fields = [];\n";
267
+
268
+ await mkdir(path.dirname(targetPath), { recursive: true });
269
+ await writeFile(targetPath, originalSource, "utf8");
270
+
271
+ await assert.rejects(
272
+ runGeneratorSubcommand({
273
+ appRoot,
274
+ subcommand: "outlet",
275
+ args: [targetFile],
276
+ options: {
277
+ target: "vet-view"
278
+ }
279
+ }),
280
+ /ui-generator outlet target file must be an existing Vue SFC \(\.vue\): src\/pages\/w\/\[workspaceSlug\]\/admin\/practice\/vets\/_components\/VetAddEditFormFields\.js\./
281
+ );
282
+
283
+ const output = await readFile(targetPath, "utf8");
284
+ assert.equal(output, originalSource);
285
+ });
286
+ });
287
+
262
288
  test("ui-generator outlet validates target format", async () => {
263
289
  await withTempApp(async (appRoot) => {
264
290
  const targetFile = "src/components/ContactDetailsPanel.vue";
@@ -73,6 +73,7 @@ test("ui-generator page subcommand creates an index page from an explicit target
73
73
  });
74
74
 
75
75
  assert.deepEqual(result.touchedFiles, [toPagePath(targetFile), "src/placement.js"]);
76
+ assert.equal(result.summary, 'Generated UI page "/practice" at src/pages/w/[workspaceSlug]/admin/practice/index.vue.');
76
77
 
77
78
  const pageSource = await readFile(path.join(appRoot, toPagePath(targetFile)), "utf8");
78
79
  assert.match(pageSource, /<h1 class="text-h5 mb-2">Practice<\/h1>/);
@@ -350,3 +351,65 @@ test("ui-generator page subcommand overwrites an existing page when --force is p
350
351
  assert.doesNotMatch(pageSource, /custom practice page/);
351
352
  });
352
353
  });
354
+
355
+ test("ui-generator page subcommand rejects invalid link placement before creating a new page", async () => {
356
+ await withTempApp(async (appRoot) => {
357
+ await writeAppFixture(appRoot);
358
+
359
+ const targetFile = "w/[workspaceSlug]/admin/practice/index.vue";
360
+ const placementPath = path.join(appRoot, "src", "placement.js");
361
+ const originalPlacementSource = await readFile(placementPath, "utf8");
362
+
363
+ await assert.rejects(
364
+ runGeneratorSubcommand({
365
+ appRoot,
366
+ subcommand: "page",
367
+ args: [targetFile],
368
+ options: {
369
+ "link-placement": "missing:target"
370
+ }
371
+ }),
372
+ /ui-generator page option "placement" target "missing:target" is not declared/
373
+ );
374
+
375
+ await assert.rejects(readFile(path.join(appRoot, toPagePath(targetFile)), "utf8"), /ENOENT/);
376
+ const placementSource = await readFile(placementPath, "utf8");
377
+ assert.equal(placementSource, originalPlacementSource);
378
+ });
379
+ });
380
+
381
+ test("ui-generator page subcommand rejects invalid link placement before overwriting an existing page", async () => {
382
+ await withTempApp(async (appRoot) => {
383
+ await writeAppFixture(appRoot);
384
+
385
+ const targetFile = "w/[workspaceSlug]/admin/practice/index.vue";
386
+ const targetPath = path.join(appRoot, toPagePath(targetFile));
387
+ const originalPageSource = `<template>
388
+ <div>custom practice page</div>
389
+ </template>
390
+ `;
391
+ const placementPath = path.join(appRoot, "src", "placement.js");
392
+ const originalPlacementSource = await readFile(placementPath, "utf8");
393
+
394
+ await mkdir(path.dirname(targetPath), { recursive: true });
395
+ await writeFile(targetPath, originalPageSource, "utf8");
396
+
397
+ await assert.rejects(
398
+ runGeneratorSubcommand({
399
+ appRoot,
400
+ subcommand: "page",
401
+ args: [targetFile],
402
+ options: {
403
+ force: "true",
404
+ "link-placement": "missing:target"
405
+ }
406
+ }),
407
+ /ui-generator page option "placement" target "missing:target" is not declared/
408
+ );
409
+
410
+ const pageSource = await readFile(targetPath, "utf8");
411
+ assert.equal(pageSource, originalPageSource);
412
+ const placementSource = await readFile(placementPath, "utf8");
413
+ assert.equal(placementSource, originalPlacementSource);
414
+ });
415
+ });