@consilioweb/payload-support 6.0.0 → 6.0.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/utils/db.js +20 -0
- package/dist/utils/readSettings.js +150 -0
- package/package.json +4 -3
- package/scripts/copy-subpath-types.mjs +103 -0
- package/scripts/verify-dist-imports.mjs +84 -0
package/dist/utils/db.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function dbFind(payload, slug, options = {}) {
|
|
2
|
+
return payload.find({ collection: slug, ...options });
|
|
3
|
+
}
|
|
4
|
+
function dbFindByID(payload, slug, options) {
|
|
5
|
+
return payload.findByID({ collection: slug, ...options });
|
|
6
|
+
}
|
|
7
|
+
function dbCreate(payload, slug, options) {
|
|
8
|
+
return payload.create({ collection: slug, ...options });
|
|
9
|
+
}
|
|
10
|
+
function dbUpdate(payload, slug, options) {
|
|
11
|
+
return payload.update({ collection: slug, ...options });
|
|
12
|
+
}
|
|
13
|
+
function dbCount(payload, slug, options = {}) {
|
|
14
|
+
return payload.count({ collection: slug, ...options });
|
|
15
|
+
}
|
|
16
|
+
function dbDelete(payload, slug, options) {
|
|
17
|
+
return payload.delete({ collection: slug, ...options });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { dbCount, dbCreate, dbDelete, dbFind, dbFindByID, dbUpdate };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { dbFind } from './db.js';
|
|
2
|
+
import { DEFAULT_TICKETING_FEATURES, projectAutoClose, normalizeFeatures } from './features.js';
|
|
3
|
+
|
|
4
|
+
const SUPPORT_SETTINGS_PREF_KEY = "support-settings";
|
|
5
|
+
const PREF_KEY = SUPPORT_SETTINGS_PREF_KEY;
|
|
6
|
+
const USER_PREFS_KEY_PREFIX = "support-user-prefs";
|
|
7
|
+
const LEGACY_ROUND_ROBIN_KEY = "support-round-robin";
|
|
8
|
+
const SUPPORT_STAFF_SLUG_CONFIG_KEY = "supportStaffCollection";
|
|
9
|
+
function resolveStaffPrefSlug(payload, staffSlug) {
|
|
10
|
+
if (staffSlug) return staffSlug;
|
|
11
|
+
const config = payload.config;
|
|
12
|
+
const registered = config?.custom?.[SUPPORT_STAFF_SLUG_CONFIG_KEY];
|
|
13
|
+
if (typeof registered === "string" && registered) return registered;
|
|
14
|
+
return config?.admin?.user || "users";
|
|
15
|
+
}
|
|
16
|
+
const DEFAULT_SETTINGS = {
|
|
17
|
+
email: { fromAddress: "", fromName: "Support", replyToAddress: "" },
|
|
18
|
+
ai: { provider: "anthropic", model: "claude-haiku-4-5-20251001", enableSentiment: true, enableSynthesis: true, enableSuggestion: true, enableRewrite: true },
|
|
19
|
+
sla: { firstResponseMinutes: 120, resolutionMinutes: 1440, businessHoursOnly: true, escalationEmail: "" },
|
|
20
|
+
autoClose: { enabled: true, daysBeforeClose: 7, reminderDaysBefore: 2 },
|
|
21
|
+
features: { ...DEFAULT_TICKETING_FEATURES }
|
|
22
|
+
};
|
|
23
|
+
const DEFAULT_USER_PREFS = {
|
|
24
|
+
locale: "fr",
|
|
25
|
+
signature: ""
|
|
26
|
+
};
|
|
27
|
+
const settingsCache = /* @__PURE__ */ new Map();
|
|
28
|
+
const SETTINGS_TTL_MS = 6e4;
|
|
29
|
+
const SETTINGS_CACHE_MAX = 8;
|
|
30
|
+
const warnedForeignSettingsRow = /* @__PURE__ */ new Set();
|
|
31
|
+
function invalidateSupportSettingsCache() {
|
|
32
|
+
settingsCache.clear();
|
|
33
|
+
warnedForeignSettingsRow.clear();
|
|
34
|
+
}
|
|
35
|
+
function mergeSupportSettings(stored, base = DEFAULT_SETTINGS) {
|
|
36
|
+
const autoClose = { ...base.autoClose, ...stored?.autoClose };
|
|
37
|
+
return {
|
|
38
|
+
email: { ...base.email, ...stored?.email },
|
|
39
|
+
ai: { ...base.ai, ...stored?.ai },
|
|
40
|
+
sla: { ...base.sla, ...stored?.sla },
|
|
41
|
+
autoClose,
|
|
42
|
+
// `autoClose` / `autoCloseDays` are projections of the block above, so they
|
|
43
|
+
// are recomputed here rather than trusted from whatever was persisted.
|
|
44
|
+
features: projectAutoClose(
|
|
45
|
+
normalizeFeatures({ ...base.features, ...stored?.features }),
|
|
46
|
+
autoClose
|
|
47
|
+
)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async function readSupportSettingsState(payload, staffSlug) {
|
|
51
|
+
const staff = resolveStaffPrefSlug(payload, staffSlug);
|
|
52
|
+
const cached = settingsCache.get(staff);
|
|
53
|
+
if (cached && Date.now() - cached.ts < SETTINGS_TTL_MS) {
|
|
54
|
+
return cached.value;
|
|
55
|
+
}
|
|
56
|
+
let value = {
|
|
57
|
+
settings: mergeSupportSettings(null),
|
|
58
|
+
featuresConfigured: false
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
const prefs = await dbFind(payload, "payload-preferences", {
|
|
62
|
+
// Sibling keys are AND-ed by Payload. The `user.relationTo` clause is the
|
|
63
|
+
// security boundary: without it any authenticated principal can plant a
|
|
64
|
+
// `support-settings` row and own the plugin's server settings.
|
|
65
|
+
where: { key: { equals: PREF_KEY }, "user.relationTo": { equals: staff } },
|
|
66
|
+
// The upsert is scoped per admin user, so several rows can share the key.
|
|
67
|
+
// Sorting makes "last write wins" deterministic instead of arbitrary.
|
|
68
|
+
sort: "-updatedAt",
|
|
69
|
+
limit: 1,
|
|
70
|
+
depth: 0,
|
|
71
|
+
overrideAccess: true
|
|
72
|
+
});
|
|
73
|
+
if (prefs.docs.length > 0) {
|
|
74
|
+
const stored = prefs.docs[0].value;
|
|
75
|
+
const featuresConfigured = !!stored.features && typeof stored.features === "object";
|
|
76
|
+
const settings = mergeSupportSettings(stored);
|
|
77
|
+
if (!featuresConfigured) {
|
|
78
|
+
settings.features.roundRobin = await readLegacyRoundRobin(payload, staff);
|
|
79
|
+
}
|
|
80
|
+
value = { settings, featuresConfigured };
|
|
81
|
+
} else {
|
|
82
|
+
await warnOnForeignSettingsRow(payload, staff);
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
if (settingsCache.size >= SETTINGS_CACHE_MAX && !settingsCache.has(staff)) settingsCache.clear();
|
|
87
|
+
settingsCache.set(staff, { value, ts: Date.now() });
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
async function warnOnForeignSettingsRow(payload, staff) {
|
|
91
|
+
if (warnedForeignSettingsRow.has(staff)) return;
|
|
92
|
+
try {
|
|
93
|
+
const any = await dbFind(payload, "payload-preferences", {
|
|
94
|
+
where: { key: { equals: PREF_KEY } },
|
|
95
|
+
limit: 1,
|
|
96
|
+
depth: 0,
|
|
97
|
+
overrideAccess: true
|
|
98
|
+
});
|
|
99
|
+
if (any.docs.length === 0) return;
|
|
100
|
+
if (warnedForeignSettingsRow.size >= SETTINGS_CACHE_MAX) warnedForeignSettingsRow.clear();
|
|
101
|
+
warnedForeignSettingsRow.add(staff);
|
|
102
|
+
console.warn(
|
|
103
|
+
`[support] A "${PREF_KEY}" preference row exists but none is owned by the "${staff}" collection: the plugin is running on its DEFAULT settings. Either the staff auth collection differs from \`admin.user\`, or the row was written by a principal that is not staff \u2014 in which case it is ignored on purpose.`
|
|
104
|
+
);
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function readSupportSettings(payload, staffSlug) {
|
|
109
|
+
return (await readSupportSettingsState(payload, staffSlug)).settings;
|
|
110
|
+
}
|
|
111
|
+
async function readLegacyRoundRobin(payload, staff) {
|
|
112
|
+
try {
|
|
113
|
+
const prefs = await dbFind(payload, "payload-preferences", {
|
|
114
|
+
where: { key: { equals: LEGACY_ROUND_ROBIN_KEY }, "user.relationTo": { equals: staff } },
|
|
115
|
+
limit: 1,
|
|
116
|
+
depth: 0,
|
|
117
|
+
overrideAccess: true
|
|
118
|
+
});
|
|
119
|
+
if (prefs.docs.length > 0) {
|
|
120
|
+
return prefs.docs[0].value?.enabled === true;
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
return DEFAULT_TICKETING_FEATURES.roundRobin;
|
|
125
|
+
}
|
|
126
|
+
async function readUserPrefs(payload, userId, staffSlug) {
|
|
127
|
+
try {
|
|
128
|
+
const key = `${USER_PREFS_KEY_PREFIX}-${userId}`;
|
|
129
|
+
const prefs = await dbFind(payload, "payload-preferences", {
|
|
130
|
+
where: {
|
|
131
|
+
key: { equals: key },
|
|
132
|
+
"user.relationTo": { equals: resolveStaffPrefSlug(payload, staffSlug) }
|
|
133
|
+
},
|
|
134
|
+
limit: 1,
|
|
135
|
+
depth: 0,
|
|
136
|
+
overrideAccess: true
|
|
137
|
+
});
|
|
138
|
+
if (prefs.docs.length > 0) {
|
|
139
|
+
const stored = prefs.docs[0].value;
|
|
140
|
+
return {
|
|
141
|
+
locale: stored.locale || DEFAULT_USER_PREFS.locale,
|
|
142
|
+
signature: stored.signature ?? DEFAULT_USER_PREFS.signature
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
return { ...DEFAULT_USER_PREFS };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export { DEFAULT_SETTINGS, DEFAULT_USER_PREFS, SUPPORT_SETTINGS_PREF_KEY, SUPPORT_STAFF_SLUG_CONFIG_KEY, invalidateSupportSettingsCache, mergeSupportSettings, readSupportSettings, readSupportSettingsState, readUserPrefs, resolveStaffPrefSlug };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@consilioweb/payload-support",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"description": "Payload CMS plugin — professional support & ticketing system with AI, SLA, time tracking, live chat, and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
"scripts/uninstall.mjs",
|
|
53
53
|
"scripts/uninstall-data.mjs",
|
|
54
54
|
"README.md",
|
|
55
|
-
"LICENSE"
|
|
55
|
+
"LICENSE",
|
|
56
|
+
"scripts"
|
|
56
57
|
],
|
|
57
58
|
"keywords": [
|
|
58
59
|
"payload",
|
|
@@ -137,7 +138,7 @@
|
|
|
137
138
|
}
|
|
138
139
|
},
|
|
139
140
|
"scripts": {
|
|
140
|
-
"build": "tsup && tsc -p tsconfig.types.json && node scripts/copy-subpath-types.mjs",
|
|
141
|
+
"build": "tsup && tsc -p tsconfig.types.json && node scripts/copy-subpath-types.mjs && node scripts/verify-dist-imports.mjs",
|
|
141
142
|
"typecheck": "tsc --noEmit",
|
|
142
143
|
"test": "vitest run",
|
|
143
144
|
"test:watch": "vitest",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copies the declarations emitted by `tsc -p tsconfig.types.json` into dist/,
|
|
3
|
+
* then rewrites their relative import specifiers so they carry an explicit
|
|
4
|
+
* `.js` extension.
|
|
5
|
+
*
|
|
6
|
+
* Why a separate pass: the `bundle: false` tsup entry that emits dist/views/**
|
|
7
|
+
* and dist/components/** cannot generate declarations — turning `dts: true` on
|
|
8
|
+
* for ~100 entries makes rollup-plugin-dts run for well over ten minutes without
|
|
9
|
+
* finishing. Without them, `dist/views.d.ts` re-exports 13 views from paths that
|
|
10
|
+
* carry no declaration (TS7016 under noImplicitAny) and the publicly documented
|
|
11
|
+
* `./components/TicketConversation` subpath has no types at all.
|
|
12
|
+
*
|
|
13
|
+
* Why the extension rewrite: the sources are compiled with
|
|
14
|
+
* `moduleResolution: bundler`, so both tsc and tsup emit extensionless relative
|
|
15
|
+
* specifiers (`from './client'`, `from '../../utils/features'`). A consumer on
|
|
16
|
+
* `moduleResolution: node16 | nodenext` — the recommended setting for an ESM
|
|
17
|
+
* package — then gets `TS2835: Relative import paths need explicit file
|
|
18
|
+
* extensions`, and with the default `skipLibCheck: true` that error is swallowed
|
|
19
|
+
* and every export silently degrades to `any`. tsup's `onSuccess` already does
|
|
20
|
+
* this for the emitted `.js`; this does the same for the `.d.ts` (the copied ones
|
|
21
|
+
* *and* the `dist/views.d.ts` barrel produced by tsup's dts pass).
|
|
22
|
+
*
|
|
23
|
+
* `utils` is copied too: the emitted view declarations reference
|
|
24
|
+
* `../../utils/features`, whose JS is already emitted by the same tsup pass.
|
|
25
|
+
*/
|
|
26
|
+
import { cpSync, existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
27
|
+
import { dirname, join, resolve } from 'node:path'
|
|
28
|
+
|
|
29
|
+
const OUT = '.types-out'
|
|
30
|
+
const SUBPATHS = ['views', 'components', 'utils']
|
|
31
|
+
|
|
32
|
+
if (!existsSync(OUT)) {
|
|
33
|
+
console.error(`[build] "${OUT}" is missing — run \`tsc -p tsconfig.types.json\` before this script.`)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const dir of SUBPATHS) {
|
|
38
|
+
const from = `${OUT}/${dir}`
|
|
39
|
+
if (existsSync(from)) cpSync(from, `dist/${dir}`, { recursive: true })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
rmSync(OUT, { recursive: true, force: true })
|
|
43
|
+
console.log('✓ Copied subpath declarations into dist/')
|
|
44
|
+
|
|
45
|
+
// ─── Explicit .js extensions on relative specifiers ──────────────────────────
|
|
46
|
+
|
|
47
|
+
const HAS_EXTENSION = /\.(js|jsx|mjs|cjs|css|scss|json)$/
|
|
48
|
+
const RELATIVE_SPECIFIER = /((?:from|import)\s*['"])(\.\.?\/[^'"]+?)(['"])/g
|
|
49
|
+
|
|
50
|
+
const errors = []
|
|
51
|
+
|
|
52
|
+
/** Resolve an extensionless specifier against the emitted declarations. */
|
|
53
|
+
function withExtension(fromFile, specifier) {
|
|
54
|
+
const base = resolve(dirname(fromFile), specifier)
|
|
55
|
+
if (existsSync(`${base}.d.ts`)) return `${specifier}.js`
|
|
56
|
+
if (existsSync(join(base, 'index.d.ts'))) return `${specifier}/index.js`
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function rewrite(file) {
|
|
61
|
+
const content = readFileSync(file, 'utf-8')
|
|
62
|
+
const fixed = content.replace(RELATIVE_SPECIFIER, (match, prefix, specifier, suffix) => {
|
|
63
|
+
if (HAS_EXTENSION.test(specifier)) return match
|
|
64
|
+
const resolved = withExtension(file, specifier)
|
|
65
|
+
if (!resolved) {
|
|
66
|
+
errors.push(`${file}: cannot resolve "${specifier}" to an emitted declaration`)
|
|
67
|
+
return match
|
|
68
|
+
}
|
|
69
|
+
return `${prefix}${resolved}${suffix}`
|
|
70
|
+
})
|
|
71
|
+
if (fixed !== content) writeFileSync(file, fixed)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function walkDts(dir) {
|
|
75
|
+
if (!existsSync(dir)) return
|
|
76
|
+
for (const entry of readdirSync(dir)) {
|
|
77
|
+
const path = join(dir, entry)
|
|
78
|
+
if (statSync(path).isDirectory()) {
|
|
79
|
+
walkDts(path)
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
if (path.endsWith('.d.ts')) rewrite(path)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The `./views` barrel is emitted by tsup's dts pass, not by the tsc pass above,
|
|
87
|
+
// but it has the exact same extensionless specifiers — and it is the entry point
|
|
88
|
+
// consumers actually import, so it matters most.
|
|
89
|
+
const VIEWS_BARREL = 'dist/views.d.ts'
|
|
90
|
+
if (!existsSync(VIEWS_BARREL)) {
|
|
91
|
+
console.error(`[build] "${VIEWS_BARREL}" is missing — did the tsup views barrel entry run?`)
|
|
92
|
+
process.exit(1)
|
|
93
|
+
}
|
|
94
|
+
rewrite(VIEWS_BARREL)
|
|
95
|
+
for (const dir of SUBPATHS) walkDts(`dist/${dir}`)
|
|
96
|
+
|
|
97
|
+
if (errors.length > 0) {
|
|
98
|
+
console.error('[build] unresolved relative specifiers in emitted declarations:')
|
|
99
|
+
for (const error of errors) console.error(` - ${error}`)
|
|
100
|
+
process.exit(1)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log('✓ Added explicit .js extensions to relative specifiers in dist/**/*.d.ts')
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Every relative import in `dist/` must resolve to a file that is actually there.
|
|
4
|
+
*
|
|
5
|
+
* This catches one specific, silent failure mode of a multi-pass tsup build.
|
|
6
|
+
* The `bundle: false` pass preserves the source tree and rewrites nothing, so a
|
|
7
|
+
* relative import survives into the emitted file. If the target module is not
|
|
8
|
+
* itself in that pass's `entry` list, it is never emitted as a standalone file —
|
|
9
|
+
* it only exists inlined inside the bundled `dist/index.js`. The import then
|
|
10
|
+
* points at nothing.
|
|
11
|
+
*
|
|
12
|
+
* Nothing in the normal toolchain sees it. `tsc` type-checks the SOURCE tree,
|
|
13
|
+
* where the module exists. Vitest imports from `src/`, same thing. The build
|
|
14
|
+
* itself succeeds — tsup has no reason to object. The failure surfaces only when
|
|
15
|
+
* the HOST application's bundler resolves the published package, which is to say
|
|
16
|
+
* after publication, in someone else's build.
|
|
17
|
+
*
|
|
18
|
+
* `import type` is invisible here on purpose: TypeScript erases it, so it leaves
|
|
19
|
+
* no trace in the emitted file and cannot break anything. Only value imports do.
|
|
20
|
+
* That is why the defect hid for two releases in this package — every other
|
|
21
|
+
* importer of the same module used `import type`.
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
24
|
+
import { dirname, join, resolve } from 'node:path'
|
|
25
|
+
|
|
26
|
+
const DIST = resolve(process.cwd(), 'dist')
|
|
27
|
+
|
|
28
|
+
if (!existsSync(DIST)) {
|
|
29
|
+
console.error('verify-dist-imports: dist/ is missing — run the build first.')
|
|
30
|
+
process.exit(1)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Every emitted JavaScript file, at any depth. */
|
|
34
|
+
function walk(dir) {
|
|
35
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
36
|
+
const path = join(dir, entry.name)
|
|
37
|
+
if (entry.isDirectory()) return walk(path)
|
|
38
|
+
return /\.(js|cjs|mjs)$/.test(entry.name) ? [path] : []
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Relative specifiers only. Bare specifiers are the consumer's dependency
|
|
44
|
+
* problem, not ours, and `node:` builtins always resolve.
|
|
45
|
+
*/
|
|
46
|
+
const SPECIFIER =
|
|
47
|
+
/(?:^|[\s;{(])(?:import|export)\s+(?:[^'"]*?\sfrom\s+)?['"](\.[^'"]+)['"]|require\(\s*['"](\.[^'"]+)['"]\s*\)|import\(\s*['"](\.[^'"]+)['"]\s*\)/g
|
|
48
|
+
|
|
49
|
+
/** Resolve the way Node and a bundler would: as written, then the usual suffixes. */
|
|
50
|
+
function resolves(fromFile, specifier) {
|
|
51
|
+
const base = resolve(dirname(fromFile), specifier)
|
|
52
|
+
const candidates = [
|
|
53
|
+
base,
|
|
54
|
+
`${base}.js`, `${base}.cjs`, `${base}.mjs`,
|
|
55
|
+
join(base, 'index.js'), join(base, 'index.cjs'), join(base, 'index.mjs'),
|
|
56
|
+
]
|
|
57
|
+
return candidates.some((c) => existsSync(c) && statSync(c).isFile())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const broken = []
|
|
61
|
+
for (const file of walk(DIST)) {
|
|
62
|
+
const source = readFileSync(file, 'utf8')
|
|
63
|
+
for (const match of source.matchAll(SPECIFIER)) {
|
|
64
|
+
const specifier = match[1] || match[2] || match[3]
|
|
65
|
+
if (!specifier || resolves(file, specifier)) continue
|
|
66
|
+
broken.push({ file: file.replace(`${process.cwd()}/`, ''), specifier })
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (broken.length === 0) {
|
|
71
|
+
console.log('verify-dist-imports: every relative import in dist/ resolves.')
|
|
72
|
+
process.exit(0)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.error(`verify-dist-imports: ${broken.length} unresolved relative import(s) in dist/.\n`)
|
|
76
|
+
for (const { file, specifier } of broken) {
|
|
77
|
+
console.error(` ${file}\n imports '${specifier}' — not emitted`)
|
|
78
|
+
}
|
|
79
|
+
console.error(
|
|
80
|
+
'\nThe target is almost certainly missing from the `entry` list of the\n' +
|
|
81
|
+
'`bundle: false` pass in tsup.config.ts. Add it there, or move the value\n' +
|
|
82
|
+
'being imported into a module that pass already emits.',
|
|
83
|
+
)
|
|
84
|
+
process.exit(1)
|