@craftpipe/contextpack 1.0.0
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/.contextpackrc.example.json +167 -0
- package/.env.example +5 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +26 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
- package/.github/pull_request_template.md +9 -0
- package/CODE_OF_CONDUCT.md +40 -0
- package/CONTRIBUTING.md +59 -0
- package/LICENSE +21 -0
- package/README.md +100 -0
- package/SECURITY.md +21 -0
- package/index.js +428 -0
- package/lib/analyzer.js +547 -0
- package/lib/bundler.js +477 -0
- package/lib/config.js +269 -0
- package/lib/license.js +180 -0
- package/lib/premium/config-file.js +917 -0
- package/lib/premium/gate.js +13 -0
- package/lib/premium/html-report.js +1094 -0
- package/lib/premium/index.js +57 -0
- package/lib/premium/watch-mode.js +627 -0
- package/lib/scanner.js +480 -0
- package/lib/tokenizer.js +291 -0
- package/lib/validator.js +561 -0
- package/package.json +12 -0
- package/tests/analyzer.test.mjs +128 -0
- package/tests/bundler.test.mjs +126 -0
- package/tests/config.test.mjs +103 -0
- package/tests/gate.test.mjs +118 -0
- package/tests/index.test.mjs +103 -0
- package/tests/license.test.mjs +97 -0
- package/tests/scanner.test.mjs +110 -0
- package/tests/tokenizer.test.mjs +103 -0
- package/tests/validator.test.mjs +111 -0
- package/vitest.config.mjs +13 -0
package/lib/config.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Default configuration values for ContextPack
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULTS = {
|
|
10
|
+
include: ['**/*'],
|
|
11
|
+
exclude: [
|
|
12
|
+
'node_modules/**',
|
|
13
|
+
'.git/**',
|
|
14
|
+
'dist/**',
|
|
15
|
+
'build/**',
|
|
16
|
+
'coverage/**',
|
|
17
|
+
'.nyc_output/**',
|
|
18
|
+
'**/*.min.js',
|
|
19
|
+
'**/*.map',
|
|
20
|
+
],
|
|
21
|
+
output: 'contextpack-output.json',
|
|
22
|
+
format: 'json',
|
|
23
|
+
maxFileSummaryLength: 500,
|
|
24
|
+
includeDependencyMap: true,
|
|
25
|
+
includeSymbolIndex: true,
|
|
26
|
+
verbose: false,
|
|
27
|
+
rootDir: process.cwd(),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Attempt to read and parse a JSON file from disk.
|
|
32
|
+
* Returns null if the file does not exist or cannot be parsed.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} filePath - Absolute path to the JSON file
|
|
35
|
+
* @returns {object|null} Parsed JSON object or null
|
|
36
|
+
*/
|
|
37
|
+
function readJSONFile(filePath) {
|
|
38
|
+
try {
|
|
39
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
40
|
+
return JSON.parse(raw);
|
|
41
|
+
} catch (_err) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Locate and load the ContextPack configuration from one of the supported
|
|
48
|
+
* sources, in priority order:
|
|
49
|
+
* 1. .contextpackrc.json in the given root directory
|
|
50
|
+
* 2. "contextpack" key inside package.json in the given root directory
|
|
51
|
+
*
|
|
52
|
+
* If neither source is found, an empty object is returned so that defaults
|
|
53
|
+
* can be applied downstream.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} [rootDir] - Directory to search for config files.
|
|
56
|
+
* Defaults to process.cwd() when not provided or falsy.
|
|
57
|
+
* @returns {object} Raw configuration object loaded from disk (may be empty)
|
|
58
|
+
*/
|
|
59
|
+
function loadConfig(rootDir) {
|
|
60
|
+
const searchDir = (rootDir && typeof rootDir === 'string') ? rootDir : process.cwd();
|
|
61
|
+
|
|
62
|
+
// 1. Try .contextpackrc.json
|
|
63
|
+
const rcPath = path.join(searchDir, '.contextpackrc.json');
|
|
64
|
+
const rcConfig = readJSONFile(rcPath);
|
|
65
|
+
if (rcConfig !== null && typeof rcConfig === 'object') {
|
|
66
|
+
return normalizeConfig(rcConfig, searchDir);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 2. Try "contextpack" key in package.json
|
|
70
|
+
const pkgPath = path.join(searchDir, 'package.json');
|
|
71
|
+
const pkg = readJSONFile(pkgPath);
|
|
72
|
+
if (pkg !== null && typeof pkg === 'object' && pkg.contextpack && typeof pkg.contextpack === 'object') {
|
|
73
|
+
return normalizeConfig(pkg.contextpack, searchDir);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 3. No config found — return defaults
|
|
77
|
+
return normalizeConfig({}, searchDir);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Deep-merge two plain objects. Values from `override` take precedence over
|
|
82
|
+
* values from `base`. Arrays are replaced entirely (not concatenated) so that
|
|
83
|
+
* CLI flags or file config can fully replace default arrays.
|
|
84
|
+
*
|
|
85
|
+
* @param {object} base - Base object
|
|
86
|
+
* @param {object} override - Object whose values override base
|
|
87
|
+
* @returns {object} New merged object
|
|
88
|
+
*/
|
|
89
|
+
function deepMerge(base, override) {
|
|
90
|
+
const result = Object.assign({}, base);
|
|
91
|
+
|
|
92
|
+
for (const key of Object.keys(override)) {
|
|
93
|
+
const overrideVal = override[key];
|
|
94
|
+
const baseVal = result[key];
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
overrideVal !== null &&
|
|
98
|
+
typeof overrideVal === 'object' &&
|
|
99
|
+
!Array.isArray(overrideVal) &&
|
|
100
|
+
baseVal !== null &&
|
|
101
|
+
typeof baseVal === 'object' &&
|
|
102
|
+
!Array.isArray(baseVal)
|
|
103
|
+
) {
|
|
104
|
+
result[key] = deepMerge(baseVal, overrideVal);
|
|
105
|
+
} else {
|
|
106
|
+
result[key] = overrideVal;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Apply defaults to a raw config object and coerce values to their expected
|
|
115
|
+
* types. This ensures the returned config is always fully populated and safe
|
|
116
|
+
* to use without further null-checks by callers.
|
|
117
|
+
*
|
|
118
|
+
* @param {object} raw - Raw config object (may be partial or empty)
|
|
119
|
+
* @param {string} [rootDir] - Root directory to embed in the config
|
|
120
|
+
* @returns {object} Normalized config object with all defaults applied
|
|
121
|
+
*/
|
|
122
|
+
function normalizeConfig(raw, rootDir) {
|
|
123
|
+
const src = (raw !== null && typeof raw === 'object') ? raw : {};
|
|
124
|
+
const merged = deepMerge(DEFAULTS, src);
|
|
125
|
+
|
|
126
|
+
// Ensure rootDir is always set
|
|
127
|
+
if (rootDir && typeof rootDir === 'string') {
|
|
128
|
+
merged.rootDir = rootDir;
|
|
129
|
+
}
|
|
130
|
+
if (!merged.rootDir || typeof merged.rootDir !== 'string') {
|
|
131
|
+
merged.rootDir = process.cwd();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Coerce arrays
|
|
135
|
+
if (!Array.isArray(merged.include)) {
|
|
136
|
+
merged.include = DEFAULTS.include.slice();
|
|
137
|
+
}
|
|
138
|
+
if (!Array.isArray(merged.exclude)) {
|
|
139
|
+
merged.exclude = DEFAULTS.exclude.slice();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Coerce strings
|
|
143
|
+
if (typeof merged.output !== 'string' || !merged.output) {
|
|
144
|
+
merged.output = DEFAULTS.output;
|
|
145
|
+
}
|
|
146
|
+
if (typeof merged.format !== 'string' || !merged.format) {
|
|
147
|
+
merged.format = DEFAULTS.format;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Normalise format to lowercase
|
|
151
|
+
merged.format = merged.format.toLowerCase();
|
|
152
|
+
if (!['json', 'markdown', 'md'].includes(merged.format)) {
|
|
153
|
+
merged.format = DEFAULTS.format;
|
|
154
|
+
}
|
|
155
|
+
// Normalise 'md' alias
|
|
156
|
+
if (merged.format === 'md') {
|
|
157
|
+
merged.format = 'markdown';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Coerce numbers
|
|
161
|
+
const maxLen = parseInt(merged.maxFileSummaryLength, 10);
|
|
162
|
+
merged.maxFileSummaryLength = isNaN(maxLen) || maxLen < 0 ? DEFAULTS.maxFileSummaryLength : maxLen;
|
|
163
|
+
|
|
164
|
+
// Coerce booleans
|
|
165
|
+
merged.includeDependencyMap = Boolean(merged.includeDependencyMap);
|
|
166
|
+
merged.includeSymbolIndex = Boolean(merged.includeSymbolIndex);
|
|
167
|
+
merged.verbose = Boolean(merged.verbose);
|
|
168
|
+
|
|
169
|
+
return merged;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Merge a file-based (or default) config object with CLI flags. CLI flags
|
|
174
|
+
* take the highest precedence. Only flags that are explicitly provided
|
|
175
|
+
* (i.e. not undefined) are applied so that absent CLI flags do not
|
|
176
|
+
* accidentally overwrite file-based config values.
|
|
177
|
+
*
|
|
178
|
+
* Recognised flag keys and their mappings:
|
|
179
|
+
* flags.include → config.include (array or comma-separated string)
|
|
180
|
+
* flags.exclude → config.exclude (array or comma-separated string)
|
|
181
|
+
* flags.output → config.output
|
|
182
|
+
* flags.format → config.format
|
|
183
|
+
* flags.maxFileSummaryLength → config.maxFileSummaryLength
|
|
184
|
+
* flags.includeDependencyMap → config.includeDependencyMap
|
|
185
|
+
* flags.includeSymbolIndex → config.includeSymbolIndex
|
|
186
|
+
* flags.verbose → config.verbose
|
|
187
|
+
* flags.rootDir → config.rootDir
|
|
188
|
+
*
|
|
189
|
+
* @param {object} fileConfig - Config object produced by loadConfig()
|
|
190
|
+
* @param {object} [flags] - CLI flag object (e.g. from minimist or yargs).
|
|
191
|
+
* May be null or undefined — treated as an empty object in that case.
|
|
192
|
+
* @returns {object} New normalized config object with CLI flags applied
|
|
193
|
+
*/
|
|
194
|
+
function mergeWithFlags(fileConfig, flags) {
|
|
195
|
+
const base = (fileConfig !== null && typeof fileConfig === 'object') ? fileConfig : {};
|
|
196
|
+
const cliFlags = (flags !== null && typeof flags === 'object') ? flags : {};
|
|
197
|
+
|
|
198
|
+
const overrides = {};
|
|
199
|
+
|
|
200
|
+
// --include
|
|
201
|
+
if (cliFlags.include !== undefined) {
|
|
202
|
+
overrides.include = normalizeArrayFlag(cliFlags.include);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// --exclude
|
|
206
|
+
if (cliFlags.exclude !== undefined) {
|
|
207
|
+
overrides.exclude = normalizeArrayFlag(cliFlags.exclude);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// --output / -o
|
|
211
|
+
if (cliFlags.output !== undefined && cliFlags.output !== null) {
|
|
212
|
+
overrides.output = String(cliFlags.output);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// --format / -f
|
|
216
|
+
if (cliFlags.format !== undefined && cliFlags.format !== null) {
|
|
217
|
+
overrides.format = String(cliFlags.format);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// --maxFileSummaryLength
|
|
221
|
+
if (cliFlags.maxFileSummaryLength !== undefined) {
|
|
222
|
+
const parsed = parseInt(cliFlags.maxFileSummaryLength, 10);
|
|
223
|
+
if (!isNaN(parsed) && parsed >= 0) {
|
|
224
|
+
overrides.maxFileSummaryLength = parsed;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// --includeDependencyMap / --no-includeDependencyMap
|
|
229
|
+
if (cliFlags.includeDependencyMap !== undefined) {
|
|
230
|
+
overrides.includeDependencyMap = Boolean(cliFlags.includeDependencyMap);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// --includeSymbolIndex / --no-includeSymbolIndex
|
|
234
|
+
if (cliFlags.includeSymbolIndex !== undefined) {
|
|
235
|
+
overrides.includeSymbolIndex = Boolean(cliFlags.includeSymbolIndex);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// --verbose / -v
|
|
239
|
+
if (cliFlags.verbose !== undefined) {
|
|
240
|
+
overrides.verbose = Boolean(cliFlags.verbose);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// --rootDir
|
|
244
|
+
if (cliFlags.rootDir !== undefined && cliFlags.rootDir !== null && typeof cliFlags.rootDir === 'string' && cliFlags.rootDir.trim() !== '') {
|
|
245
|
+
overrides.rootDir = cliFlags.rootDir.trim();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const merged = deepMerge(base, overrides);
|
|
249
|
+
return normalizeConfig(merged, merged.rootDir || base.rootDir);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Normalise a flag value that should represent an array of strings.
|
|
254
|
+
* Accepts an existing array, a comma-separated string, or a single string.
|
|
255
|
+
*
|
|
256
|
+
* @param {string|string[]} value - Raw flag value
|
|
257
|
+
* @returns {string[]} Array of trimmed, non-empty strings
|
|
258
|
+
*/
|
|
259
|
+
function normalizeArrayFlag(value) {
|
|
260
|
+
if (Array.isArray(value)) {
|
|
261
|
+
return value.map(String).map(function (s) { return s.trim(); }).filter(Boolean);
|
|
262
|
+
}
|
|
263
|
+
if (typeof value === 'string') {
|
|
264
|
+
return value.split(',').map(function (s) { return s.trim(); }).filter(Boolean);
|
|
265
|
+
}
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
module.exports = { loadConfig, mergeWithFlags };
|
package/lib/license.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* lib/license.js
|
|
5
|
+
* Checks PRO_LICENSE environment variable; returns current license tier (free or pro);
|
|
6
|
+
* asserts pro license for gated features and shows a formatted upgrade prompt when license is absent.
|
|
7
|
+
*
|
|
8
|
+
* Part of the ContextPack product.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Valid license tiers
|
|
13
|
+
*/
|
|
14
|
+
const TIER_FREE = 'free';
|
|
15
|
+
const TIER_PRO = 'pro';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Upgrade prompt lines shown when a premium feature is requested without a valid license
|
|
19
|
+
*/
|
|
20
|
+
const UPGRADE_PROMPT_LINES = [
|
|
21
|
+
'',
|
|
22
|
+
'╔══════════════════════════════════════════════════════════════╗',
|
|
23
|
+
'║ ContextPack — Upgrade to PRO ║',
|
|
24
|
+
'╠══════════════════════════════════════════════════════════════╣',
|
|
25
|
+
'║ This feature requires a ContextPack PRO license. ║',
|
|
26
|
+
'║ ║',
|
|
27
|
+
'║ PRO features include: ║',
|
|
28
|
+
'║ • Watch mode — auto-regenerate bundles on file change ║',
|
|
29
|
+
'║ • HTML report — rich visual dependency & token report ║',
|
|
30
|
+
'║ • Enhanced config — per-directory rules & filters ║',
|
|
31
|
+
'║ ║',
|
|
32
|
+
'║ To activate PRO, set the environment variable: ║',
|
|
33
|
+
'║ PRO_LICENSE=<your-license-key> ║',
|
|
34
|
+
'║ ║',
|
|
35
|
+
'║ Get your license at: https://contextpack.dev/pro ║',
|
|
36
|
+
'╚══════════════════════════════════════════════════════════════╝',
|
|
37
|
+
'',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Determine whether the supplied license key string is considered valid.
|
|
42
|
+
* A key is valid when it is a non-empty string after trimming whitespace.
|
|
43
|
+
*
|
|
44
|
+
* @param {*} key - Raw value of the PRO_LICENSE environment variable
|
|
45
|
+
* @returns {boolean} True when the key is a non-empty string
|
|
46
|
+
*/
|
|
47
|
+
function isValidKey(key) {
|
|
48
|
+
return typeof key === 'string' && key.trim().length > 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns the current license status based on the PRO_LICENSE environment variable.
|
|
53
|
+
*
|
|
54
|
+
* @returns {{ tier: string, isProLicensed: boolean, licenseKey: string|null }}
|
|
55
|
+
* An object describing the current license state:
|
|
56
|
+
* - tier: 'pro' when a valid PRO_LICENSE is present, otherwise 'free'
|
|
57
|
+
* - isProLicensed: boolean shorthand for tier === 'pro'
|
|
58
|
+
* - licenseKey: the trimmed license key string, or null when absent/invalid
|
|
59
|
+
*/
|
|
60
|
+
function getLicenseStatus() {
|
|
61
|
+
const raw = process.env.PRO_LICENSE;
|
|
62
|
+
|
|
63
|
+
if (isValidKey(raw)) {
|
|
64
|
+
return {
|
|
65
|
+
tier: TIER_PRO,
|
|
66
|
+
isProLicensed: true,
|
|
67
|
+
licenseKey: raw.trim(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
tier: TIER_FREE,
|
|
73
|
+
isProLicensed: false,
|
|
74
|
+
licenseKey: null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Builds and returns the formatted upgrade prompt string.
|
|
80
|
+
* The prompt is also printed to stderr so it is visible in terminal output
|
|
81
|
+
* without polluting stdout (which may carry JSON or Markdown bundle output).
|
|
82
|
+
*
|
|
83
|
+
* @param {object} [options] - Optional display options
|
|
84
|
+
* @param {string} [options.featureName] - Name of the premium feature that was requested;
|
|
85
|
+
* when provided it is included in the prompt header for context.
|
|
86
|
+
* @param {boolean} [options.printToStderr=true] - When true (default) the prompt is
|
|
87
|
+
* written to process.stderr in addition to being returned as a string.
|
|
88
|
+
* @returns {string} The formatted upgrade prompt text
|
|
89
|
+
*/
|
|
90
|
+
function showUpgradePrompt(options) {
|
|
91
|
+
const opts = options || {};
|
|
92
|
+
const featureName = (typeof opts.featureName === 'string' && opts.featureName.trim().length > 0)
|
|
93
|
+
? opts.featureName.trim()
|
|
94
|
+
: null;
|
|
95
|
+
const printToStderr = opts.printToStderr !== false;
|
|
96
|
+
|
|
97
|
+
const lines = UPGRADE_PROMPT_LINES.slice();
|
|
98
|
+
|
|
99
|
+
if (featureName) {
|
|
100
|
+
// Insert a feature-specific line after the first separator
|
|
101
|
+
const insertIndex = 4;
|
|
102
|
+
lines.splice(insertIndex, 0, '║ Requested feature: ' + padRight(featureName, 41) + '║');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const prompt = lines.join('\n');
|
|
106
|
+
|
|
107
|
+
if (printToStderr) {
|
|
108
|
+
try {
|
|
109
|
+
process.stderr.write(prompt + '\n');
|
|
110
|
+
} catch (_err) {
|
|
111
|
+
// Silently ignore write errors (e.g. in test environments with closed stderr)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return prompt;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Asserts that a valid PRO_LICENSE is present in the environment.
|
|
120
|
+
* When the license is absent or invalid, displays the upgrade prompt and
|
|
121
|
+
* throws an Error so that callers can handle the gate gracefully.
|
|
122
|
+
*
|
|
123
|
+
* @param {object} [options] - Optional options forwarded to showUpgradePrompt
|
|
124
|
+
* @param {string} [options.featureName] - Name of the premium feature being gated
|
|
125
|
+
* @param {boolean} [options.printToStderr=true] - Whether to print the prompt to stderr
|
|
126
|
+
* @throws {Error} When no valid PRO_LICENSE is found
|
|
127
|
+
* @returns {void} Returns normally when a valid license is present
|
|
128
|
+
*/
|
|
129
|
+
function assertProLicense(options) {
|
|
130
|
+
const opts = options || {};
|
|
131
|
+
const status = getLicenseStatus();
|
|
132
|
+
|
|
133
|
+
if (status.isProLicensed) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
showUpgradePrompt(opts);
|
|
138
|
+
|
|
139
|
+
const featureName = (typeof opts.featureName === 'string' && opts.featureName.trim().length > 0)
|
|
140
|
+
? opts.featureName.trim()
|
|
141
|
+
: 'this feature';
|
|
142
|
+
|
|
143
|
+
throw new Error(
|
|
144
|
+
'ContextPack PRO license required to use ' + featureName + '. ' +
|
|
145
|
+
'Set the PRO_LICENSE environment variable to your license key. ' +
|
|
146
|
+
'Visit https://contextpack.dev/pro to obtain a license.'
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Internal helpers
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Right-pads a string with spaces to the given total length.
|
|
156
|
+
* Truncates with an ellipsis if the string exceeds the target length.
|
|
157
|
+
*
|
|
158
|
+
* @param {string} str - The string to pad
|
|
159
|
+
* @param {number} length - Desired total length
|
|
160
|
+
* @returns {string} Padded (or truncated) string
|
|
161
|
+
*/
|
|
162
|
+
function padRight(str, length) {
|
|
163
|
+
if (typeof str !== 'string') {
|
|
164
|
+
str = String(str);
|
|
165
|
+
}
|
|
166
|
+
if (str.length > length) {
|
|
167
|
+
return str.slice(0, length - 1) + '…';
|
|
168
|
+
}
|
|
169
|
+
return str + ' '.repeat(length - str.length);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Exports
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
module.exports = {
|
|
177
|
+
getLicenseStatus,
|
|
178
|
+
assertProLicense,
|
|
179
|
+
showUpgradePrompt,
|
|
180
|
+
};
|