quiver-cli 1.2.0 → 1.3.1
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/dist/cli.js +237 -10
- package/package.json +1 -1
- package/template/.agents/skills/agent-browser/SKILL.md +1 -0
- package/template/.agents/skills/apps/skybridge/SKILL.md +2 -0
- package/template/.agents/skills/data/prisma-cli/SKILL.md +25 -7
- package/template/.agents/skills/data/prisma-cli/references/agent-safety.md +27 -0
- package/template/.agents/skills/data/prisma-cli/references/complete.md +22 -0
- package/template/.agents/skills/data/prisma-cli/references/db-push.md +2 -0
- package/template/.agents/skills/data/prisma-cli/references/init.md +3 -0
- package/template/.agents/skills/data/prisma-cli/references/mcp.md +2 -1
- package/template/.agents/skills/data/prisma-cli/references/migrate-dev.md +1 -1
- package/template/.agents/skills/data/prisma-cli/references/migrate-reset.md +2 -0
- package/template/.agents/skills/data/prisma-client-api/SKILL.md +1 -1
- package/template/.agents/skills/data/prisma-client-api/references/constructor.md +13 -0
- package/template/.agents/skills/data/prisma-client-api/references/raw-queries.md +4 -0
- package/template/.agents/skills/design/impeccable/SKILL.md +5 -5
- package/template/.agents/skills/design/impeccable/reference/android.md +6 -0
- package/template/.agents/skills/design/impeccable/reference/animate.md +3 -0
- package/template/.agents/skills/design/impeccable/reference/bolder.md +3 -1
- package/template/.agents/skills/design/impeccable/reference/craft-floor.md +2 -0
- package/template/.agents/skills/design/impeccable/reference/critique.md +23 -5
- package/template/.agents/skills/design/impeccable/reference/degraded/asset-producer.md +15 -68
- package/template/.agents/skills/design/impeccable/reference/degraded/finish-reviewer.md +13 -12
- package/template/.agents/skills/design/impeccable/reference/distill.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/doctor.md +1 -0
- package/template/.agents/skills/design/impeccable/reference/document.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/extract.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/hooks.md +17 -11
- package/template/.agents/skills/design/impeccable/reference/init.md +9 -3
- package/template/.agents/skills/design/impeccable/reference/ios.md +6 -0
- package/template/.agents/skills/design/impeccable/reference/new-work.md +69 -29
- package/template/.agents/skills/design/impeccable/reference/overdrive.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/polish.md +13 -5
- package/template/.agents/skills/design/impeccable/reference/quieter.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/routing.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/visualize.md +21 -22
- package/template/.agents/skills/design/impeccable/scripts/build-phase.mjs +1022 -0
- package/template/.agents/skills/design/impeccable/scripts/comp-diff.mjs +391 -0
- package/template/.agents/skills/design/impeccable/scripts/comp-spec.mjs +513 -0
- package/template/.agents/skills/design/impeccable/scripts/concept-seed.mjs +297 -41
- package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +10 -19
- package/template/.agents/skills/design/impeccable/scripts/context.mjs +124 -9
- package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +279 -19
- package/template/.agents/skills/design/impeccable/scripts/data/font-index-failures.json +121 -0
- package/template/.agents/skills/design/impeccable/scripts/data/font-index.json +1 -0
- package/template/.agents/skills/design/impeccable/scripts/detect.mjs +9 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +192 -11
- package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +10 -16
- package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +339 -11
- package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +1482 -722
- package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns.mjs +1 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +64 -2
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +580 -29
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +62 -7
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +31 -7
- package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +23 -22
- package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +18 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +471 -370
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/color.mjs +474 -2
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/constants.mjs +17 -2
- package/template/.agents/skills/design/impeccable/scripts/doctor.mjs +14 -21
- package/template/.agents/skills/design/impeccable/scripts/embed-prompt.mjs +81 -48
- package/template/.agents/skills/design/impeccable/scripts/font-match.mjs +457 -0
- package/template/.agents/skills/design/impeccable/scripts/generate-image.mjs +219 -12
- package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +93 -15
- package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +41 -19
- package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +507 -117
- package/template/.agents/skills/design/impeccable/scripts/hook.mjs +10 -9
- package/template/.agents/skills/design/impeccable/scripts/lib/concept-catalog.mjs +40 -1
- package/template/.agents/skills/design/impeccable/scripts/lib/design-parser.mjs +120 -82
- package/template/.agents/skills/design/impeccable/scripts/lib/font-fingerprint.mjs +564 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/font-index.mjs +130 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/hero-checks.mjs +246 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/image-metrics.mjs +306 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +41 -59
- package/template/.agents/skills/design/impeccable/scripts/lib/is-generated.mjs +5 -2
- package/template/.agents/skills/design/impeccable/scripts/lib/live-path-globs.mjs +37 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/open-system-browser.mjs +26 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/png.mjs +281 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/raster.mjs +194 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/roll-selection.mjs +26 -19
- package/template/.agents/skills/design/impeccable/scripts/lib/staleness-deep.mjs +31 -3
- package/template/.agents/skills/design/impeccable/scripts/lib/staleness.mjs +93 -17
- package/template/.agents/skills/design/impeccable/scripts/lib/surface-briefs.mjs +9 -11
- package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +31 -2
- package/template/.agents/skills/design/impeccable/scripts/live/project-ignores.mjs +139 -0
- package/template/.agents/skills/design/impeccable/scripts/live/svelte-ast.mjs +10 -2
- package/template/.agents/skills/design/impeccable/scripts/live/svelte-component.mjs +26 -2
- package/template/.agents/skills/design/impeccable/scripts/live/sveltekit-adapter.mjs +15 -27
- package/template/.agents/skills/design/impeccable/scripts/live/tanstack-adapter.mjs +4 -25
- package/template/.agents/skills/design/impeccable/scripts/live/ui-surfaces.mjs +75 -0
- package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +21 -37
- package/template/.agents/skills/design/impeccable/scripts/live-browser-ignores.js +242 -0
- package/template/.agents/skills/design/impeccable/scripts/live-browser.js +62 -23
- package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +68 -112
- package/template/.agents/skills/design/impeccable/scripts/live-copy-edit-agent.mjs +132 -15
- package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +2 -42
- package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +5 -4
- package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +56 -19
- package/template/.agents/skills/design/impeccable/scripts/live.mjs +12 -37
- package/template/.agents/skills/design/impeccable/scripts/pin.mjs +8 -5
- package/template/.agents/skills/design/impeccable/scripts/serve-question.mjs +1006 -155
- package/template/.agents/skills/design/shadcn/rules/chat.md +26 -0
- package/template/.agents/skills/hono/SKILL.md +17 -3
- package/template/.agents/skills/integrations/langfuse/SKILL.md +8 -4
- package/template/.agents/skills/integrations/langfuse/references/cli.md +1 -1
- package/template/.agents/skills/integrations/langfuse/references/create-dataset.md +35 -0
- package/template/.agents/skills/integrations/langfuse/references/error-analysis.md +9 -10
- package/template/.agents/skills/integrations/langfuse/references/judge-calibration.md +8 -6
- package/template/.agents/skills/integrations/langfuse/references/sdk-upgrade.md +34 -0
- package/template/.agents/skills/integrations/langfuse/references/setting-up-evals.md +65 -0
- package/template/.agents/skills/integrations/langfuse/references/skill-feedback.md +20 -40
- package/template/.agents/skills/integrations/langfuse/references/user-feedback.md +4 -30
- package/template/.agents/skills/integrations/langfuse/references/v4-project-migration.md +3 -1
- package/template/.agents/skills/supabase/CHANGELOG.md +7 -0
- package/template/.agents/skills/supabase/SKILL.md +5 -1
- package/template/.agents/skills/writing/humanizer/LICENSE +21 -0
- package/template/.agents/skills/writing/humanizer/README.md +209 -0
- package/template/.agents/skills/writing/humanizer/SKILL.md +173 -338
- package/template/.agents/upstreams.json +21 -22
- package/template/.agents/skills/design/impeccable/scripts/live/ui-core.mjs +0 -180
|
@@ -47,18 +47,33 @@ import {
|
|
|
47
47
|
|
|
48
48
|
// Top-level keys any reader honors: `hook` and `detector` subtrees (hook-lib's
|
|
49
49
|
// readConfig), `updateCheck` (context.mjs), `projectRoots` (context.mjs's
|
|
50
|
-
// monorepo resolution),
|
|
51
|
-
// are allowed as conventional
|
|
50
|
+
// monorepo resolution), `buildPath` (context.mjs's build-path directive), plus
|
|
51
|
+
// `stalenessCheck` below. `$schema` and `version` are allowed as conventional
|
|
52
|
+
// metadata nobody reads.
|
|
52
53
|
const KNOWN_CONFIG_KEYS = new Set([
|
|
53
54
|
'hook',
|
|
54
55
|
'detector',
|
|
55
56
|
'updateCheck',
|
|
56
57
|
'stalenessCheck',
|
|
57
58
|
'projectRoots',
|
|
59
|
+
'buildPath',
|
|
58
60
|
'$schema',
|
|
59
61
|
'version',
|
|
60
62
|
]);
|
|
61
63
|
|
|
64
|
+
// The only two values context.mjs and new-work honor. A near miss reads as a
|
|
65
|
+
// working preference and silently rides the opposite path, so it is worth
|
|
66
|
+
// reporting rather than coercing.
|
|
67
|
+
const BUILD_PATH_VALUES = Object.freeze(['comp', 'code']);
|
|
68
|
+
|
|
69
|
+
// Evidence that this project does the kind of work `buildPath` governs. A
|
|
70
|
+
// project that only ever ran polish or audit has no use for the setting and
|
|
71
|
+
// should never be told it exists. Two stats, so Tier 1 can afford it.
|
|
72
|
+
const DIRECTION_WORK_PATHS = Object.freeze([
|
|
73
|
+
path.join('.impeccable', 'surfaces'),
|
|
74
|
+
path.join('.impeccable', 'mocks', 'decision'),
|
|
75
|
+
]);
|
|
76
|
+
|
|
62
77
|
// `detector` is a closed set, so a typo here is worth reporting. `hook` is not
|
|
63
78
|
// checked: it carries runtime settings from several writers and the false
|
|
64
79
|
// positive rate would outweigh the catch.
|
|
@@ -325,6 +340,20 @@ export function checkConfig({ projectRoot, repoRoot }) {
|
|
|
325
340
|
}));
|
|
326
341
|
}
|
|
327
342
|
|
|
343
|
+
if (Object.prototype.hasOwnProperty.call(raw, 'buildPath')
|
|
344
|
+
&& !BUILD_PATH_VALUES.includes(raw.buildPath)) {
|
|
345
|
+
findings.push(finding({
|
|
346
|
+
id: 'config-invalid-build-path',
|
|
347
|
+
artifact: 'config.json',
|
|
348
|
+
filePath: rel,
|
|
349
|
+
severity: 'mention',
|
|
350
|
+
summary: `${rel} sets \`buildPath\` to ${JSON.stringify(raw.buildPath)}, which nothing reads. `
|
|
351
|
+
+ `The values are ${BUILD_PATH_VALUES.map((value) => `\`${value}\``).join(' and ')}.`,
|
|
352
|
+
fix: 'Report the value. An unread `buildPath` does not fall back to the other path; '
|
|
353
|
+
+ 'it falls back to the default, so a project meaning `code` has been building comp-led.',
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
356
|
+
|
|
328
357
|
const detector = raw.detector;
|
|
329
358
|
if (detector && typeof detector === 'object' && !Array.isArray(detector)) {
|
|
330
359
|
const unknownDetector = Object.keys(detector).filter((key) => !KNOWN_DETECTOR_KEYS.has(key));
|
|
@@ -345,6 +374,47 @@ export function checkConfig({ projectRoot, repoRoot }) {
|
|
|
345
374
|
return findings;
|
|
346
375
|
}
|
|
347
376
|
|
|
377
|
+
/**
|
|
378
|
+
* No recorded build-path preference on a project that plainly does visual
|
|
379
|
+
* direction work. Not drift in the usual sense: the setting is newer than the
|
|
380
|
+
* project, so every project that predates it lands here at once. That is why
|
|
381
|
+
* it is gated twice, on a product record and on evidence of the work the
|
|
382
|
+
* setting governs, and why it says the choice rather than assuming a harness
|
|
383
|
+
* can make it. Image generation is the real precondition and this module
|
|
384
|
+
* cannot see it: a harness-native image tool leaves no trace on disk, so the
|
|
385
|
+
* finding hands the question to the one reader that knows.
|
|
386
|
+
*/
|
|
387
|
+
export function checkBuildPathUnset({ projectRoot, repoRoot, product }) {
|
|
388
|
+
if (!projectRoot || !product) return [];
|
|
389
|
+
const roots = [...new Set([projectRoot, repoRoot].filter(Boolean).map((root) => path.resolve(root)))];
|
|
390
|
+
|
|
391
|
+
for (const root of roots) {
|
|
392
|
+
for (const name of ['config.json', 'config.local.json']) {
|
|
393
|
+
const raw = readJson(path.join(root, '.impeccable', name));
|
|
394
|
+
// Any declared value ends this, valid or not: an invalid one already has
|
|
395
|
+
// its own finding and two reports of one key is noise.
|
|
396
|
+
if (raw && Object.prototype.hasOwnProperty.call(raw, 'buildPath')) return [];
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const evidence = DIRECTION_WORK_PATHS.filter((rel) => fs.existsSync(path.join(projectRoot, rel)));
|
|
401
|
+
if (!evidence.length) return [];
|
|
402
|
+
|
|
403
|
+
return [finding({
|
|
404
|
+
id: 'config-build-path-unset',
|
|
405
|
+
artifact: 'config.json',
|
|
406
|
+
filePath: '.impeccable/config.json',
|
|
407
|
+
severity: 'mention',
|
|
408
|
+
summary: 'This project has run visual direction work but records no `buildPath`, '
|
|
409
|
+
+ 'so every direction round takes the comp-first default without anyone having chosen it.',
|
|
410
|
+
fix: 'Only when image generation exists in your tool surface, offer the choice once: '
|
|
411
|
+
+ '**comp-first** (an image sets the bar before any code; bolder composition, slower) or '
|
|
412
|
+
+ '**code-first** (build directly; ambition carried by the direction contract; leaner, faster). '
|
|
413
|
+
+ 'Write the answer to `.impeccable/config.json` as `"buildPath": "comp"` or `"buildPath": "code"`, '
|
|
414
|
+
+ 'merging with the keys already there. Without image generation there is no choice to record: stay silent.',
|
|
415
|
+
})];
|
|
416
|
+
}
|
|
417
|
+
|
|
348
418
|
// ─── Surface briefs ────────────────────────────────────────────────────────
|
|
349
419
|
|
|
350
420
|
/**
|
|
@@ -418,40 +488,46 @@ export function describeWorkspaceContext(candidates = []) {
|
|
|
418
488
|
// ─── Tier 1 orchestration ──────────────────────────────────────────────────
|
|
419
489
|
|
|
420
490
|
/**
|
|
421
|
-
* Everything a boot can afford
|
|
422
|
-
*
|
|
491
|
+
* Everything a boot can afford, grouped by artifact so deeper reports can
|
|
492
|
+
* interleave their own checks without rebuilding this policy. `ctx` is the
|
|
493
|
+
* loadContext result; `extras` carries values the caller already computed so
|
|
494
|
+
* nothing is recomputed here.
|
|
423
495
|
*/
|
|
424
|
-
export function
|
|
425
|
-
if (!ctx) return
|
|
496
|
+
export function collectBootFindingGroups(ctx, extras = {}) {
|
|
497
|
+
if (!ctx) return {};
|
|
426
498
|
const projectRoot = ctx.projectRoot || process.cwd();
|
|
427
|
-
const absProductPath = extras.absProductPath || null;
|
|
428
499
|
const absDesignPath = extras.absDesignPath || null;
|
|
429
500
|
|
|
430
|
-
return
|
|
431
|
-
|
|
501
|
+
return {
|
|
502
|
+
product: checkProduct(ctx.product, ctx.productPath || 'PRODUCT.md'),
|
|
432
503
|
// Only checked once a PRODUCT.md exists. Without one the boot already
|
|
433
504
|
// emits NO_PRODUCT_MD and routes into init, which asks for the platform
|
|
434
505
|
// directly; a second signal saying the same thing is noise.
|
|
435
|
-
|
|
506
|
+
nativePlatform: ctx.product
|
|
436
507
|
? checkNativePlatformEvidence({
|
|
437
508
|
projectRoot,
|
|
438
509
|
platform: ctx.platform,
|
|
439
510
|
product: ctx.product,
|
|
440
511
|
productPath: ctx.productPath,
|
|
441
512
|
})
|
|
442
|
-
: []
|
|
443
|
-
|
|
513
|
+
: [],
|
|
514
|
+
designSidecar: checkDesignSidecar({
|
|
444
515
|
designPath: absDesignPath,
|
|
445
516
|
sidecarCandidates: extras.sidecarCandidates || [],
|
|
446
517
|
projectRoot,
|
|
447
518
|
}),
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
519
|
+
config: checkConfig({ projectRoot, repoRoot: ctx.repoRoot }),
|
|
520
|
+
buildPath: checkBuildPathUnset({ projectRoot, repoRoot: ctx.repoRoot, product: ctx.product }),
|
|
521
|
+
surfaceBriefs: checkSurfaceBriefs({ candidates: ctx.surfaceBriefCandidates, projectRoot }),
|
|
522
|
+
projectRoots: extras.projectRootPatterns
|
|
451
523
|
? checkProjectRoots({
|
|
452
524
|
patterns: extras.projectRootPatterns,
|
|
453
525
|
candidates: extras.targetCandidates || [],
|
|
454
526
|
})
|
|
455
|
-
: []
|
|
456
|
-
|
|
527
|
+
: [],
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function collectBootFindings(ctx, extras = {}) {
|
|
532
|
+
return Object.values(collectBootFindingGroups(ctx, extras)).flat();
|
|
457
533
|
}
|
|
@@ -8,6 +8,12 @@ export function getSurfaceBriefDir(projectRoot) {
|
|
|
8
8
|
return path.join(projectRoot, '.impeccable', 'surfaces');
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
function normalizeRouteTarget(route) {
|
|
12
|
+
if (!route.startsWith('/') || route.includes('..')) return null;
|
|
13
|
+
const normalized = route.split(/[?#]/, 1)[0].replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/';
|
|
14
|
+
return `route:${normalized}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
export function normalizeSurfaceTarget(target, { projectRoot = process.cwd() } = {}) {
|
|
12
18
|
if (!target || typeof target !== 'string' || !target.trim()) return null;
|
|
13
19
|
const trimmed = target.trim();
|
|
@@ -21,21 +27,13 @@ export function normalizeSurfaceTarget(target, { projectRoot = process.cwd() } =
|
|
|
21
27
|
return null;
|
|
22
28
|
}
|
|
23
29
|
}
|
|
24
|
-
if (/^route:/i.test(trimmed))
|
|
25
|
-
|
|
26
|
-
if (!route.startsWith('/') || route.includes('..')) return null;
|
|
27
|
-
const normalizedRoute = route.split(/[?#]/, 1)[0].replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/';
|
|
28
|
-
return `route:${normalizedRoute}`;
|
|
29
|
-
}
|
|
30
|
-
if (trimmed === '/') return 'route:/';
|
|
30
|
+
if (/^route:/i.test(trimmed)) return normalizeRouteTarget(trimmed.slice(trimmed.indexOf(':') + 1).trim());
|
|
31
|
+
if (trimmed === '/') return normalizeRouteTarget(trimmed);
|
|
31
32
|
if (trimmed.startsWith('/')) {
|
|
32
33
|
const absolute = path.resolve(trimmed);
|
|
33
34
|
const relativeToProject = path.relative(projectRoot, absolute);
|
|
34
35
|
const isProjectFile = relativeToProject && !relativeToProject.startsWith('..') && !path.isAbsolute(relativeToProject);
|
|
35
|
-
if (!isProjectFile && !fs.existsSync(absolute)
|
|
36
|
-
const normalizedRoute = trimmed.split(/[?#]/, 1)[0].replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/';
|
|
37
|
-
return `route:${normalizedRoute}`;
|
|
38
|
-
}
|
|
36
|
+
if (!isProjectFile && !fs.existsSync(absolute)) return normalizeRouteTarget(trimmed);
|
|
39
37
|
}
|
|
40
38
|
const abs = path.isAbsolute(trimmed) ? trimmed : path.resolve(projectRoot, trimmed);
|
|
41
39
|
const rel = path.relative(projectRoot, abs);
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
+
import { LIVE_CHROME_MOUNT_CONTRACT, LIVE_UI_SURFACES } from './ui-surfaces.mjs';
|
|
5
|
+
|
|
4
6
|
export const LIVE_BROWSER_SCRIPT_PARTS = Object.freeze([
|
|
5
7
|
Object.freeze({ name: 'session-state', file: 'live-browser-session.js' }),
|
|
6
8
|
Object.freeze({ name: 'dom-helpers', file: 'live-browser-dom.js' }),
|
|
9
|
+
Object.freeze({ name: 'project-ignores', file: 'live-browser-ignores.js' }),
|
|
7
10
|
Object.freeze({ name: 'browser-ui', file: 'live-browser.js' }),
|
|
8
11
|
]);
|
|
9
12
|
|
|
@@ -32,7 +35,25 @@ export function readLiveBrowserScriptParts(parts, readFile = (filePath) => fs.re
|
|
|
32
35
|
}));
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
export function assembleLiveBrowserScript({
|
|
38
|
+
export function assembleLiveBrowserScript({
|
|
39
|
+
token,
|
|
40
|
+
port,
|
|
41
|
+
vocabulary,
|
|
42
|
+
commandPrefix = '/',
|
|
43
|
+
appRoot = null,
|
|
44
|
+
parts,
|
|
45
|
+
// Defaulted rather than threaded through live-server.mjs: the browser bundle
|
|
46
|
+
// must always carry the canonical inventory, and a default makes that true by
|
|
47
|
+
// construction instead of by every caller remembering to pass it. Overridable
|
|
48
|
+
// so tests can assemble with a stand-in.
|
|
49
|
+
uiSurfaces = LIVE_UI_SURFACES,
|
|
50
|
+
mountContract = LIVE_CHROME_MOUNT_CONTRACT,
|
|
51
|
+
// Project detector waivers ({ ignoreRules, ignoreValues, roots }), read from
|
|
52
|
+
// .impeccable config by live-server.mjs. live-browser-ignores.js resolves
|
|
53
|
+
// them against the page when a detect scan starts, so the overlay filters
|
|
54
|
+
// the same findings the CLI and the edit hook do (issue #639).
|
|
55
|
+
projectIgnores = null,
|
|
56
|
+
}) {
|
|
36
57
|
const prelude =
|
|
37
58
|
`window.__IMPECCABLE_TOKEN__ = '${token}';\n` +
|
|
38
59
|
`window.__IMPECCABLE_PORT__ = ${port};\n` +
|
|
@@ -44,7 +65,15 @@ export function assembleLiveBrowserScript({ token, port, vocabulary, commandPref
|
|
|
44
65
|
`window.__IMPECCABLE_COMMAND_PREFIX__ = ${JSON.stringify(commandPrefix)};\n` +
|
|
45
66
|
// Canonical command vocabulary (values + labels + icons). live-browser.js
|
|
46
67
|
// builds its action picker from this instead of an inline copy.
|
|
47
|
-
`window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(vocabulary)};\n
|
|
68
|
+
`window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(vocabulary)};\n` +
|
|
69
|
+
// Canonical Live chrome inventory from live/ui-surfaces.mjs. live-browser.js
|
|
70
|
+
// is a classic script and cannot import an ES module at runtime, so the list
|
|
71
|
+
// is serialized here and read off the global there. Node consumers (this
|
|
72
|
+
// repo's tests, the impeccable-site Live UI lab) import the module directly,
|
|
73
|
+
// which is what keeps the two from drifting.
|
|
74
|
+
`window.__IMPECCABLE_LIVE_UI_SURFACES__ = ${JSON.stringify(uiSurfaces)};\n` +
|
|
75
|
+
`window.__IMPECCABLE_LIVE_MOUNT_CONTRACT__ = ${JSON.stringify(mountContract)};\n` +
|
|
76
|
+
`window.__IMPECCABLE_PROJECT_IGNORES__ = ${JSON.stringify(projectIgnores)};\n`;
|
|
48
77
|
|
|
49
78
|
const body = parts.map((part) => {
|
|
50
79
|
const file = part.file || path.basename(part.path || '');
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project detector waivers for the live overlay (issue #639, hardened in the
|
|
3
|
+
* PR #645 follow-up). One place decides what the /live.js prelude serializes
|
|
4
|
+
* as window.__IMPECCABLE_PROJECT_IGNORES__:
|
|
5
|
+
*
|
|
6
|
+
* ignoreRules detector.ignoreRules, unioned across every live root.
|
|
7
|
+
* ignoreValues detector.ignoreValues entries ({rule, value, files?}),
|
|
8
|
+
* deduped across roots; createdAt/reason stay local.
|
|
9
|
+
* ignoreFiles detector.ignoreFiles globs, unioned across roots, so a
|
|
10
|
+
* wholly waived page scans to zero findings in the overlay
|
|
11
|
+
* just as it reports nothing through the CLI and the hook.
|
|
12
|
+
* roots served-root prefixes derived from the inject config's own
|
|
13
|
+
* `files` globs. Never derived from the ignore globs: one
|
|
14
|
+
* entry scoped to prototype/library/** would lend
|
|
15
|
+
* prototype/library/ as a candidate prefix to every page,
|
|
16
|
+
* and that rule would suppress site-wide (issue #639).
|
|
17
|
+
* pageFiles the inject config's `files` expanded to real project
|
|
18
|
+
* files, so the browser can resolve a URL to the one file it
|
|
19
|
+
* actually serves instead of trying every root (PR #645
|
|
20
|
+
* review: with src/ and public/ both served, /foo.html must
|
|
21
|
+
* not borrow src/foo.html's waivers while actually serving
|
|
22
|
+
* public/foo.html).
|
|
23
|
+
*
|
|
24
|
+
* Config is read from every root the live session spans: the appRoot the
|
|
25
|
+
* server chdir'd onto, plus contextRoot and repoRoot when they differ. The
|
|
26
|
+
* edit hook keys the same config at the session cwd (the repo root in a
|
|
27
|
+
* monorepo, via resolveCacheCwd), and `impeccable detect` reads it from its
|
|
28
|
+
* invocation cwd, so reading only the appRoot silently dropped every waiver
|
|
29
|
+
* in exactly the monorepo layouts the roots manifest exists for. Reading is
|
|
30
|
+
* additive across roots, matching readConfig's own union of config.json and
|
|
31
|
+
* config.local.json.
|
|
32
|
+
*
|
|
33
|
+
* In a monorepo, roots and pageFiles are serialized repo-relative (the
|
|
34
|
+
* appRoot's path inside the repo is prefixed), so waivers spelled from
|
|
35
|
+
* either root match through the resolver's suffix expansion.
|
|
36
|
+
*/
|
|
37
|
+
import fs from 'node:fs';
|
|
38
|
+
import path from 'node:path';
|
|
39
|
+
import { readConfig } from '../hook-lib.mjs';
|
|
40
|
+
import { resolveFiles } from '../live-inject.mjs';
|
|
41
|
+
import { resolveLiveConfigPath } from '../lib/impeccable-paths.mjs';
|
|
42
|
+
|
|
43
|
+
// Serializing thousands of page identities into every /live.js response
|
|
44
|
+
// helps nobody; past this cap pageFiles is omitted and the resolver falls
|
|
45
|
+
// back to the served-root common ancestor, which is correct, just less
|
|
46
|
+
// precise about cross-root duplicates.
|
|
47
|
+
const PAGE_FILES_CAP = 500;
|
|
48
|
+
|
|
49
|
+
export function collectProjectDetectorIgnores({ appRoot, contextRoot, repoRoot, scriptsDir } = {}) {
|
|
50
|
+
const configRoots = [];
|
|
51
|
+
for (const dir of [appRoot, contextRoot, repoRoot]) {
|
|
52
|
+
if (typeof dir !== 'string' || !dir) continue;
|
|
53
|
+
const resolved = path.resolve(dir);
|
|
54
|
+
if (!configRoots.includes(resolved)) configRoots.push(resolved);
|
|
55
|
+
}
|
|
56
|
+
if (configRoots.length === 0) configRoots.push(process.cwd());
|
|
57
|
+
|
|
58
|
+
const ignoreRules = new Set();
|
|
59
|
+
const ignoreFiles = new Set();
|
|
60
|
+
const valueEntries = new Map();
|
|
61
|
+
for (const dir of configRoots) {
|
|
62
|
+
// readConfig merges config.json with the gitignored config.local.json
|
|
63
|
+
// and type-checks both, exactly as the edit hook reads the same pair.
|
|
64
|
+
const config = readConfig(dir);
|
|
65
|
+
for (const rule of Array.isArray(config.ignoreRules) ? config.ignoreRules : []) {
|
|
66
|
+
if (typeof rule === 'string' && rule.trim()) ignoreRules.add(rule);
|
|
67
|
+
}
|
|
68
|
+
for (const glob of Array.isArray(config.ignoreFiles) ? config.ignoreFiles : []) {
|
|
69
|
+
if (typeof glob === 'string' && glob.trim()) ignoreFiles.add(glob);
|
|
70
|
+
}
|
|
71
|
+
for (const entry of Array.isArray(config.ignoreValues) ? config.ignoreValues : []) {
|
|
72
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
73
|
+
// readConfig already normalized rule/value and folded `file` into
|
|
74
|
+
// `files`; serve only what the browser matches on.
|
|
75
|
+
const serialized = {
|
|
76
|
+
rule: entry.rule,
|
|
77
|
+
value: entry.value,
|
|
78
|
+
...(Array.isArray(entry.files) && entry.files.length > 0 ? { files: entry.files } : {}),
|
|
79
|
+
};
|
|
80
|
+
const key = JSON.stringify([serialized.rule, serialized.value,
|
|
81
|
+
Array.isArray(serialized.files) ? [...serialized.files].sort() : []]);
|
|
82
|
+
if (!valueEntries.has(key)) valueEntries.set(key, serialized);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const served = readLiveServedPages({ appRoot: configRoots[0], repoRoot, scriptsDir });
|
|
87
|
+
return {
|
|
88
|
+
ignoreRules: [...ignoreRules],
|
|
89
|
+
ignoreValues: [...valueEntries.values()],
|
|
90
|
+
ignoreFiles: [...ignoreFiles],
|
|
91
|
+
roots: served.roots,
|
|
92
|
+
pageFiles: served.pageFiles,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readLiveServedPages({ appRoot, repoRoot, scriptsDir }) {
|
|
97
|
+
let live = null;
|
|
98
|
+
try {
|
|
99
|
+
const configPath = resolveLiveConfigPath({ cwd: appRoot, scriptsDir });
|
|
100
|
+
live = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
101
|
+
} catch {
|
|
102
|
+
// No readable inject config: the browser matches URL paths as-is.
|
|
103
|
+
return { roots: [], pageFiles: [] };
|
|
104
|
+
}
|
|
105
|
+
const files = Array.isArray(live?.files)
|
|
106
|
+
? live.files.filter((glob) => typeof glob === 'string' && glob)
|
|
107
|
+
: [];
|
|
108
|
+
|
|
109
|
+
// A monorepo appRoot serializes identities repo-relative, so waivers
|
|
110
|
+
// spelled from either root match through the resolver's suffix expansion.
|
|
111
|
+
let prefix = '';
|
|
112
|
+
if (typeof repoRoot === 'string' && repoRoot) {
|
|
113
|
+
const rel = path.relative(path.resolve(repoRoot), path.resolve(appRoot)).split(path.sep).join('/');
|
|
114
|
+
if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) prefix = `${rel}/`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const roots = [...new Set(files.map((glob) => {
|
|
118
|
+
const wildcardAt = glob.search(/[*?{]/);
|
|
119
|
+
const head = wildcardAt === -1 ? glob : glob.slice(0, wildcardAt);
|
|
120
|
+
const cut = head.lastIndexOf('/');
|
|
121
|
+
return prefix + (cut > -1 ? head.slice(0, cut + 1) : '');
|
|
122
|
+
}))];
|
|
123
|
+
|
|
124
|
+
let pageFiles = [];
|
|
125
|
+
try {
|
|
126
|
+
pageFiles = resolveFiles(appRoot, { ...live, files })
|
|
127
|
+
.filter((rel) => {
|
|
128
|
+
// resolveFiles passes literal entries through even when they do not
|
|
129
|
+
// exist; a missing file is nobody's identity.
|
|
130
|
+
try { return fs.statSync(path.join(appRoot, rel)).isFile(); } catch { return false; }
|
|
131
|
+
})
|
|
132
|
+
.map((rel) => prefix + rel);
|
|
133
|
+
} catch {
|
|
134
|
+
pageFiles = [];
|
|
135
|
+
}
|
|
136
|
+
if (pageFiles.length > PAGE_FILES_CAP) pageFiles = [];
|
|
137
|
+
|
|
138
|
+
return { roots, pageFiles };
|
|
139
|
+
}
|
|
@@ -933,9 +933,17 @@ function collectFreeIdentifierRanges(node, scopes, emit) {
|
|
|
933
933
|
* Build the preview component's script block from a v2 contract, with
|
|
934
934
|
* defaults that keep an unhydrated mount rendering instead of crashing.
|
|
935
935
|
*/
|
|
936
|
+
// `/** @type {...} */` directly before a destructuring declaration is also
|
|
937
|
+
// JSDoc's cast syntax, and Svelte 5.50+ re-emits the annotation in cast form
|
|
938
|
+
// onto the template's own declaration: `var /** @type {...} */ (h1) = root()`.
|
|
939
|
+
// That is a syntax error, so the browser's dynamic import of the variant dies
|
|
940
|
+
// with "Unexpected token '('" and nothing renders. `@typedef` carries the same
|
|
941
|
+
// shape without being a cast. Keep it a typedef;
|
|
942
|
+
// tests/live-svelte-props-script.test.mjs compiles what these builders emit
|
|
943
|
+
// and parses the result.
|
|
936
944
|
export function buildPropsScriptV2(contract) {
|
|
937
945
|
if (!contract || contract.length === 0) {
|
|
938
|
-
return '<script>\n /** @
|
|
946
|
+
return '<script>\n /** @typedef {Record<string, never>} Props */\n let {} = $props();\n</script>\n';
|
|
939
947
|
}
|
|
940
948
|
const defaults = {
|
|
941
949
|
text: "''",
|
|
@@ -957,5 +965,5 @@ export function buildPropsScriptV2(contract) {
|
|
|
957
965
|
const typeFields = contract
|
|
958
966
|
.map((c) => ` ${c.prop}?: ${types[c.kind] ?? 'string'};`)
|
|
959
967
|
.join('\n');
|
|
960
|
-
return `<script>\n /** @
|
|
968
|
+
return `<script>\n /** @typedef {{\n${typeFields}\n }} Props */\n let { ${names} } = $props();\n</script>\n`;
|
|
961
969
|
}
|
|
@@ -154,13 +154,19 @@ export function parseSvelteComponentFile(content) {
|
|
|
154
154
|
return { markup, cssLines, styleBlock };
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
// A JSDoc `@type` directly before a destructuring declaration is JSDoc cast
|
|
158
|
+
// syntax, and Svelte 5.50+ re-emits it onto the template's own declaration as
|
|
159
|
+
// `var /** @type {...} */ (h1) = root()`, which does not parse. The browser's
|
|
160
|
+
// import of the variant then fails with "Unexpected token '('" and the session
|
|
161
|
+
// shows nothing. `@typedef` documents the same shape without being a cast.
|
|
162
|
+
// tests/live-svelte-props-script.test.mjs compiles and parses what this emits.
|
|
157
163
|
function buildPropsScript(contract) {
|
|
158
164
|
if (contract.length === 0) {
|
|
159
|
-
return '<script>\n /** @
|
|
165
|
+
return '<script>\n /** @typedef {Record<string, never>} Props */\n let {} = $props();\n</script>\n';
|
|
160
166
|
}
|
|
161
167
|
const names = contract.map((c) => c.prop).join(', ');
|
|
162
168
|
const typeFields = contract.map((c) => ` ${c.prop}: string;`).join('\n');
|
|
163
|
-
return `<script>\n /** @
|
|
169
|
+
return `<script>\n /** @typedef {{\n${typeFields}\n }} Props */\n let { ${names} } = $props();\n</script>\n`;
|
|
164
170
|
}
|
|
165
171
|
|
|
166
172
|
function buildVariantStub(variantNum, originalWithProps, contract) {
|
|
@@ -1111,6 +1117,24 @@ export function removeSvelteComponentSession(id, cwd = process.cwd()) {
|
|
|
1111
1117
|
* seeded one) used to surface as a red Vite overlay in the user's page plus
|
|
1112
1118
|
* a mount-failure round trip; bounced at publish time it is a private
|
|
1113
1119
|
* agent-side fix with the exact file and line.
|
|
1120
|
+
*
|
|
1121
|
+
* What this deliberately does NOT prove is that the emitted module is valid
|
|
1122
|
+
* JavaScript, and issue #580 was exactly that gap: valid .svelte source whose
|
|
1123
|
+
* generated JS did not parse, so this check passed and the browser's import
|
|
1124
|
+
* failed with "Unexpected token '('". Two reasons it cannot close the gap, both
|
|
1125
|
+
* structural rather than oversights:
|
|
1126
|
+
*
|
|
1127
|
+
* - `generate: false` produces no JS to inspect, and generating it here would
|
|
1128
|
+
* spend a full codegen per variant on the publish path.
|
|
1129
|
+
* - `loadSvelteCompiler` reaches the compiler through createRequire, which
|
|
1130
|
+
* Svelte's export map routes to a prebuilt CJS build. The dev server
|
|
1131
|
+
* imports `src/compiler` instead, and only that path uses the app's
|
|
1132
|
+
* installed printer (esrap). The two can disagree, so even a generated
|
|
1133
|
+
* check here would be checking a different compiler than the one whose
|
|
1134
|
+
* output the browser runs.
|
|
1135
|
+
*
|
|
1136
|
+
* tests/live-svelte-props-script.test.mjs covers the emitted JS, importing the
|
|
1137
|
+
* compiler as ESM so it sees what the dev server sees.
|
|
1114
1138
|
*/
|
|
1115
1139
|
export function compileCheckVariants(id, cwd = process.cwd()) {
|
|
1116
1140
|
const manifest = findSvelteComponentManifest(id, cwd);
|
|
@@ -11,6 +11,8 @@ import crypto from 'node:crypto';
|
|
|
11
11
|
import fs from 'node:fs';
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
|
|
14
|
+
import { firstExistingFile, hasAnyDependency } from './frameworks/detect-utils.mjs';
|
|
15
|
+
|
|
14
16
|
export const SVELTE_LIVE_ROOT_COMPONENT = 'src/lib/impeccable/ImpeccableLiveRoot.svelte';
|
|
15
17
|
export const SVELTE_LAYOUT_MARKER_OPEN = '<!-- impeccable-live-svelte-start -->';
|
|
16
18
|
export const SVELTE_LAYOUT_MARKER_CLOSE = '<!-- impeccable-live-svelte-end -->';
|
|
@@ -45,11 +47,17 @@ export function detectSvelteKitProject(cwd = process.cwd(), config = null) {
|
|
|
45
47
|
&& fileIncludes(path.join(cwd, appHtml), '%sveltekit.head%');
|
|
46
48
|
if (!hasTemplateMarkers) return null;
|
|
47
49
|
|
|
48
|
-
const hasSvelteConfig =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
const hasSvelteConfig = Boolean(firstExistingFile(cwd, [
|
|
51
|
+
'svelte.config.js',
|
|
52
|
+
'svelte.config.mjs',
|
|
53
|
+
'svelte.config.cjs',
|
|
54
|
+
'svelte.config.ts',
|
|
55
|
+
]));
|
|
56
|
+
const hasKitPackage = hasAnyDependency(cwd, [
|
|
57
|
+
'@sveltejs/kit',
|
|
58
|
+
'@sveltejs/vite-plugin-svelte',
|
|
59
|
+
'svelte',
|
|
60
|
+
]);
|
|
53
61
|
if (!hasSvelteConfig && !hasKitPackage) return null;
|
|
54
62
|
|
|
55
63
|
return {
|
|
@@ -260,36 +268,16 @@ function findSvelteKitAppHtml(cwd, config) {
|
|
|
260
268
|
}
|
|
261
269
|
|
|
262
270
|
function findSvelteKitLayout(cwd) {
|
|
263
|
-
|
|
271
|
+
return firstExistingFile(cwd, [
|
|
264
272
|
'src/routes/+layout.svelte',
|
|
265
273
|
'src/routes/(app)/+layout.svelte',
|
|
266
|
-
];
|
|
267
|
-
for (const rel of candidates) {
|
|
268
|
-
if (fs.existsSync(path.join(cwd, rel))) return rel;
|
|
269
|
-
}
|
|
270
|
-
return 'src/routes/+layout.svelte';
|
|
274
|
+
]) || 'src/routes/+layout.svelte';
|
|
271
275
|
}
|
|
272
276
|
|
|
273
277
|
function defaultSvelteLayout() {
|
|
274
278
|
return `<script>\n let { children } = $props();\n</script>\n\n{@render children?.()}\n`;
|
|
275
279
|
}
|
|
276
280
|
|
|
277
|
-
function packageHasSvelteKit(cwd) {
|
|
278
|
-
const file = path.join(cwd, 'package.json');
|
|
279
|
-
if (!fs.existsSync(file)) return false;
|
|
280
|
-
try {
|
|
281
|
-
const pkg = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
282
|
-
const deps = {
|
|
283
|
-
...(pkg.dependencies || {}),
|
|
284
|
-
...(pkg.devDependencies || {}),
|
|
285
|
-
...(pkg.peerDependencies || {}),
|
|
286
|
-
};
|
|
287
|
-
return Boolean(deps['@sveltejs/kit'] || deps['@sveltejs/vite-plugin-svelte'] || deps.svelte);
|
|
288
|
-
} catch {
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
281
|
function fileIncludes(file, text) {
|
|
294
282
|
try {
|
|
295
283
|
return fs.readFileSync(file, 'utf-8').includes(text);
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
import fs from 'node:fs';
|
|
21
21
|
import path from 'node:path';
|
|
22
|
+
|
|
23
|
+
import { firstExistingFile, hasAnyDependency } from './frameworks/detect-utils.mjs';
|
|
22
24
|
import { buildLiveScriptSrc } from './frameworks/script-src.mjs';
|
|
23
25
|
|
|
24
26
|
export const TANSTACK_MARKER_OPEN = '{/* impeccable-live-tanstack-start */}';
|
|
@@ -42,8 +44,8 @@ const START_PACKAGES = [
|
|
|
42
44
|
];
|
|
43
45
|
|
|
44
46
|
export function detectTanStackStartProject(cwd = process.cwd()) {
|
|
45
|
-
if (!
|
|
46
|
-
const rootRoute =
|
|
47
|
+
if (!hasAnyDependency(cwd, START_PACKAGES)) return null;
|
|
48
|
+
const rootRoute = firstExistingFile(cwd, ROOT_ROUTE_CANDIDATES);
|
|
47
49
|
if (!rootRoute) return null;
|
|
48
50
|
|
|
49
51
|
const ext = path.extname(rootRoute);
|
|
@@ -218,29 +220,6 @@ function isManagedComponent(content) {
|
|
|
218
220
|
return String(content || '').includes('impeccable-live-tanstack');
|
|
219
221
|
}
|
|
220
222
|
|
|
221
|
-
function findRootRouteFile(cwd) {
|
|
222
|
-
for (const rel of ROOT_ROUTE_CANDIDATES) {
|
|
223
|
-
if (fs.existsSync(path.join(cwd, rel))) return rel;
|
|
224
|
-
}
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function packageHasTanStackStart(cwd) {
|
|
229
|
-
const file = path.join(cwd, 'package.json');
|
|
230
|
-
if (!fs.existsSync(file)) return false;
|
|
231
|
-
try {
|
|
232
|
-
const pkg = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
233
|
-
const deps = {
|
|
234
|
-
...(pkg.dependencies || {}),
|
|
235
|
-
...(pkg.devDependencies || {}),
|
|
236
|
-
...(pkg.peerDependencies || {}),
|
|
237
|
-
};
|
|
238
|
-
return START_PACKAGES.some((name) => Boolean(deps[name]));
|
|
239
|
-
} catch {
|
|
240
|
-
return false;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
223
|
function relativeImportSpecifier(fromFile, toFile) {
|
|
245
224
|
const rel = path.posix.relative(
|
|
246
225
|
path.posix.dirname(fromFile.split(path.sep).join('/')),
|