@immense/vue-pom-generator 1.0.53 → 1.0.55
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/README.md +12 -5
- package/RELEASE_NOTES.md +13 -48
- package/class-generation/base-page.ts +18 -6
- package/class-generation/callout.ts +229 -679
- package/class-generation/floating-ui-callout.ts +857 -0
- package/class-generation/index.ts +1 -1
- package/class-generation/pointer.ts +152 -109
- package/dist/class-generation/base-page.d.ts +11 -5
- package/dist/class-generation/base-page.d.ts.map +1 -1
- package/dist/class-generation/callout.d.ts +44 -1
- package/dist/class-generation/callout.d.ts.map +1 -1
- package/dist/class-generation/floating-ui-callout.d.ts +4 -0
- package/dist/class-generation/floating-ui-callout.d.ts.map +1 -0
- package/dist/class-generation/pointer.d.ts +24 -5
- package/dist/class-generation/pointer.d.ts.map +1 -1
- package/dist/index.cjs +267 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +267 -49
- package/dist/index.mjs.map +1 -1
- package/dist/plugin/support/build-plugin.d.ts.map +1 -1
- package/dist/plugin/support/dev-plugin.d.ts.map +1 -1
- package/dist/plugin/support/virtual-modules.d.ts +2 -2
- package/dist/plugin/support/virtual-modules.d.ts.map +1 -1
- package/dist/plugin/types.d.ts +2 -2
- package/dist/router-introspection.d.ts.map +1 -1
- package/dist/vite.config.d.ts.map +1 -1
- package/package.json +9 -13
package/dist/index.mjs
CHANGED
|
@@ -9,10 +9,8 @@ import { parse as parse$1, compileScript } from "@vue/compiler-sfc";
|
|
|
9
9
|
import { parseExpression, parse } from "@babel/parser";
|
|
10
10
|
import { NodeTypes, stringifyExpression, ConstantTypes, createSimpleExpression, ElementTypes } from "@vue/compiler-core";
|
|
11
11
|
import { Project, QuoteKind, NewLineKind, IndentationText, StructureKind, CodeBlockWriter, VariableDeclarationKind } from "ts-morph";
|
|
12
|
-
import { JSDOM } from "jsdom";
|
|
13
12
|
import { isArrayExpression, isStringLiteral, isTemplateLiteral, isAssignmentExpression, isIdentifier, isMemberExpression, isCallExpression, isExpressionStatement, isArrowFunctionExpression, isOptionalMemberExpression, isObjectExpression, isFile, isBlockStatement, isOptionalCallExpression, isLogicalExpression, isConditionalExpression, isSequenceExpression, isAssignmentPattern, isRestElement, isObjectPattern, isObjectProperty, isProgram, VISITOR_KEYS, isBooleanLiteral, isNumericLiteral, isNullLiteral } from "@babel/types";
|
|
14
13
|
import { performance } from "node:perf_hooks";
|
|
15
|
-
import virtualImport from "vite-plugin-virtual";
|
|
16
14
|
import vue from "@vitejs/plugin-vue";
|
|
17
15
|
const VUE_POM_GENERATOR_LOG_PREFIX = "[vue-pom-generator]";
|
|
18
16
|
function normalize(message) {
|
|
@@ -3270,24 +3268,254 @@ function resolveIntrospectedComponentName(componentInfo, componentNaming) {
|
|
|
3270
3268
|
}
|
|
3271
3269
|
return componentInfo.componentName;
|
|
3272
3270
|
}
|
|
3271
|
+
function hasChildren(parent) {
|
|
3272
|
+
return "children" in parent;
|
|
3273
|
+
}
|
|
3274
|
+
function appendNode(parent, child) {
|
|
3275
|
+
parent.childNodes.push(child);
|
|
3276
|
+
if (hasChildren(parent) && child.nodeType === 1)
|
|
3277
|
+
parent.children.push(child);
|
|
3278
|
+
return child;
|
|
3279
|
+
}
|
|
3280
|
+
function removeNode(parent, child) {
|
|
3281
|
+
const idx = parent.childNodes.indexOf(child);
|
|
3282
|
+
if (idx >= 0)
|
|
3283
|
+
parent.childNodes.splice(idx, 1);
|
|
3284
|
+
if (hasChildren(parent) && child.nodeType === 1) {
|
|
3285
|
+
const childIdx = parent.children.indexOf(child);
|
|
3286
|
+
if (childIdx >= 0)
|
|
3287
|
+
parent.children.splice(childIdx, 1);
|
|
3288
|
+
}
|
|
3289
|
+
return child;
|
|
3290
|
+
}
|
|
3291
|
+
function createMinimalDocument() {
|
|
3292
|
+
const doc = {
|
|
3293
|
+
nodeType: 9,
|
|
3294
|
+
// DOCUMENT_NODE
|
|
3295
|
+
documentElement: null,
|
|
3296
|
+
head: null,
|
|
3297
|
+
body: null,
|
|
3298
|
+
createElement(tag) {
|
|
3299
|
+
const element = {
|
|
3300
|
+
nodeType: 1,
|
|
3301
|
+
tagName: tag.toUpperCase(),
|
|
3302
|
+
childNodes: [],
|
|
3303
|
+
children: [],
|
|
3304
|
+
style: {},
|
|
3305
|
+
dataset: {},
|
|
3306
|
+
setAttribute() {
|
|
3307
|
+
},
|
|
3308
|
+
getAttribute() {
|
|
3309
|
+
return null;
|
|
3310
|
+
},
|
|
3311
|
+
removeAttribute() {
|
|
3312
|
+
},
|
|
3313
|
+
addEventListener() {
|
|
3314
|
+
},
|
|
3315
|
+
removeEventListener() {
|
|
3316
|
+
},
|
|
3317
|
+
appendChild(child) {
|
|
3318
|
+
return appendNode(this, child);
|
|
3319
|
+
},
|
|
3320
|
+
removeChild(child) {
|
|
3321
|
+
return removeNode(this, child);
|
|
3322
|
+
},
|
|
3323
|
+
insertBefore(child) {
|
|
3324
|
+
return appendNode(this, child);
|
|
3325
|
+
},
|
|
3326
|
+
querySelector() {
|
|
3327
|
+
return null;
|
|
3328
|
+
},
|
|
3329
|
+
querySelectorAll() {
|
|
3330
|
+
return [];
|
|
3331
|
+
},
|
|
3332
|
+
contains() {
|
|
3333
|
+
return false;
|
|
3334
|
+
},
|
|
3335
|
+
matches() {
|
|
3336
|
+
return false;
|
|
3337
|
+
},
|
|
3338
|
+
closest() {
|
|
3339
|
+
return null;
|
|
3340
|
+
},
|
|
3341
|
+
getBoundingClientRect() {
|
|
3342
|
+
return { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0, x: 0, y: 0, toJSON() {
|
|
3343
|
+
return {};
|
|
3344
|
+
} };
|
|
3345
|
+
},
|
|
3346
|
+
cloneNode() {
|
|
3347
|
+
return this;
|
|
3348
|
+
}
|
|
3349
|
+
};
|
|
3350
|
+
return element;
|
|
3351
|
+
},
|
|
3352
|
+
createTextNode(text) {
|
|
3353
|
+
return { nodeType: 3, textContent: text };
|
|
3354
|
+
},
|
|
3355
|
+
createComment(text) {
|
|
3356
|
+
return { nodeType: 8, textContent: text };
|
|
3357
|
+
},
|
|
3358
|
+
createDocumentFragment() {
|
|
3359
|
+
return {
|
|
3360
|
+
nodeType: 11,
|
|
3361
|
+
childNodes: [],
|
|
3362
|
+
appendChild(child) {
|
|
3363
|
+
return appendNode(this, child);
|
|
3364
|
+
}
|
|
3365
|
+
};
|
|
3366
|
+
},
|
|
3367
|
+
getElementById() {
|
|
3368
|
+
return null;
|
|
3369
|
+
},
|
|
3370
|
+
querySelector() {
|
|
3371
|
+
return null;
|
|
3372
|
+
},
|
|
3373
|
+
querySelectorAll() {
|
|
3374
|
+
return [];
|
|
3375
|
+
},
|
|
3376
|
+
addEventListener() {
|
|
3377
|
+
},
|
|
3378
|
+
removeEventListener() {
|
|
3379
|
+
},
|
|
3380
|
+
createEvent() {
|
|
3381
|
+
return {
|
|
3382
|
+
initEvent() {
|
|
3383
|
+
}
|
|
3384
|
+
};
|
|
3385
|
+
},
|
|
3386
|
+
queryCommandSupported() {
|
|
3387
|
+
return false;
|
|
3388
|
+
}
|
|
3389
|
+
};
|
|
3390
|
+
const html = doc.createElement("html");
|
|
3391
|
+
const head = doc.createElement("head");
|
|
3392
|
+
const body = doc.createElement("body");
|
|
3393
|
+
const app = doc.createElement("div");
|
|
3394
|
+
app.id = "app";
|
|
3395
|
+
html.appendChild(head);
|
|
3396
|
+
html.appendChild(body);
|
|
3397
|
+
body.appendChild(app);
|
|
3398
|
+
doc.documentElement = html;
|
|
3399
|
+
doc.head = head;
|
|
3400
|
+
doc.body = body;
|
|
3401
|
+
return Object.assign({}, doc);
|
|
3402
|
+
}
|
|
3403
|
+
function createMinimalLocation() {
|
|
3404
|
+
const url = new URL("https://example.test/");
|
|
3405
|
+
return {
|
|
3406
|
+
get href() {
|
|
3407
|
+
return url.href;
|
|
3408
|
+
},
|
|
3409
|
+
set href(v) {
|
|
3410
|
+
try {
|
|
3411
|
+
Object.assign(url, new URL(v));
|
|
3412
|
+
} catch {
|
|
3413
|
+
}
|
|
3414
|
+
},
|
|
3415
|
+
get origin() {
|
|
3416
|
+
return url.origin;
|
|
3417
|
+
},
|
|
3418
|
+
get protocol() {
|
|
3419
|
+
return url.protocol;
|
|
3420
|
+
},
|
|
3421
|
+
get host() {
|
|
3422
|
+
return url.host;
|
|
3423
|
+
},
|
|
3424
|
+
get hostname() {
|
|
3425
|
+
return url.hostname;
|
|
3426
|
+
},
|
|
3427
|
+
get port() {
|
|
3428
|
+
return url.port;
|
|
3429
|
+
},
|
|
3430
|
+
get pathname() {
|
|
3431
|
+
return url.pathname;
|
|
3432
|
+
},
|
|
3433
|
+
set pathname(v) {
|
|
3434
|
+
url.pathname = v;
|
|
3435
|
+
},
|
|
3436
|
+
get search() {
|
|
3437
|
+
return url.search;
|
|
3438
|
+
},
|
|
3439
|
+
set search(v) {
|
|
3440
|
+
url.search = v;
|
|
3441
|
+
},
|
|
3442
|
+
get hash() {
|
|
3443
|
+
return url.hash;
|
|
3444
|
+
},
|
|
3445
|
+
set hash(v) {
|
|
3446
|
+
url.hash = v;
|
|
3447
|
+
},
|
|
3448
|
+
assign() {
|
|
3449
|
+
},
|
|
3450
|
+
reload() {
|
|
3451
|
+
},
|
|
3452
|
+
replace() {
|
|
3453
|
+
},
|
|
3454
|
+
toString() {
|
|
3455
|
+
return url.href;
|
|
3456
|
+
},
|
|
3457
|
+
ancestorOrigins: { length: 0, contains: () => false, item: () => null, [Symbol.iterator]: [][Symbol.iterator] }
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3273
3460
|
async function ensureDomShim() {
|
|
3274
|
-
const domShimHtml = "<!doctype html><html><head></head><body><div id='app'></div></body></html>";
|
|
3275
|
-
const domShimUrl = "https://example.test/";
|
|
3276
3461
|
const g = globalThis;
|
|
3277
3462
|
if (typeof document !== "undefined" && typeof window !== "undefined")
|
|
3278
3463
|
return;
|
|
3279
|
-
const
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3464
|
+
const minimalDoc = createMinimalDocument();
|
|
3465
|
+
const minimalLocation = createMinimalLocation();
|
|
3466
|
+
const win = {
|
|
3467
|
+
document: minimalDoc,
|
|
3468
|
+
location: minimalLocation,
|
|
3469
|
+
navigator: { userAgent: "node" },
|
|
3470
|
+
history: { pushState() {
|
|
3471
|
+
}, replaceState() {
|
|
3472
|
+
}, go() {
|
|
3473
|
+
}, back() {
|
|
3474
|
+
}, forward() {
|
|
3475
|
+
}, state: null, length: 0, scrollRestoration: "auto" },
|
|
3476
|
+
addEventListener() {
|
|
3477
|
+
},
|
|
3478
|
+
removeEventListener() {
|
|
3479
|
+
},
|
|
3480
|
+
dispatchEvent() {
|
|
3481
|
+
return true;
|
|
3482
|
+
},
|
|
3483
|
+
getComputedStyle() {
|
|
3484
|
+
return {};
|
|
3485
|
+
},
|
|
3486
|
+
scrollTo() {
|
|
3487
|
+
},
|
|
3488
|
+
scroll() {
|
|
3489
|
+
},
|
|
3490
|
+
scrollBy() {
|
|
3491
|
+
},
|
|
3492
|
+
matchMedia() {
|
|
3493
|
+
return { matches: false, media: "", onchange: null, addListener() {
|
|
3494
|
+
}, removeListener() {
|
|
3495
|
+
}, addEventListener() {
|
|
3496
|
+
}, removeEventListener() {
|
|
3497
|
+
}, dispatchEvent() {
|
|
3498
|
+
return true;
|
|
3499
|
+
} };
|
|
3500
|
+
},
|
|
3501
|
+
requestAnimationFrame: (cb) => setTimeout(() => cb(Date.now()), 16),
|
|
3502
|
+
cancelAnimationFrame: (id) => clearTimeout(id),
|
|
3503
|
+
setTimeout,
|
|
3504
|
+
clearTimeout,
|
|
3505
|
+
setInterval,
|
|
3506
|
+
clearInterval,
|
|
3507
|
+
queueMicrotask,
|
|
3508
|
+
performance: globalThis.performance
|
|
3509
|
+
};
|
|
3510
|
+
g.window = win;
|
|
3511
|
+
g.document = minimalDoc;
|
|
3512
|
+
g.location = minimalLocation;
|
|
3283
3513
|
if (!g.self)
|
|
3284
|
-
g.self =
|
|
3514
|
+
g.self = win;
|
|
3285
3515
|
if (!g.navigator)
|
|
3286
|
-
g.navigator =
|
|
3516
|
+
g.navigator = win.navigator;
|
|
3287
3517
|
if (!g.history)
|
|
3288
|
-
g.history =
|
|
3289
|
-
}, replaceState() {
|
|
3290
|
-
} };
|
|
3518
|
+
g.history = win.history;
|
|
3291
3519
|
if (!g.MutationObserver) {
|
|
3292
3520
|
g.MutationObserver = class {
|
|
3293
3521
|
disconnect() {
|
|
@@ -3325,10 +3553,6 @@ async function ensureDomShim() {
|
|
|
3325
3553
|
if (!g.requestIdleCallback) {
|
|
3326
3554
|
g.requestIdleCallback = (cb) => setTimeout(() => cb({ didTimeout: false, timeRemaining: () => 0 }), 1);
|
|
3327
3555
|
}
|
|
3328
|
-
const doc = g.document;
|
|
3329
|
-
if (doc && typeof doc.queryCommandSupported !== "function") {
|
|
3330
|
-
doc.queryCommandSupported = () => false;
|
|
3331
|
-
}
|
|
3332
3556
|
if (!g.localStorage || !g.sessionStorage) {
|
|
3333
3557
|
const storageFactory = () => {
|
|
3334
3558
|
const store = /* @__PURE__ */ new Map();
|
|
@@ -3354,25 +3578,6 @@ async function ensureDomShim() {
|
|
|
3354
3578
|
if (!g.sessionStorage)
|
|
3355
3579
|
g.sessionStorage = storageFactory();
|
|
3356
3580
|
}
|
|
3357
|
-
const names = Object.getOwnPropertyNames(dom.window);
|
|
3358
|
-
const shouldCopyGlobal = (name) => {
|
|
3359
|
-
if (name === "Node" || name === "Element" || name === "Document" || name === "Event" || name === "EventTarget")
|
|
3360
|
-
return true;
|
|
3361
|
-
if (name.endsWith("Event"))
|
|
3362
|
-
return true;
|
|
3363
|
-
if (name.startsWith("HTML") && name.endsWith("Element"))
|
|
3364
|
-
return true;
|
|
3365
|
-
if (name.startsWith("SVG") && name.endsWith("Element"))
|
|
3366
|
-
return true;
|
|
3367
|
-
return false;
|
|
3368
|
-
};
|
|
3369
|
-
for (const name of names) {
|
|
3370
|
-
if (!shouldCopyGlobal(name) || g[name])
|
|
3371
|
-
continue;
|
|
3372
|
-
const value = Reflect.get(dom.window, name);
|
|
3373
|
-
if (value)
|
|
3374
|
-
g[name] = value;
|
|
3375
|
-
}
|
|
3376
3581
|
if (!g.requestAnimationFrame)
|
|
3377
3582
|
g.requestAnimationFrame = (cb) => setTimeout(() => cb(Date.now()), 16);
|
|
3378
3583
|
}
|
|
@@ -4344,7 +4549,7 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4344
4549
|
" }",
|
|
4345
4550
|
"",
|
|
4346
4551
|
" // Minimal vue-select helper mirroring the TS BasePage.selectVSelectByTestId behavior.",
|
|
4347
|
-
" // Note: annotationText is currently a no-op in C# output (we don't render a
|
|
4552
|
+
" // Note: annotationText is currently a no-op in C# output (we don't render a pointer overlay).",
|
|
4348
4553
|
" protected async Task SelectVSelectByTestIdAsync(string testId, string value, int timeOut = 500)",
|
|
4349
4554
|
" {",
|
|
4350
4555
|
" var root = LocatorByTestId(testId);",
|
|
@@ -6304,7 +6509,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6304
6509
|
const existingIdBehavior = options.existingIdBehavior ?? "preserve";
|
|
6305
6510
|
const testIdAttribute = (options.testIdAttribute || "data-testid").trim() || "data-testid";
|
|
6306
6511
|
const nameCollisionBehavior = options.nameCollisionBehavior ?? "suffix";
|
|
6307
|
-
const missingSemanticNameBehavior = options.missingSemanticNameBehavior ?? "
|
|
6512
|
+
const missingSemanticNameBehavior = options.missingSemanticNameBehavior ?? "error";
|
|
6308
6513
|
const warn = options.warn;
|
|
6309
6514
|
const vueFilesPathMap = options.vueFilesPathMap;
|
|
6310
6515
|
const wrapperSearchRoots = options.wrapperSearchRoots ?? [];
|
|
@@ -6807,7 +7012,7 @@ function createBuildProcessorPlugin(options) {
|
|
|
6807
7012
|
customPomImportNameCollisionBehavior,
|
|
6808
7013
|
testIdAttribute,
|
|
6809
7014
|
nameCollisionBehavior,
|
|
6810
|
-
missingSemanticNameBehavior,
|
|
7015
|
+
missingSemanticNameBehavior = "error",
|
|
6811
7016
|
existingIdBehavior,
|
|
6812
7017
|
nativeWrappers,
|
|
6813
7018
|
excludedComponents,
|
|
@@ -6993,6 +7198,10 @@ function createBuildProcessorPlugin(options) {
|
|
|
6993
7198
|
this.error(`callout.ts not found at ${calloutPath}. Ensure it is included in the build.`);
|
|
6994
7199
|
}
|
|
6995
7200
|
this.addWatchFile(calloutPath);
|
|
7201
|
+
const floatingUiCalloutPath = path.resolve(path.dirname(basePageClassPath), "floating-ui-callout.ts");
|
|
7202
|
+
if (fs.existsSync(floatingUiCalloutPath)) {
|
|
7203
|
+
this.addWatchFile(floatingUiCalloutPath);
|
|
7204
|
+
}
|
|
6996
7205
|
},
|
|
6997
7206
|
async buildEnd(error) {
|
|
6998
7207
|
if (error) {
|
|
@@ -7056,7 +7265,7 @@ function createDevProcessorPlugin(options) {
|
|
|
7056
7265
|
customPomImportAliases,
|
|
7057
7266
|
customPomImportNameCollisionBehavior,
|
|
7058
7267
|
nameCollisionBehavior = "suffix",
|
|
7059
|
-
missingSemanticNameBehavior,
|
|
7268
|
+
missingSemanticNameBehavior = "error",
|
|
7060
7269
|
existingIdBehavior,
|
|
7061
7270
|
testIdAttribute,
|
|
7062
7271
|
routerAwarePoms,
|
|
@@ -7378,7 +7587,8 @@ function createDevProcessorPlugin(options) {
|
|
|
7378
7587
|
watchedPluginGlob,
|
|
7379
7588
|
basePageClassPath,
|
|
7380
7589
|
path.resolve(runtimeDir, "pointer.ts"),
|
|
7381
|
-
path.resolve(runtimeDir, "callout.ts")
|
|
7590
|
+
path.resolve(runtimeDir, "callout.ts"),
|
|
7591
|
+
path.resolve(runtimeDir, "floating-ui-callout.ts")
|
|
7382
7592
|
]);
|
|
7383
7593
|
scheduleVueFileRegenLocal = (filePath, source) => {
|
|
7384
7594
|
pendingChangedVueFiles.add(filePath);
|
|
@@ -7510,12 +7720,20 @@ function generateTestIdsModule(componentTestIds) {
|
|
|
7510
7720
|
});
|
|
7511
7721
|
});
|
|
7512
7722
|
}
|
|
7723
|
+
const VIRTUAL_ID = "virtual:testids";
|
|
7724
|
+
const RESOLVED_ID = `\0${VIRTUAL_ID}`;
|
|
7513
7725
|
function createTestIdsVirtualModulesPlugin(componentTestIds) {
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7726
|
+
return {
|
|
7727
|
+
name: "vue-pom-generator:virtual-testids",
|
|
7728
|
+
resolveId(id) {
|
|
7729
|
+
if (id === VIRTUAL_ID)
|
|
7730
|
+
return RESOLVED_ID;
|
|
7731
|
+
},
|
|
7732
|
+
load(id) {
|
|
7733
|
+
if (id === RESOLVED_ID)
|
|
7734
|
+
return generateTestIdsModule(componentTestIds);
|
|
7735
|
+
}
|
|
7736
|
+
};
|
|
7519
7737
|
}
|
|
7520
7738
|
function createSupportPlugins(options) {
|
|
7521
7739
|
const {
|
|
@@ -7531,7 +7749,7 @@ function createSupportPlugins(options) {
|
|
|
7531
7749
|
getSourceDirs,
|
|
7532
7750
|
getWrapperSearchRoots,
|
|
7533
7751
|
nameCollisionBehavior = "suffix",
|
|
7534
|
-
missingSemanticNameBehavior,
|
|
7752
|
+
missingSemanticNameBehavior = "error",
|
|
7535
7753
|
existingIdBehavior,
|
|
7536
7754
|
outDir,
|
|
7537
7755
|
emitLanguages,
|
|
@@ -8046,12 +8264,12 @@ function assertErrorBehavior(value, name) {
|
|
|
8046
8264
|
}
|
|
8047
8265
|
function resolveMissingSemanticNameBehavior(value) {
|
|
8048
8266
|
if (!value) {
|
|
8049
|
-
return "
|
|
8267
|
+
return "error";
|
|
8050
8268
|
}
|
|
8051
8269
|
if (value === "ignore" || value === "error") {
|
|
8052
8270
|
return value;
|
|
8053
8271
|
}
|
|
8054
|
-
return value.missingSemanticNameBehavior ?? "
|
|
8272
|
+
return value.missingSemanticNameBehavior ?? "error";
|
|
8055
8273
|
}
|
|
8056
8274
|
function readPackageJson(projectRoot) {
|
|
8057
8275
|
const packageJsonPath = path.join(projectRoot, "package.json");
|