@awesomeness-js/server 1.1.13 → 1.1.14

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": "@awesomeness-js/server",
3
- "version": "1.1.13",
3
+ "version": "1.1.14",
4
4
  "description": "Awesomeness Multi-Site Server",
5
5
  "author": "Scott Forte",
6
6
  "type": "module",
@@ -52,319 +52,322 @@ export default function componentDependencies(
52
52
 
53
53
  }
54
54
 
55
- let componentsToProcess = [ ...allComponents ];
55
+ let componentsToProcess = [ ...new Set(allComponents) ];
56
+ const queuedComponents = new Set(componentsToProcess);
57
+ const processedComponents = new Set();
56
58
  const out = {};
57
59
 
58
60
  while (componentsToProcess.length > 0) {
59
61
 
60
- const newComponentsToProcess = [];
62
+ const component = componentsToProcess.shift();
61
63
 
62
- componentsToProcess.forEach((component) => {
64
+ queuedComponents.delete(component);
63
65
 
64
- // Build roots in priority order; last is default because it’s last
65
- const candidateRoots = componentLocations.map((baseUrl) => {
66
+ if (processedComponents.has(component)) {
66
67
 
67
- // baseUrl should point at a directory; we resolve component under it
68
- const componentUrl = new URL(`./${component}/`, baseUrl);
68
+ continue;
69
+
70
+ }
69
71
 
70
-
71
- return path.resolve(urlToFsPath(componentUrl));
72
-
73
- });
72
+ processedComponents.add(component);
74
73
 
75
- let allFiles;
76
- let chosenRoot;
77
- let lastErr;
74
+ const candidateRoots = componentLocations.map((baseUrl) => {
78
75
 
79
- for (const root of candidateRoots) {
76
+ const componentUrl = new URL(`./${component}/`, baseUrl);
80
77
 
81
- try {
78
+ return path.resolve(urlToFsPath(componentUrl));
79
+
80
+ });
82
81
 
83
- // IMPORTANT: pass root so getAllFiles returns paths relative to the scan root
84
- allFiles = getAllFiles(".", {
85
- dir: root,
86
- root,
87
- ignore,
88
- });
82
+ let allFiles;
83
+ let chosenRoot;
84
+ let lastErr;
89
85
 
90
- chosenRoot = root;
91
- break; // first match wins
92
-
93
- } catch (e) {
86
+ for (const root of candidateRoots) {
94
87
 
95
- lastErr = e;
96
-
97
- }
98
-
99
- }
88
+ try {
100
89
 
101
- if (!allFiles) {
90
+ allFiles = getAllFiles(".", {
91
+ dir: root,
92
+ root,
93
+ ignore,
94
+ });
102
95
 
103
- throw {
104
- message: "component does not exist (no location matched)",
105
- component,
106
- tried: candidateRoots,
107
- cause: lastErr?.message ?? lastErr,
108
- };
96
+ chosenRoot = root;
97
+ break;
109
98
 
110
- }
99
+ } catch (e) {
111
100
 
112
- if (
113
- awesomenessConfig.debug_componentDependencies &&
114
- Array.isArray(awesomenessConfig.debug_componentDependencies) &&
115
- awesomenessConfig.debug_componentDependencies.includes(component)
116
- ) {
117
-
118
- console.log("[awesomenessConfig.debug componentDependencies] chosenRoot:", chosenRoot);
119
- console.log("[awesomenessConfig.debug componentDependencies] allFiles count:", allFiles.length);
120
- console.log("[awesomenessConfig.debug componentDependencies] first 50 files:", allFiles.slice(0, 50));
121
- console.log(
122
- "[awesomenessConfig.debug componentDependencies] any non-string:",
123
- allFiles.some((f) => typeof f !== "string")
124
- );
125
- console.log(
126
- "[awesomenessConfig.debug componentDependencies] any absolute:",
127
- allFiles.some((f) => path.isAbsolute(f))
128
- );
101
+ lastErr = e;
129
102
 
130
103
  }
104
+
105
+ }
131
106
 
132
- allFiles.forEach((file) => {
107
+ if (!allFiles) {
133
108
 
134
- const normalizedPath = path.normalize(file);
109
+ throw {
110
+ message: "component does not exist (no location matched)",
111
+ component,
112
+ tried: candidateRoots,
113
+ cause: lastErr?.message ?? lastErr,
114
+ };
115
+
116
+ }
135
117
 
136
- const fileNameFull = path.basename(normalizedPath);
137
- const fileTypeArr = fileNameFull.split(".");
138
- const fileType = fileTypeArr[fileTypeArr.length - 1].toLowerCase();
139
- const fileName = fileTypeArr.slice(0, -1).join(".");
118
+ if (
119
+ awesomenessConfig.debug_componentDependencies &&
120
+ Array.isArray(awesomenessConfig.debug_componentDependencies) &&
121
+ awesomenessConfig.debug_componentDependencies.includes(component)
122
+ ) {
123
+
124
+ console.log("[awesomenessConfig.debug componentDependencies] chosenRoot:", chosenRoot);
125
+ console.log("[awesomenessConfig.debug componentDependencies] allFiles count:", allFiles.length);
126
+ console.log("[awesomenessConfig.debug componentDependencies] first 50 files:", allFiles.slice(0, 50));
127
+ console.log(
128
+ "[awesomenessConfig.debug componentDependencies] any non-string:",
129
+ allFiles.some((f) => typeof f !== "string")
130
+ );
131
+ console.log(
132
+ "[awesomenessConfig.debug componentDependencies] any absolute:",
133
+ allFiles.some((f) => path.isAbsolute(f))
134
+ );
135
+
136
+ }
140
137
 
141
- out[component] = out[component] || {};
142
- out[component][fileType] = out[component][fileType] || {};
138
+ allFiles.forEach((file) => {
143
139
 
144
- // Build tail from the file's relative directory (NOT by searching for `component` in the path)
145
- const dir = path.dirname(normalizedPath);
146
- const dirParts = dir === "." ? [] : dir.split(path.sep);
140
+ const normalizedPath = path.normalize(file);
141
+ const fileNameFull = path.basename(normalizedPath);
142
+ const fileTypeArr = fileNameFull.split(".");
143
+ const fileType = fileTypeArr[fileTypeArr.length - 1].toLowerCase();
144
+ const fileName = fileTypeArr.slice(0, -1).join(".");
147
145
 
148
- let tail = "";
146
+ out[component] = out[component] || {};
147
+ out[component][fileType] = out[component][fileType] || {};
149
148
 
150
- if (fileType === "js" || fileType === "css") {
149
+ const dir = path.dirname(normalizedPath);
150
+ const dirParts = dir === "." ? [] : dir.split(path.sep);
151
151
 
152
- if (dirParts.length > 0) {
152
+ let tail = "";
153
153
 
154
- tail =
155
- fileName === "index"
156
- ? "." + dirParts.join(".")
157
- : `.${dirParts.join(".")}.${fileName}`;
158
-
159
- } else {
154
+ if (fileType === "js" || fileType === "css") {
160
155
 
161
- tail = fileName === "index" ? "" : `.${fileName}`;
162
-
163
- }
156
+ if (dirParts.length > 0) {
157
+
158
+ tail = fileName === "index"
159
+ ? "." + dirParts.join(".")
160
+ : `.${dirParts.join(".")}.${fileName}`;
164
161
 
165
- }
162
+ } else {
166
163
 
167
- const componentName = `${namespace}.${component}${tail}`;
164
+ tail = fileName === "index" ? "" : `.${fileName}`;
165
+
166
+ }
167
+
168
+ }
168
169
 
169
- try {
170
+ const componentName = `${namespace}.${component}${tail}`;
170
171
 
171
- // readFileSync must use chosenRoot + relative file path
172
- const filePath = path.isAbsolute(file) ? file : path.join(chosenRoot, file);
173
- const fileContent = readFileMemoized(filePath);
172
+ try {
174
173
 
175
- const lines = fileContent.split("\n");
176
- let fileWithImportsStripped = "";
174
+ const filePath = path.isAbsolute(file) ? file : path.join(chosenRoot, file);
175
+ const fileContent = readFileMemoized(filePath);
177
176
 
178
- try {
177
+ const lines = fileContent.split("\n");
178
+ let fileWithImportsStripped = "";
179
179
 
180
- const newTest = extractUiRefsFromFileMemoized(filePath, {
181
- namespace,
182
- includeDotAccess: true,
183
- cacheContext: `component:${component}|file:${filePath}`,
184
- });
180
+ try {
185
181
 
186
- if (newTest.length > 0) {
182
+ const discoveredFromContent = extractUiRefsFromFileMemoized(filePath, {
183
+ namespace,
184
+ includeDotAccess: true,
185
+ cacheContext: `component:${component}|file:${filePath}`,
186
+ });
187
187
 
188
- newTest.forEach((newComp) => {
188
+ discoveredFromContent.forEach((newComp) => {
189
189
 
190
- if (!allComponents.includes(newComp)) {
190
+ if (!allComponents.includes(newComp)) {
191
191
 
192
- allComponents.push(newComp);
193
- newComponentsToProcess.push(newComp);
194
-
195
- }
196
-
197
- });
192
+ allComponents.push(newComp);
198
193
 
199
194
  }
200
-
201
- } catch (error) {
202
195
 
203
- console.error("Error extracting UI parts:", error);
196
+ if (!processedComponents.has(newComp) && !queuedComponents.has(newComp)) {
197
+
198
+ componentsToProcess.push(newComp);
199
+ queuedComponents.add(newComp);
200
+
201
+ }
204
202
 
205
- }
203
+ });
204
+
205
+ } catch (error) {
206
206
 
207
- lines.forEach((line) => {
207
+ console.error("Error extracting UI parts:", error);
208
+
209
+ }
208
210
 
209
- if (line.startsWith("import ui")) {
211
+ lines.forEach((line) => {
210
212
 
211
- if (line.includes(`import ui from '#ui'; // `)) {
213
+ if (line.startsWith("import ui")) {
212
214
 
213
- const imports = line.split(`import ui from '#ui'; // `);
215
+ if (line.includes(`import ui from '#ui'; // `)) {
214
216
 
215
- if (imports.length > 1) {
217
+ const imports = line.split(`import ui from '#ui'; // `);
216
218
 
217
- const importComponents = imports[1]
218
- .split(",")
219
- .map((c) => c.trim());
219
+ if (imports.length > 1) {
220
220
 
221
- importComponents.forEach((importComponent) => {
221
+ imports[1]
222
+ .split(",")
223
+ .map((c) => c.trim())
224
+ .forEach((importComponent) => {
222
225
 
223
226
  if (!allComponents.includes(importComponent)) {
224
227
 
225
228
  allComponents.push(importComponent);
226
- newComponentsToProcess.push(importComponent);
229
+
230
+ }
231
+
232
+ if (!processedComponents.has(importComponent) && !queuedComponents.has(importComponent)) {
233
+
234
+ componentsToProcess.push(importComponent);
235
+ queuedComponents.add(importComponent);
227
236
 
228
237
  }
229
238
 
230
239
  });
231
-
232
- }
233
240
 
234
241
  }
235
242
 
236
- } else if (
237
- line.startsWith("// awesomeness import") ||
238
- line.startsWith("/* awesomeness @import")
239
- ) {
243
+ }
244
+
245
+ } else if (
246
+ line.startsWith("// awesomeness import") ||
247
+ line.startsWith("/* awesomeness @import")
248
+ ) {
240
249
 
241
- const importPathMatch = line.match(/['"]([^'"]+)['"]/);
250
+ const importPathMatch = line.match(/['"]([^'"]+)['"]/);
242
251
 
243
- if (importPathMatch) {
252
+ if (importPathMatch) {
244
253
 
245
- const importedComponentName = importPathMatch[1]
246
- .replace(/;$/, "")
247
- .trim();
254
+ const importedComponentName = importPathMatch[1].replace(/;$/, "").trim();
248
255
 
249
- if (!allComponents.includes(importedComponentName)) {
256
+ if (!allComponents.includes(importedComponentName)) {
250
257
 
251
- allComponents.push(importedComponentName);
252
- newComponentsToProcess.push(importedComponentName);
253
-
254
- }
258
+ allComponents.push(importedComponentName);
255
259
 
256
260
  }
257
-
258
- } else {
259
261
 
260
- fileWithImportsStripped += `${line}\n`;
262
+ if (!processedComponents.has(importedComponentName) && !queuedComponents.has(importedComponentName)) {
263
+
264
+ componentsToProcess.push(importedComponentName);
265
+ queuedComponents.add(importedComponentName);
266
+
267
+ }
261
268
 
262
269
  }
263
270
 
264
- });
271
+ } else {
265
272
 
266
- if (fileType === "js") {
273
+ fileWithImportsStripped += `${line}\n`;
274
+
275
+ }
276
+
277
+ });
267
278
 
268
- if (
269
- fileWithImportsStripped.startsWith("(function") ||
270
- fileWithImportsStripped.startsWith("((")
271
- ) {
279
+ if (fileType === "js") {
272
280
 
273
- fileWithImportsStripped = `;${fileWithImportsStripped}`;
274
-
275
- } else {
281
+ if (
282
+ fileWithImportsStripped.startsWith("(function") ||
283
+ fileWithImportsStripped.startsWith("((")
284
+ ) {
276
285
 
277
- fileWithImportsStripped = fileWithImportsStripped.replace(
278
- "export default ",
279
- `${componentName} = `
280
- );
281
-
282
- }
286
+ fileWithImportsStripped = `;${fileWithImportsStripped}`;
283
287
 
284
- }
288
+ } else {
285
289
 
286
- out[component][fileType][componentName] = fileWithImportsStripped;
290
+ fileWithImportsStripped = fileWithImportsStripped.replace(
291
+ "export default ",
292
+ `${componentName} = `
293
+ );
294
+
295
+ }
287
296
 
288
- } catch (err) {
297
+ }
289
298
 
290
- const full = path.isAbsolute(file) ? file : path.join(chosenRoot, file);
299
+ out[component][fileType][componentName] = fileWithImportsStripped;
300
+
301
+ } catch (err) {
291
302
 
292
- console.error("Failed to get dependencies", {
293
- component,
294
- file,
295
- full,
296
- code: err?.code,
297
- message: err?.message,
298
- stack: err?.stack,
299
- });
300
-
301
- }
303
+ const full = path.isAbsolute(file) ? file : path.join(chosenRoot, file);
304
+
305
+ console.error("Failed to get dependencies", {
306
+ component,
307
+ file,
308
+ full,
309
+ code: err?.code,
310
+ message: err?.message,
311
+ stack: err?.stack,
312
+ });
302
313
 
303
- });
314
+ }
315
+
316
+ });
304
317
 
305
- if (out[component]) {
318
+ if (out[component]) {
306
319
 
307
- each(out[component], (files, type) => {
320
+ each(out[component], (files, type) => {
308
321
 
309
- if (type === "js") {
322
+ if (type === "js") {
310
323
 
311
- const jsKeys = Object.keys(files);
324
+ const jsKeys = Object.keys(files);
312
325
 
313
- jsKeys.forEach((key) => {
326
+ jsKeys.forEach((key) => {
314
327
 
315
- const keyParts = key.split(".");
328
+ const keyParts = key.split(".");
316
329
 
317
- for (let i = 2; i < keyParts.length; i++) {
330
+ for (let i = 2; i < keyParts.length; i++) {
318
331
 
319
- const parentPath = keyParts.slice(0, i).join(".");
332
+ const parentPath = keyParts.slice(0, i).join(".");
320
333
 
321
- if (!files[parentPath]) {
334
+ if (!files[parentPath]) {
322
335
 
323
- files[parentPath] = `${parentPath} = ${parentPath} || {}; `;
324
-
325
- }
336
+ files[parentPath] = `${parentPath} = ${parentPath} || {}; `;
326
337
 
327
338
  }
328
339
 
329
- });
330
-
331
- }
332
-
333
- if (type === "js" && !files[`${namespace}.${component}`]) {
334
-
335
- files[`${namespace}.${component}`] = `${namespace}.${component} = {}; `;
340
+ }
336
341
 
337
- }
342
+ });
343
+
344
+ }
338
345
 
339
- files = Object.keys(files)
340
- .sort()
341
- .reduce((obj, key) => {
346
+ if (type === "js" && !files[`${namespace}.${component}`]) {
342
347
 
343
- obj[key] = files[key];
344
-
345
- return obj;
346
-
347
- }, {});
348
+ files[`${namespace}.${component}`] = `${namespace}.${component} = {}; `;
349
+
350
+ }
348
351
 
349
- if (showDetails) {
352
+ files = Object.keys(files)
353
+ .sort()
354
+ .reduce((obj, key) => {
350
355
 
351
- out[component][type + "_details"] = files;
356
+ obj[key] = files[key];
357
+
358
+ return obj;
352
359
 
353
- }
360
+ }, {});
354
361
 
355
- out[component][type] = ` ${Object.values(files).join("\n")} `;
356
-
357
- });
358
-
359
- }
362
+ if (showDetails) {
360
363
 
361
- componentsToProcess = componentsToProcess.filter((f) => f !== component);
362
-
363
- });
364
-
365
- if (newComponentsToProcess.length) {
364
+ out[component][type + "_details"] = files;
365
+
366
+ }
366
367
 
367
- componentsToProcess = componentsToProcess.concat(newComponentsToProcess);
368
+ out[component][type] = ` ${Object.values(files).join("\n")} `;
369
+
370
+ });
368
371
 
369
372
  }
370
373
 
@@ -0,0 +1,85 @@
1
+ import fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+ import { pathToFileURL } from "url";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+
7
+ import { clearComponentAndPageMemory } from "../src/componentAndPageMemory.js";
8
+
9
+ const mocks = vi.hoisted(() => ({
10
+ getConfig: vi.fn(),
11
+ }));
12
+
13
+ vi.mock("../src/getConfig.js", () => ({
14
+ default: mocks.getConfig,
15
+ }));
16
+
17
+ import componentDependencies from "../src/componentDependencies.js";
18
+
19
+ describe("componentDependencies", () => {
20
+
21
+ let tempRoot;
22
+
23
+ function writeComponentFile(componentName, relativeFilePath, content) {
24
+
25
+ const filePath = path.join(tempRoot, componentName, relativeFilePath);
26
+
27
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
28
+ fs.writeFileSync(filePath, content);
29
+
30
+ }
31
+
32
+ beforeEach(() => {
33
+
34
+ clearComponentAndPageMemory();
35
+ tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "awes-component-deps-"));
36
+ mocks.getConfig.mockReturnValue({});
37
+
38
+ });
39
+
40
+ afterEach(() => {
41
+
42
+ clearComponentAndPageMemory();
43
+ fs.rmSync(tempRoot, {
44
+ recursive: true,
45
+ force: true
46
+ });
47
+ vi.clearAllMocks();
48
+
49
+ });
50
+
51
+ it("recursively resolves transitive dependencies through nested component files", () => {
52
+
53
+ writeComponentFile("alpha", "index.js", `export default function alpha() {
54
+ ui.beta.start();
55
+ return true;
56
+ }`);
57
+
58
+ writeComponentFile("beta", path.join("nested", "index.js"), `export default function betaNested() {
59
+ ui.gamma.mount();
60
+ return true;
61
+ }`);
62
+
63
+ writeComponentFile("gamma", path.join("deeper", "bridge.js"), `export default function gammaBridge() {
64
+ ui.delta.render();
65
+ return true;
66
+ }`);
67
+
68
+ writeComponentFile("delta", "index.js", `export default function delta() {
69
+ return "done";
70
+ }`);
71
+
72
+ const out = componentDependencies([ "alpha" ], {
73
+ componentLocations: [ pathToFileURL(tempRoot + path.sep) ],
74
+ namespace: "ui",
75
+ });
76
+
77
+ expect(Object.keys(out).sort()).toEqual([ "alpha", "beta", "delta", "gamma" ]);
78
+ expect(out.alpha.js).toContain("ui.alpha = function alpha()");
79
+ expect(out.beta.js).toContain("ui.beta.nested = function betaNested()");
80
+ expect(out.gamma.js).toContain("ui.gamma.deeper.bridge = function gammaBridge()");
81
+ expect(out.delta.js).toContain("ui.delta = function delta()");
82
+
83
+ });
84
+
85
+ });