@knighted/css 1.0.0-rc.11 → 1.0.0-rc.12
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/cjs/generateTypes.cjs +102 -181
- package/dist/cjs/generateTypes.d.cts +23 -28
- package/dist/generateTypes.d.ts +23 -28
- package/dist/generateTypes.js +102 -181
- package/package.json +1 -1
|
@@ -16,7 +16,6 @@ const node_module_type_1 = require("node-module-type");
|
|
|
16
16
|
const get_tsconfig_1 = require("get-tsconfig");
|
|
17
17
|
const tsconfig_paths_1 = require("tsconfig-paths");
|
|
18
18
|
const css_js_1 = require("./css.cjs");
|
|
19
|
-
const loaderInternals_js_1 = require("./loaderInternals.cjs");
|
|
20
19
|
const stableSelectorsLiteral_js_1 = require("./stableSelectorsLiteral.cjs");
|
|
21
20
|
const stableNamespace_js_1 = require("./stableNamespace.cjs");
|
|
22
21
|
let activeCssWithMeta = css_js_1.cssWithMeta;
|
|
@@ -65,22 +64,19 @@ function getImportMetaUrl() {
|
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
66
|
const PACKAGE_ROOT = resolvePackageRoot();
|
|
68
|
-
const
|
|
69
|
-
const
|
|
67
|
+
const SELECTOR_REFERENCE = '.knighted-css';
|
|
68
|
+
const SELECTOR_MODULE_SUFFIX = '.knighted-css.ts';
|
|
70
69
|
async function generateTypes(options = {}) {
|
|
71
70
|
const rootDir = node_path_1.default.resolve(options.rootDir ?? process.cwd());
|
|
72
71
|
const include = normalizeIncludeOptions(options.include, rootDir);
|
|
73
|
-
const
|
|
74
|
-
const typesRoot = node_path_1.default.resolve(options.typesRoot ?? DEFAULT_TYPES_ROOT);
|
|
72
|
+
const cacheDir = node_path_1.default.resolve(options.outDir ?? node_path_1.default.join(rootDir, '.knighted-css'));
|
|
75
73
|
const tsconfig = loadTsconfigResolutionContext(rootDir);
|
|
76
74
|
await es_module_lexer_1.init;
|
|
77
|
-
await promises_1.default.mkdir(
|
|
78
|
-
await promises_1.default.mkdir(typesRoot, { recursive: true });
|
|
75
|
+
await promises_1.default.mkdir(cacheDir, { recursive: true });
|
|
79
76
|
const internalOptions = {
|
|
80
77
|
rootDir,
|
|
81
78
|
include,
|
|
82
|
-
|
|
83
|
-
typesRoot,
|
|
79
|
+
cacheDir,
|
|
84
80
|
stableNamespace: options.stableNamespace,
|
|
85
81
|
tsconfig,
|
|
86
82
|
};
|
|
@@ -89,29 +85,27 @@ async function generateTypes(options = {}) {
|
|
|
89
85
|
async function generateDeclarations(options) {
|
|
90
86
|
const peerResolver = createProjectPeerResolver(options.rootDir);
|
|
91
87
|
const files = await collectCandidateFiles(options.include);
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const
|
|
88
|
+
const selectorModulesManifestPath = node_path_1.default.join(options.cacheDir, 'selector-modules.json');
|
|
89
|
+
const previousSelectorManifest = await readManifest(selectorModulesManifestPath);
|
|
90
|
+
const nextSelectorManifest = {};
|
|
95
91
|
const selectorCache = new Map();
|
|
96
|
-
const
|
|
97
|
-
const declarations = [];
|
|
92
|
+
const processedSelectors = new Set();
|
|
98
93
|
const warnings = [];
|
|
99
|
-
let
|
|
94
|
+
let selectorModuleWrites = 0;
|
|
100
95
|
for (const filePath of files) {
|
|
101
96
|
const matches = await findSpecifierImports(filePath);
|
|
102
97
|
for (const match of matches) {
|
|
103
98
|
const cleaned = match.specifier.trim();
|
|
104
99
|
const inlineFree = stripInlineLoader(cleaned);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (!query || !(0, loaderInternals_js_1.hasQueryFlag)(query, loaderInternals_js_1.TYPES_QUERY_FLAG)) {
|
|
100
|
+
const { resource } = splitResourceAndQuery(inlineFree);
|
|
101
|
+
const selectorSource = extractSelectorSourceSpecifier(resource);
|
|
102
|
+
if (!selectorSource) {
|
|
109
103
|
continue;
|
|
110
104
|
}
|
|
111
105
|
const resolvedNamespace = (0, stableNamespace_js_1.resolveStableNamespace)(options.stableNamespace);
|
|
112
|
-
const resolvedPath = await resolveImportPath(
|
|
106
|
+
const resolvedPath = await resolveImportPath(selectorSource, match.importer, options.rootDir, options.tsconfig);
|
|
113
107
|
if (!resolvedPath) {
|
|
114
|
-
warnings.push(`Unable to resolve ${
|
|
108
|
+
warnings.push(`Unable to resolve ${selectorSource} referenced by ${relativeToRoot(match.importer, options.rootDir)}.`);
|
|
115
109
|
continue;
|
|
116
110
|
}
|
|
117
111
|
const cacheKey = `${resolvedPath}::${resolvedNamespace}`;
|
|
@@ -135,42 +129,28 @@ async function generateDeclarations(options) {
|
|
|
135
129
|
}
|
|
136
130
|
selectorCache.set(cacheKey, selectorMap);
|
|
137
131
|
}
|
|
138
|
-
|
|
139
|
-
|
|
132
|
+
if (!isWithinRoot(resolvedPath, options.rootDir)) {
|
|
133
|
+
warnings.push(`Skipping selector module for ${relativeToRoot(resolvedPath, options.rootDir)} because it is outside the project root.`);
|
|
140
134
|
continue;
|
|
141
135
|
}
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const fileName = buildDeclarationFileName(canonicalSpecifier);
|
|
146
|
-
const targetPath = node_path_1.default.join(options.outDir, fileName);
|
|
147
|
-
const previousEntry = previousManifest[canonicalSpecifier];
|
|
148
|
-
const needsWrite = previousEntry?.hash !== declarationHash || !(await fileExists(targetPath));
|
|
149
|
-
if (needsWrite) {
|
|
150
|
-
await promises_1.default.writeFile(targetPath, declaration, 'utf8');
|
|
151
|
-
writes += 1;
|
|
136
|
+
const manifestKey = buildSelectorModuleManifestKey(resolvedPath);
|
|
137
|
+
if (processedSelectors.has(manifestKey)) {
|
|
138
|
+
continue;
|
|
152
139
|
}
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
|
|
140
|
+
const moduleWrite = await ensureSelectorModule(resolvedPath, selectorMap, previousSelectorManifest, nextSelectorManifest);
|
|
141
|
+
if (moduleWrite) {
|
|
142
|
+
selectorModuleWrites += 1;
|
|
156
143
|
}
|
|
157
|
-
|
|
144
|
+
processedSelectors.add(manifestKey);
|
|
158
145
|
}
|
|
159
146
|
}
|
|
160
|
-
const
|
|
161
|
-
await writeManifest(
|
|
162
|
-
const typesIndexPath = node_path_1.default.join(options.typesRoot, 'index.d.ts');
|
|
163
|
-
await writeTypesIndex(typesIndexPath, nextManifest, options.outDir);
|
|
164
|
-
if (Object.keys(nextManifest).length === 0) {
|
|
165
|
-
declarations.length = 0;
|
|
166
|
-
}
|
|
147
|
+
const selectorModulesRemoved = await removeStaleSelectorModules(previousSelectorManifest, nextSelectorManifest);
|
|
148
|
+
await writeManifest(selectorModulesManifestPath, nextSelectorManifest);
|
|
167
149
|
return {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
declarations,
|
|
150
|
+
selectorModulesWritten: selectorModuleWrites,
|
|
151
|
+
selectorModulesRemoved,
|
|
171
152
|
warnings,
|
|
172
|
-
|
|
173
|
-
typesIndexPath,
|
|
153
|
+
manifestPath: selectorModulesManifestPath,
|
|
174
154
|
};
|
|
175
155
|
}
|
|
176
156
|
function normalizeIncludeOptions(include, rootDir) {
|
|
@@ -228,18 +208,18 @@ async function findSpecifierImports(filePath) {
|
|
|
228
208
|
catch {
|
|
229
209
|
return [];
|
|
230
210
|
}
|
|
231
|
-
if (!source.includes(
|
|
211
|
+
if (!source.includes(SELECTOR_REFERENCE)) {
|
|
232
212
|
return [];
|
|
233
213
|
}
|
|
234
214
|
const matches = [];
|
|
235
215
|
const [imports] = (0, es_module_lexer_1.parse)(source, filePath);
|
|
236
216
|
for (const record of imports) {
|
|
237
217
|
const specifier = record.n ?? source.slice(record.s, record.e);
|
|
238
|
-
if (specifier && specifier.includes(
|
|
218
|
+
if (specifier && specifier.includes(SELECTOR_REFERENCE)) {
|
|
239
219
|
matches.push({ specifier, importer: filePath });
|
|
240
220
|
}
|
|
241
221
|
}
|
|
242
|
-
const requireRegex = /require\((['"])([^'"`]
|
|
222
|
+
const requireRegex = /require\((['"])([^'"`]+?\.knighted-css[^'"`]*)\1\)/g;
|
|
243
223
|
let reqMatch;
|
|
244
224
|
while ((reqMatch = requireRegex.exec(source)) !== null) {
|
|
245
225
|
const spec = reqMatch[2];
|
|
@@ -262,6 +242,21 @@ function splitResourceAndQuery(specifier) {
|
|
|
262
242
|
}
|
|
263
243
|
return { resource: trimmed.slice(0, queryIndex), query: trimmed.slice(queryIndex) };
|
|
264
244
|
}
|
|
245
|
+
function extractSelectorSourceSpecifier(specifier) {
|
|
246
|
+
const markerIndex = specifier.indexOf(SELECTOR_REFERENCE);
|
|
247
|
+
if (markerIndex < 0) {
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
const suffix = specifier.slice(markerIndex + SELECTOR_REFERENCE.length);
|
|
251
|
+
if (suffix.length > 0 && !/\.(?:[cm]?[tj]s|[tj]sx)$/.test(suffix)) {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
const base = specifier.slice(0, markerIndex);
|
|
255
|
+
if (!base) {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
return base;
|
|
259
|
+
}
|
|
265
260
|
const projectRequireCache = new Map();
|
|
266
261
|
async function resolveImportPath(resourceSpecifier, importerPath, rootDir, tsconfig) {
|
|
267
262
|
if (!resourceSpecifier)
|
|
@@ -284,91 +279,29 @@ async function resolveImportPath(resourceSpecifier, importerPath, rootDir, tscon
|
|
|
284
279
|
return undefined;
|
|
285
280
|
}
|
|
286
281
|
}
|
|
287
|
-
function
|
|
288
|
-
|
|
289
|
-
return `knt-${digest}.d.ts`;
|
|
290
|
-
}
|
|
291
|
-
function formatModuleDeclaration(specifier, variant, selectors) {
|
|
292
|
-
const literalSpecifier = JSON.stringify(specifier);
|
|
293
|
-
const selectorType = formatSelectorType(selectors);
|
|
294
|
-
const header = `declare module ${literalSpecifier} {`;
|
|
295
|
-
const footer = '}';
|
|
296
|
-
if (variant === 'types') {
|
|
297
|
-
return `${header}
|
|
298
|
-
export const knightedCss: string
|
|
299
|
-
export const stableSelectors: ${selectorType}
|
|
300
|
-
${footer}
|
|
301
|
-
`;
|
|
302
|
-
}
|
|
303
|
-
const stableLine = ` export const stableSelectors: ${selectorType}`;
|
|
304
|
-
const shared = ` const combined: KnightedCssCombinedModule<Record<string, unknown>>
|
|
305
|
-
export const knightedCss: string
|
|
306
|
-
${stableLine}`;
|
|
307
|
-
if (variant === 'combined') {
|
|
308
|
-
return `${header}
|
|
309
|
-
${shared}
|
|
310
|
-
export default combined
|
|
311
|
-
${footer}
|
|
312
|
-
`;
|
|
313
|
-
}
|
|
314
|
-
return `${header}
|
|
315
|
-
${shared}
|
|
316
|
-
${footer}
|
|
317
|
-
`;
|
|
282
|
+
function buildSelectorModuleManifestKey(resolvedPath) {
|
|
283
|
+
return resolvedPath.split(node_path_1.default.sep).join('/');
|
|
318
284
|
}
|
|
319
|
-
function
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
285
|
+
function buildSelectorModulePath(resolvedPath) {
|
|
286
|
+
return `${resolvedPath}${SELECTOR_MODULE_SUFFIX}`;
|
|
287
|
+
}
|
|
288
|
+
function formatSelectorModuleSource(selectors) {
|
|
289
|
+
const header = '// Generated by @knighted/css/generate-types\n// Do not edit.\n';
|
|
323
290
|
const entries = Array.from(selectors.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
324
|
-
const lines = entries.map(([token, selector]) => `
|
|
325
|
-
|
|
291
|
+
const lines = entries.map(([token, selector]) => ` ${JSON.stringify(token)}: ${JSON.stringify(selector)},`);
|
|
292
|
+
const literal = lines.length > 0
|
|
293
|
+
? `{
|
|
326
294
|
${lines.join('\n')}
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
if (!normalized || normalized === '') {
|
|
338
|
-
normalized = '.';
|
|
339
|
-
}
|
|
340
|
-
if (normalized === '.') {
|
|
341
|
-
return './';
|
|
342
|
-
}
|
|
343
|
-
if (normalized.startsWith('./') || normalized.startsWith('../')) {
|
|
344
|
-
return normalized;
|
|
345
|
-
}
|
|
346
|
-
if (normalized.startsWith('.')) {
|
|
347
|
-
return normalized;
|
|
348
|
-
}
|
|
349
|
-
return `./${normalized}`;
|
|
350
|
-
}
|
|
351
|
-
function buildCanonicalQuery(query) {
|
|
352
|
-
if (!query) {
|
|
353
|
-
return '';
|
|
354
|
-
}
|
|
355
|
-
const sanitized = (0, loaderInternals_js_1.buildSanitizedQuery)(query);
|
|
356
|
-
const extraParts = sanitized ? sanitized.slice(1).split('&').filter(Boolean) : [];
|
|
357
|
-
const parts = [];
|
|
358
|
-
parts.push('knighted-css');
|
|
359
|
-
if ((0, loaderInternals_js_1.hasQueryFlag)(query, loaderInternals_js_1.COMBINED_QUERY_FLAG)) {
|
|
360
|
-
parts.push(loaderInternals_js_1.COMBINED_QUERY_FLAG);
|
|
361
|
-
}
|
|
362
|
-
for (const flag of loaderInternals_js_1.NAMED_ONLY_QUERY_FLAGS) {
|
|
363
|
-
if ((0, loaderInternals_js_1.hasQueryFlag)(query, flag)) {
|
|
364
|
-
parts.push(flag);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
if ((0, loaderInternals_js_1.hasQueryFlag)(query, loaderInternals_js_1.TYPES_QUERY_FLAG)) {
|
|
368
|
-
parts.push(loaderInternals_js_1.TYPES_QUERY_FLAG);
|
|
369
|
-
}
|
|
370
|
-
const merged = [...parts, ...extraParts];
|
|
371
|
-
return merged.length > 0 ? `?${merged.join('&')}` : '';
|
|
295
|
+
} as const`
|
|
296
|
+
: '{} as const';
|
|
297
|
+
return `${header}
|
|
298
|
+
export const stableSelectors = ${literal}
|
|
299
|
+
|
|
300
|
+
export type KnightedCssStableSelectors = typeof stableSelectors
|
|
301
|
+
export type KnightedCssStableSelectorToken = keyof typeof stableSelectors
|
|
302
|
+
|
|
303
|
+
export default stableSelectors
|
|
304
|
+
`;
|
|
372
305
|
}
|
|
373
306
|
function hashContent(content) {
|
|
374
307
|
return node_crypto_1.default.createHash('sha1').update(content).digest('hex');
|
|
@@ -385,13 +318,12 @@ async function readManifest(manifestPath) {
|
|
|
385
318
|
async function writeManifest(manifestPath, manifest) {
|
|
386
319
|
await promises_1.default.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
387
320
|
}
|
|
388
|
-
async function
|
|
389
|
-
const stale = Object.entries(previous).filter(([
|
|
321
|
+
async function removeStaleSelectorModules(previous, next) {
|
|
322
|
+
const stale = Object.entries(previous).filter(([key]) => !next[key]);
|
|
390
323
|
let removed = 0;
|
|
391
324
|
for (const [, entry] of stale) {
|
|
392
|
-
const targetPath = node_path_1.default.join(outDir, entry.file);
|
|
393
325
|
try {
|
|
394
|
-
await promises_1.default.unlink(
|
|
326
|
+
await promises_1.default.unlink(entry.file);
|
|
395
327
|
removed += 1;
|
|
396
328
|
}
|
|
397
329
|
catch {
|
|
@@ -400,25 +332,6 @@ async function removeStaleDeclarations(previous, next, outDir) {
|
|
|
400
332
|
}
|
|
401
333
|
return removed;
|
|
402
334
|
}
|
|
403
|
-
async function writeTypesIndex(indexPath, manifest, outDir) {
|
|
404
|
-
const header = '// Generated by @knighted/css/generate-types\n// Do not edit.\n';
|
|
405
|
-
const references = Object.values(manifest)
|
|
406
|
-
.sort((a, b) => a.file.localeCompare(b.file))
|
|
407
|
-
.map(entry => {
|
|
408
|
-
const rel = node_path_1.default
|
|
409
|
-
.relative(node_path_1.default.dirname(indexPath), node_path_1.default.join(outDir, entry.file))
|
|
410
|
-
.split(node_path_1.default.sep)
|
|
411
|
-
.join('/');
|
|
412
|
-
return `/// <reference path="${rel}" />`;
|
|
413
|
-
});
|
|
414
|
-
const content = references.length > 0
|
|
415
|
-
? `${header}
|
|
416
|
-
${references.join('\n')}
|
|
417
|
-
`
|
|
418
|
-
: `${header}
|
|
419
|
-
`;
|
|
420
|
-
await promises_1.default.writeFile(indexPath, content, 'utf8');
|
|
421
|
-
}
|
|
422
335
|
function formatErrorMessage(error) {
|
|
423
336
|
if (error instanceof Error && typeof error.message === 'string') {
|
|
424
337
|
return error.message;
|
|
@@ -428,6 +341,23 @@ function formatErrorMessage(error) {
|
|
|
428
341
|
function relativeToRoot(filePath, rootDir) {
|
|
429
342
|
return node_path_1.default.relative(rootDir, filePath) || filePath;
|
|
430
343
|
}
|
|
344
|
+
function isWithinRoot(filePath, rootDir) {
|
|
345
|
+
const relative = node_path_1.default.relative(rootDir, filePath);
|
|
346
|
+
return relative === '' || (!relative.startsWith('..') && !node_path_1.default.isAbsolute(relative));
|
|
347
|
+
}
|
|
348
|
+
async function ensureSelectorModule(resolvedPath, selectors, previousManifest, nextManifest) {
|
|
349
|
+
const manifestKey = buildSelectorModuleManifestKey(resolvedPath);
|
|
350
|
+
const targetPath = buildSelectorModulePath(resolvedPath);
|
|
351
|
+
const source = formatSelectorModuleSource(selectors);
|
|
352
|
+
const hash = hashContent(source);
|
|
353
|
+
const previousEntry = previousManifest[manifestKey];
|
|
354
|
+
const needsWrite = previousEntry?.hash !== hash || !(await fileExists(targetPath));
|
|
355
|
+
if (needsWrite) {
|
|
356
|
+
await promises_1.default.writeFile(targetPath, source, 'utf8');
|
|
357
|
+
}
|
|
358
|
+
nextManifest[manifestKey] = { file: targetPath, hash };
|
|
359
|
+
return needsWrite;
|
|
360
|
+
}
|
|
431
361
|
async function fileExists(target) {
|
|
432
362
|
try {
|
|
433
363
|
await promises_1.default.access(target);
|
|
@@ -551,7 +481,6 @@ async function runGenerateTypesCli(argv = process.argv.slice(2)) {
|
|
|
551
481
|
rootDir: parsed.rootDir,
|
|
552
482
|
include: parsed.include,
|
|
553
483
|
outDir: parsed.outDir,
|
|
554
|
-
typesRoot: parsed.typesRoot,
|
|
555
484
|
stableNamespace: parsed.stableNamespace,
|
|
556
485
|
});
|
|
557
486
|
reportCliResult(result);
|
|
@@ -566,12 +495,11 @@ function parseCliArgs(argv) {
|
|
|
566
495
|
let rootDir = process.cwd();
|
|
567
496
|
const include = [];
|
|
568
497
|
let outDir;
|
|
569
|
-
let typesRoot;
|
|
570
498
|
let stableNamespace;
|
|
571
499
|
for (let i = 0; i < argv.length; i += 1) {
|
|
572
500
|
const arg = argv[i];
|
|
573
501
|
if (arg === '--help' || arg === '-h') {
|
|
574
|
-
return { rootDir, include, outDir,
|
|
502
|
+
return { rootDir, include, outDir, stableNamespace, help: true };
|
|
575
503
|
}
|
|
576
504
|
if (arg === '--root' || arg === '-r') {
|
|
577
505
|
const value = argv[++i];
|
|
@@ -597,14 +525,6 @@ function parseCliArgs(argv) {
|
|
|
597
525
|
outDir = value;
|
|
598
526
|
continue;
|
|
599
527
|
}
|
|
600
|
-
if (arg === '--types-root') {
|
|
601
|
-
const value = argv[++i];
|
|
602
|
-
if (!value) {
|
|
603
|
-
throw new Error('Missing value for --types-root');
|
|
604
|
-
}
|
|
605
|
-
typesRoot = value;
|
|
606
|
-
continue;
|
|
607
|
-
}
|
|
608
528
|
if (arg === '--stable-namespace') {
|
|
609
529
|
const value = argv[++i];
|
|
610
530
|
if (!value) {
|
|
@@ -618,7 +538,7 @@ function parseCliArgs(argv) {
|
|
|
618
538
|
}
|
|
619
539
|
include.push(arg);
|
|
620
540
|
}
|
|
621
|
-
return { rootDir, include, outDir,
|
|
541
|
+
return { rootDir, include, outDir, stableNamespace };
|
|
622
542
|
}
|
|
623
543
|
function printHelp() {
|
|
624
544
|
console.log(`Usage: knighted-css-generate-types [options]
|
|
@@ -626,20 +546,19 @@ function printHelp() {
|
|
|
626
546
|
Options:
|
|
627
547
|
-r, --root <path> Project root directory (default: cwd)
|
|
628
548
|
-i, --include <path> Additional directories/files to scan (repeatable)
|
|
629
|
-
--out-dir <path>
|
|
630
|
-
--types-root <path> Directory for generated @types entrypoint
|
|
549
|
+
--out-dir <path> Directory to store selector module manifest cache
|
|
631
550
|
--stable-namespace <name> Stable namespace prefix for generated selector maps
|
|
632
551
|
-h, --help Show this help message
|
|
633
552
|
`);
|
|
634
553
|
}
|
|
635
554
|
function reportCliResult(result) {
|
|
636
|
-
if (result.
|
|
637
|
-
console.log('[knighted-css]
|
|
555
|
+
if (result.selectorModulesWritten === 0 && result.selectorModulesRemoved === 0) {
|
|
556
|
+
console.log('[knighted-css] Selector modules are up to date.');
|
|
638
557
|
}
|
|
639
558
|
else {
|
|
640
|
-
console.log(`[knighted-css]
|
|
559
|
+
console.log(`[knighted-css] Selector modules updated: wrote ${result.selectorModulesWritten}, removed ${result.selectorModulesRemoved}.`);
|
|
641
560
|
}
|
|
642
|
-
console.log(`[knighted-css]
|
|
561
|
+
console.log(`[knighted-css] Manifest: ${result.manifestPath}`);
|
|
643
562
|
for (const warning of result.warnings) {
|
|
644
563
|
console.warn(`[knighted-css] ${warning}`);
|
|
645
564
|
}
|
|
@@ -654,17 +573,12 @@ function setImportMetaUrlProvider(provider) {
|
|
|
654
573
|
importMetaUrlProvider = provider ?? getImportMetaUrl;
|
|
655
574
|
}
|
|
656
575
|
exports.__generateTypesInternals = {
|
|
657
|
-
writeTypesIndex,
|
|
658
576
|
stripInlineLoader,
|
|
659
577
|
splitResourceAndQuery,
|
|
578
|
+
extractSelectorSourceSpecifier,
|
|
660
579
|
findSpecifierImports,
|
|
661
580
|
resolveImportPath,
|
|
662
581
|
resolvePackageRoot,
|
|
663
|
-
buildDeclarationFileName,
|
|
664
|
-
formatModuleDeclaration,
|
|
665
|
-
formatSelectorType,
|
|
666
|
-
buildDeclarationModuleSpecifier,
|
|
667
|
-
buildCanonicalQuery,
|
|
668
582
|
relativeToRoot,
|
|
669
583
|
collectCandidateFiles,
|
|
670
584
|
normalizeIncludeOptions,
|
|
@@ -680,4 +594,11 @@ exports.__generateTypesInternals = {
|
|
|
680
594
|
parseCliArgs,
|
|
681
595
|
printHelp,
|
|
682
596
|
reportCliResult,
|
|
597
|
+
buildSelectorModuleManifestKey,
|
|
598
|
+
buildSelectorModulePath,
|
|
599
|
+
formatSelectorModuleSource,
|
|
600
|
+
ensureSelectorModule,
|
|
601
|
+
removeStaleSelectorModules,
|
|
602
|
+
readManifest,
|
|
603
|
+
writeManifest,
|
|
683
604
|
};
|
|
@@ -3,38 +3,30 @@ import { moduleType } from 'node-module-type';
|
|
|
3
3
|
import { getTsconfig } from 'get-tsconfig';
|
|
4
4
|
import { type MatchPath } from 'tsconfig-paths';
|
|
5
5
|
import { cssWithMeta } from './css.cjs';
|
|
6
|
-
import { type SelectorTypeVariant } from './loaderInternals.cjs';
|
|
7
|
-
interface ManifestEntry {
|
|
8
|
-
file: string;
|
|
9
|
-
hash: string;
|
|
10
|
-
}
|
|
11
|
-
type Manifest = Record<string, ManifestEntry>;
|
|
12
6
|
interface ImportMatch {
|
|
13
7
|
specifier: string;
|
|
14
8
|
importer: string;
|
|
15
9
|
}
|
|
16
|
-
interface
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
interface ManifestEntry {
|
|
11
|
+
file: string;
|
|
12
|
+
hash: string;
|
|
19
13
|
}
|
|
14
|
+
type SelectorModuleManifest = Record<string, ManifestEntry>;
|
|
20
15
|
interface TsconfigResolutionContext {
|
|
21
16
|
absoluteBaseUrl?: string;
|
|
22
17
|
matchPath?: MatchPath;
|
|
23
18
|
}
|
|
24
19
|
type CssWithMetaFn = typeof cssWithMeta;
|
|
25
20
|
export interface GenerateTypesResult {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
declarations: DeclarationRecord[];
|
|
21
|
+
selectorModulesWritten: number;
|
|
22
|
+
selectorModulesRemoved: number;
|
|
29
23
|
warnings: string[];
|
|
30
|
-
|
|
31
|
-
typesIndexPath: string;
|
|
24
|
+
manifestPath: string;
|
|
32
25
|
}
|
|
33
26
|
export interface GenerateTypesOptions {
|
|
34
27
|
rootDir?: string;
|
|
35
28
|
include?: string[];
|
|
36
29
|
outDir?: string;
|
|
37
|
-
typesRoot?: string;
|
|
38
30
|
stableNamespace?: string;
|
|
39
31
|
}
|
|
40
32
|
type ModuleTypeDetector = () => ReturnType<typeof moduleType>;
|
|
@@ -48,14 +40,16 @@ declare function splitResourceAndQuery(specifier: string): {
|
|
|
48
40
|
resource: string;
|
|
49
41
|
query: string;
|
|
50
42
|
};
|
|
43
|
+
declare function extractSelectorSourceSpecifier(specifier: string): string | undefined;
|
|
51
44
|
declare function resolveImportPath(resourceSpecifier: string, importerPath: string, rootDir: string, tsconfig?: TsconfigResolutionContext): Promise<string | undefined>;
|
|
52
|
-
declare function
|
|
53
|
-
declare function
|
|
54
|
-
declare function
|
|
55
|
-
declare function
|
|
56
|
-
declare function
|
|
57
|
-
declare function
|
|
45
|
+
declare function buildSelectorModuleManifestKey(resolvedPath: string): string;
|
|
46
|
+
declare function buildSelectorModulePath(resolvedPath: string): string;
|
|
47
|
+
declare function formatSelectorModuleSource(selectors: Map<string, string>): string;
|
|
48
|
+
declare function readManifest(manifestPath: string): Promise<SelectorModuleManifest>;
|
|
49
|
+
declare function writeManifest(manifestPath: string, manifest: SelectorModuleManifest): Promise<void>;
|
|
50
|
+
declare function removeStaleSelectorModules(previous: SelectorModuleManifest, next: SelectorModuleManifest): Promise<number>;
|
|
58
51
|
declare function relativeToRoot(filePath: string, rootDir: string): string;
|
|
52
|
+
declare function ensureSelectorModule(resolvedPath: string, selectors: Map<string, string>, previousManifest: SelectorModuleManifest, nextManifest: SelectorModuleManifest): Promise<boolean>;
|
|
59
53
|
declare function resolveWithTsconfigPaths(specifier: string, tsconfig?: TsconfigResolutionContext): Promise<string | undefined>;
|
|
60
54
|
declare function loadTsconfigResolutionContext(rootDir: string, loader?: typeof getTsconfig): TsconfigResolutionContext | undefined;
|
|
61
55
|
declare function normalizeTsconfigPaths(paths: Record<string, string[] | string> | undefined): Record<string, string[]> | undefined;
|
|
@@ -67,7 +61,6 @@ export interface ParsedCliArgs {
|
|
|
67
61
|
rootDir: string;
|
|
68
62
|
include?: string[];
|
|
69
63
|
outDir?: string;
|
|
70
|
-
typesRoot?: string;
|
|
71
64
|
stableNamespace?: string;
|
|
72
65
|
help?: boolean;
|
|
73
66
|
}
|
|
@@ -78,17 +71,12 @@ declare function setCssWithMetaImplementation(impl?: CssWithMetaFn): void;
|
|
|
78
71
|
declare function setModuleTypeDetector(detector?: ModuleTypeDetector): void;
|
|
79
72
|
declare function setImportMetaUrlProvider(provider?: () => string | undefined): void;
|
|
80
73
|
export declare const __generateTypesInternals: {
|
|
81
|
-
writeTypesIndex: typeof writeTypesIndex;
|
|
82
74
|
stripInlineLoader: typeof stripInlineLoader;
|
|
83
75
|
splitResourceAndQuery: typeof splitResourceAndQuery;
|
|
76
|
+
extractSelectorSourceSpecifier: typeof extractSelectorSourceSpecifier;
|
|
84
77
|
findSpecifierImports: typeof findSpecifierImports;
|
|
85
78
|
resolveImportPath: typeof resolveImportPath;
|
|
86
79
|
resolvePackageRoot: typeof resolvePackageRoot;
|
|
87
|
-
buildDeclarationFileName: typeof buildDeclarationFileName;
|
|
88
|
-
formatModuleDeclaration: typeof formatModuleDeclaration;
|
|
89
|
-
formatSelectorType: typeof formatSelectorType;
|
|
90
|
-
buildDeclarationModuleSpecifier: typeof buildDeclarationModuleSpecifier;
|
|
91
|
-
buildCanonicalQuery: typeof buildCanonicalQuery;
|
|
92
80
|
relativeToRoot: typeof relativeToRoot;
|
|
93
81
|
collectCandidateFiles: typeof collectCandidateFiles;
|
|
94
82
|
normalizeIncludeOptions: typeof normalizeIncludeOptions;
|
|
@@ -104,5 +92,12 @@ export declare const __generateTypesInternals: {
|
|
|
104
92
|
parseCliArgs: typeof parseCliArgs;
|
|
105
93
|
printHelp: typeof printHelp;
|
|
106
94
|
reportCliResult: typeof reportCliResult;
|
|
95
|
+
buildSelectorModuleManifestKey: typeof buildSelectorModuleManifestKey;
|
|
96
|
+
buildSelectorModulePath: typeof buildSelectorModulePath;
|
|
97
|
+
formatSelectorModuleSource: typeof formatSelectorModuleSource;
|
|
98
|
+
ensureSelectorModule: typeof ensureSelectorModule;
|
|
99
|
+
removeStaleSelectorModules: typeof removeStaleSelectorModules;
|
|
100
|
+
readManifest: typeof readManifest;
|
|
101
|
+
writeManifest: typeof writeManifest;
|
|
107
102
|
};
|
|
108
103
|
export {};
|
package/dist/generateTypes.d.ts
CHANGED
|
@@ -3,38 +3,30 @@ import { moduleType } from 'node-module-type';
|
|
|
3
3
|
import { getTsconfig } from 'get-tsconfig';
|
|
4
4
|
import { type MatchPath } from 'tsconfig-paths';
|
|
5
5
|
import { cssWithMeta } from './css.js';
|
|
6
|
-
import { type SelectorTypeVariant } from './loaderInternals.js';
|
|
7
|
-
interface ManifestEntry {
|
|
8
|
-
file: string;
|
|
9
|
-
hash: string;
|
|
10
|
-
}
|
|
11
|
-
type Manifest = Record<string, ManifestEntry>;
|
|
12
6
|
interface ImportMatch {
|
|
13
7
|
specifier: string;
|
|
14
8
|
importer: string;
|
|
15
9
|
}
|
|
16
|
-
interface
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
interface ManifestEntry {
|
|
11
|
+
file: string;
|
|
12
|
+
hash: string;
|
|
19
13
|
}
|
|
14
|
+
type SelectorModuleManifest = Record<string, ManifestEntry>;
|
|
20
15
|
interface TsconfigResolutionContext {
|
|
21
16
|
absoluteBaseUrl?: string;
|
|
22
17
|
matchPath?: MatchPath;
|
|
23
18
|
}
|
|
24
19
|
type CssWithMetaFn = typeof cssWithMeta;
|
|
25
20
|
export interface GenerateTypesResult {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
declarations: DeclarationRecord[];
|
|
21
|
+
selectorModulesWritten: number;
|
|
22
|
+
selectorModulesRemoved: number;
|
|
29
23
|
warnings: string[];
|
|
30
|
-
|
|
31
|
-
typesIndexPath: string;
|
|
24
|
+
manifestPath: string;
|
|
32
25
|
}
|
|
33
26
|
export interface GenerateTypesOptions {
|
|
34
27
|
rootDir?: string;
|
|
35
28
|
include?: string[];
|
|
36
29
|
outDir?: string;
|
|
37
|
-
typesRoot?: string;
|
|
38
30
|
stableNamespace?: string;
|
|
39
31
|
}
|
|
40
32
|
type ModuleTypeDetector = () => ReturnType<typeof moduleType>;
|
|
@@ -48,14 +40,16 @@ declare function splitResourceAndQuery(specifier: string): {
|
|
|
48
40
|
resource: string;
|
|
49
41
|
query: string;
|
|
50
42
|
};
|
|
43
|
+
declare function extractSelectorSourceSpecifier(specifier: string): string | undefined;
|
|
51
44
|
declare function resolveImportPath(resourceSpecifier: string, importerPath: string, rootDir: string, tsconfig?: TsconfigResolutionContext): Promise<string | undefined>;
|
|
52
|
-
declare function
|
|
53
|
-
declare function
|
|
54
|
-
declare function
|
|
55
|
-
declare function
|
|
56
|
-
declare function
|
|
57
|
-
declare function
|
|
45
|
+
declare function buildSelectorModuleManifestKey(resolvedPath: string): string;
|
|
46
|
+
declare function buildSelectorModulePath(resolvedPath: string): string;
|
|
47
|
+
declare function formatSelectorModuleSource(selectors: Map<string, string>): string;
|
|
48
|
+
declare function readManifest(manifestPath: string): Promise<SelectorModuleManifest>;
|
|
49
|
+
declare function writeManifest(manifestPath: string, manifest: SelectorModuleManifest): Promise<void>;
|
|
50
|
+
declare function removeStaleSelectorModules(previous: SelectorModuleManifest, next: SelectorModuleManifest): Promise<number>;
|
|
58
51
|
declare function relativeToRoot(filePath: string, rootDir: string): string;
|
|
52
|
+
declare function ensureSelectorModule(resolvedPath: string, selectors: Map<string, string>, previousManifest: SelectorModuleManifest, nextManifest: SelectorModuleManifest): Promise<boolean>;
|
|
59
53
|
declare function resolveWithTsconfigPaths(specifier: string, tsconfig?: TsconfigResolutionContext): Promise<string | undefined>;
|
|
60
54
|
declare function loadTsconfigResolutionContext(rootDir: string, loader?: typeof getTsconfig): TsconfigResolutionContext | undefined;
|
|
61
55
|
declare function normalizeTsconfigPaths(paths: Record<string, string[] | string> | undefined): Record<string, string[]> | undefined;
|
|
@@ -67,7 +61,6 @@ export interface ParsedCliArgs {
|
|
|
67
61
|
rootDir: string;
|
|
68
62
|
include?: string[];
|
|
69
63
|
outDir?: string;
|
|
70
|
-
typesRoot?: string;
|
|
71
64
|
stableNamespace?: string;
|
|
72
65
|
help?: boolean;
|
|
73
66
|
}
|
|
@@ -78,17 +71,12 @@ declare function setCssWithMetaImplementation(impl?: CssWithMetaFn): void;
|
|
|
78
71
|
declare function setModuleTypeDetector(detector?: ModuleTypeDetector): void;
|
|
79
72
|
declare function setImportMetaUrlProvider(provider?: () => string | undefined): void;
|
|
80
73
|
export declare const __generateTypesInternals: {
|
|
81
|
-
writeTypesIndex: typeof writeTypesIndex;
|
|
82
74
|
stripInlineLoader: typeof stripInlineLoader;
|
|
83
75
|
splitResourceAndQuery: typeof splitResourceAndQuery;
|
|
76
|
+
extractSelectorSourceSpecifier: typeof extractSelectorSourceSpecifier;
|
|
84
77
|
findSpecifierImports: typeof findSpecifierImports;
|
|
85
78
|
resolveImportPath: typeof resolveImportPath;
|
|
86
79
|
resolvePackageRoot: typeof resolvePackageRoot;
|
|
87
|
-
buildDeclarationFileName: typeof buildDeclarationFileName;
|
|
88
|
-
formatModuleDeclaration: typeof formatModuleDeclaration;
|
|
89
|
-
formatSelectorType: typeof formatSelectorType;
|
|
90
|
-
buildDeclarationModuleSpecifier: typeof buildDeclarationModuleSpecifier;
|
|
91
|
-
buildCanonicalQuery: typeof buildCanonicalQuery;
|
|
92
80
|
relativeToRoot: typeof relativeToRoot;
|
|
93
81
|
collectCandidateFiles: typeof collectCandidateFiles;
|
|
94
82
|
normalizeIncludeOptions: typeof normalizeIncludeOptions;
|
|
@@ -104,5 +92,12 @@ export declare const __generateTypesInternals: {
|
|
|
104
92
|
parseCliArgs: typeof parseCliArgs;
|
|
105
93
|
printHelp: typeof printHelp;
|
|
106
94
|
reportCliResult: typeof reportCliResult;
|
|
95
|
+
buildSelectorModuleManifestKey: typeof buildSelectorModuleManifestKey;
|
|
96
|
+
buildSelectorModulePath: typeof buildSelectorModulePath;
|
|
97
|
+
formatSelectorModuleSource: typeof formatSelectorModuleSource;
|
|
98
|
+
ensureSelectorModule: typeof ensureSelectorModule;
|
|
99
|
+
removeStaleSelectorModules: typeof removeStaleSelectorModules;
|
|
100
|
+
readManifest: typeof readManifest;
|
|
101
|
+
writeManifest: typeof writeManifest;
|
|
107
102
|
};
|
|
108
103
|
export {};
|
package/dist/generateTypes.js
CHANGED
|
@@ -8,7 +8,6 @@ import { moduleType } from 'node-module-type';
|
|
|
8
8
|
import { getTsconfig } from 'get-tsconfig';
|
|
9
9
|
import { createMatchPath } from 'tsconfig-paths';
|
|
10
10
|
import { cssWithMeta } from './css.js';
|
|
11
|
-
import { determineSelectorVariant, hasQueryFlag, TYPES_QUERY_FLAG, buildSanitizedQuery, COMBINED_QUERY_FLAG, NAMED_ONLY_QUERY_FLAGS, } from './loaderInternals.js';
|
|
12
11
|
import { buildStableSelectorsLiteral } from './stableSelectorsLiteral.js';
|
|
13
12
|
import { resolveStableNamespace } from './stableNamespace.js';
|
|
14
13
|
let activeCssWithMeta = cssWithMeta;
|
|
@@ -57,22 +56,19 @@ function getImportMetaUrl() {
|
|
|
57
56
|
}
|
|
58
57
|
}
|
|
59
58
|
const PACKAGE_ROOT = resolvePackageRoot();
|
|
60
|
-
const
|
|
61
|
-
const
|
|
59
|
+
const SELECTOR_REFERENCE = '.knighted-css';
|
|
60
|
+
const SELECTOR_MODULE_SUFFIX = '.knighted-css.ts';
|
|
62
61
|
export async function generateTypes(options = {}) {
|
|
63
62
|
const rootDir = path.resolve(options.rootDir ?? process.cwd());
|
|
64
63
|
const include = normalizeIncludeOptions(options.include, rootDir);
|
|
65
|
-
const
|
|
66
|
-
const typesRoot = path.resolve(options.typesRoot ?? DEFAULT_TYPES_ROOT);
|
|
64
|
+
const cacheDir = path.resolve(options.outDir ?? path.join(rootDir, '.knighted-css'));
|
|
67
65
|
const tsconfig = loadTsconfigResolutionContext(rootDir);
|
|
68
66
|
await init;
|
|
69
|
-
await fs.mkdir(
|
|
70
|
-
await fs.mkdir(typesRoot, { recursive: true });
|
|
67
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
71
68
|
const internalOptions = {
|
|
72
69
|
rootDir,
|
|
73
70
|
include,
|
|
74
|
-
|
|
75
|
-
typesRoot,
|
|
71
|
+
cacheDir,
|
|
76
72
|
stableNamespace: options.stableNamespace,
|
|
77
73
|
tsconfig,
|
|
78
74
|
};
|
|
@@ -81,29 +77,27 @@ export async function generateTypes(options = {}) {
|
|
|
81
77
|
async function generateDeclarations(options) {
|
|
82
78
|
const peerResolver = createProjectPeerResolver(options.rootDir);
|
|
83
79
|
const files = await collectCandidateFiles(options.include);
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
80
|
+
const selectorModulesManifestPath = path.join(options.cacheDir, 'selector-modules.json');
|
|
81
|
+
const previousSelectorManifest = await readManifest(selectorModulesManifestPath);
|
|
82
|
+
const nextSelectorManifest = {};
|
|
87
83
|
const selectorCache = new Map();
|
|
88
|
-
const
|
|
89
|
-
const declarations = [];
|
|
84
|
+
const processedSelectors = new Set();
|
|
90
85
|
const warnings = [];
|
|
91
|
-
let
|
|
86
|
+
let selectorModuleWrites = 0;
|
|
92
87
|
for (const filePath of files) {
|
|
93
88
|
const matches = await findSpecifierImports(filePath);
|
|
94
89
|
for (const match of matches) {
|
|
95
90
|
const cleaned = match.specifier.trim();
|
|
96
91
|
const inlineFree = stripInlineLoader(cleaned);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (!query || !hasQueryFlag(query, TYPES_QUERY_FLAG)) {
|
|
92
|
+
const { resource } = splitResourceAndQuery(inlineFree);
|
|
93
|
+
const selectorSource = extractSelectorSourceSpecifier(resource);
|
|
94
|
+
if (!selectorSource) {
|
|
101
95
|
continue;
|
|
102
96
|
}
|
|
103
97
|
const resolvedNamespace = resolveStableNamespace(options.stableNamespace);
|
|
104
|
-
const resolvedPath = await resolveImportPath(
|
|
98
|
+
const resolvedPath = await resolveImportPath(selectorSource, match.importer, options.rootDir, options.tsconfig);
|
|
105
99
|
if (!resolvedPath) {
|
|
106
|
-
warnings.push(`Unable to resolve ${
|
|
100
|
+
warnings.push(`Unable to resolve ${selectorSource} referenced by ${relativeToRoot(match.importer, options.rootDir)}.`);
|
|
107
101
|
continue;
|
|
108
102
|
}
|
|
109
103
|
const cacheKey = `${resolvedPath}::${resolvedNamespace}`;
|
|
@@ -127,42 +121,28 @@ async function generateDeclarations(options) {
|
|
|
127
121
|
}
|
|
128
122
|
selectorCache.set(cacheKey, selectorMap);
|
|
129
123
|
}
|
|
130
|
-
|
|
131
|
-
|
|
124
|
+
if (!isWithinRoot(resolvedPath, options.rootDir)) {
|
|
125
|
+
warnings.push(`Skipping selector module for ${relativeToRoot(resolvedPath, options.rootDir)} because it is outside the project root.`);
|
|
132
126
|
continue;
|
|
133
127
|
}
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const fileName = buildDeclarationFileName(canonicalSpecifier);
|
|
138
|
-
const targetPath = path.join(options.outDir, fileName);
|
|
139
|
-
const previousEntry = previousManifest[canonicalSpecifier];
|
|
140
|
-
const needsWrite = previousEntry?.hash !== declarationHash || !(await fileExists(targetPath));
|
|
141
|
-
if (needsWrite) {
|
|
142
|
-
await fs.writeFile(targetPath, declaration, 'utf8');
|
|
143
|
-
writes += 1;
|
|
128
|
+
const manifestKey = buildSelectorModuleManifestKey(resolvedPath);
|
|
129
|
+
if (processedSelectors.has(manifestKey)) {
|
|
130
|
+
continue;
|
|
144
131
|
}
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
|
|
132
|
+
const moduleWrite = await ensureSelectorModule(resolvedPath, selectorMap, previousSelectorManifest, nextSelectorManifest);
|
|
133
|
+
if (moduleWrite) {
|
|
134
|
+
selectorModuleWrites += 1;
|
|
148
135
|
}
|
|
149
|
-
|
|
136
|
+
processedSelectors.add(manifestKey);
|
|
150
137
|
}
|
|
151
138
|
}
|
|
152
|
-
const
|
|
153
|
-
await writeManifest(
|
|
154
|
-
const typesIndexPath = path.join(options.typesRoot, 'index.d.ts');
|
|
155
|
-
await writeTypesIndex(typesIndexPath, nextManifest, options.outDir);
|
|
156
|
-
if (Object.keys(nextManifest).length === 0) {
|
|
157
|
-
declarations.length = 0;
|
|
158
|
-
}
|
|
139
|
+
const selectorModulesRemoved = await removeStaleSelectorModules(previousSelectorManifest, nextSelectorManifest);
|
|
140
|
+
await writeManifest(selectorModulesManifestPath, nextSelectorManifest);
|
|
159
141
|
return {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
declarations,
|
|
142
|
+
selectorModulesWritten: selectorModuleWrites,
|
|
143
|
+
selectorModulesRemoved,
|
|
163
144
|
warnings,
|
|
164
|
-
|
|
165
|
-
typesIndexPath,
|
|
145
|
+
manifestPath: selectorModulesManifestPath,
|
|
166
146
|
};
|
|
167
147
|
}
|
|
168
148
|
function normalizeIncludeOptions(include, rootDir) {
|
|
@@ -220,18 +200,18 @@ async function findSpecifierImports(filePath) {
|
|
|
220
200
|
catch {
|
|
221
201
|
return [];
|
|
222
202
|
}
|
|
223
|
-
if (!source.includes(
|
|
203
|
+
if (!source.includes(SELECTOR_REFERENCE)) {
|
|
224
204
|
return [];
|
|
225
205
|
}
|
|
226
206
|
const matches = [];
|
|
227
207
|
const [imports] = parse(source, filePath);
|
|
228
208
|
for (const record of imports) {
|
|
229
209
|
const specifier = record.n ?? source.slice(record.s, record.e);
|
|
230
|
-
if (specifier && specifier.includes(
|
|
210
|
+
if (specifier && specifier.includes(SELECTOR_REFERENCE)) {
|
|
231
211
|
matches.push({ specifier, importer: filePath });
|
|
232
212
|
}
|
|
233
213
|
}
|
|
234
|
-
const requireRegex = /require\((['"])([^'"`]
|
|
214
|
+
const requireRegex = /require\((['"])([^'"`]+?\.knighted-css[^'"`]*)\1\)/g;
|
|
235
215
|
let reqMatch;
|
|
236
216
|
while ((reqMatch = requireRegex.exec(source)) !== null) {
|
|
237
217
|
const spec = reqMatch[2];
|
|
@@ -254,6 +234,21 @@ function splitResourceAndQuery(specifier) {
|
|
|
254
234
|
}
|
|
255
235
|
return { resource: trimmed.slice(0, queryIndex), query: trimmed.slice(queryIndex) };
|
|
256
236
|
}
|
|
237
|
+
function extractSelectorSourceSpecifier(specifier) {
|
|
238
|
+
const markerIndex = specifier.indexOf(SELECTOR_REFERENCE);
|
|
239
|
+
if (markerIndex < 0) {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
const suffix = specifier.slice(markerIndex + SELECTOR_REFERENCE.length);
|
|
243
|
+
if (suffix.length > 0 && !/\.(?:[cm]?[tj]s|[tj]sx)$/.test(suffix)) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
const base = specifier.slice(0, markerIndex);
|
|
247
|
+
if (!base) {
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
return base;
|
|
251
|
+
}
|
|
257
252
|
const projectRequireCache = new Map();
|
|
258
253
|
async function resolveImportPath(resourceSpecifier, importerPath, rootDir, tsconfig) {
|
|
259
254
|
if (!resourceSpecifier)
|
|
@@ -276,91 +271,29 @@ async function resolveImportPath(resourceSpecifier, importerPath, rootDir, tscon
|
|
|
276
271
|
return undefined;
|
|
277
272
|
}
|
|
278
273
|
}
|
|
279
|
-
function
|
|
280
|
-
|
|
281
|
-
return `knt-${digest}.d.ts`;
|
|
282
|
-
}
|
|
283
|
-
function formatModuleDeclaration(specifier, variant, selectors) {
|
|
284
|
-
const literalSpecifier = JSON.stringify(specifier);
|
|
285
|
-
const selectorType = formatSelectorType(selectors);
|
|
286
|
-
const header = `declare module ${literalSpecifier} {`;
|
|
287
|
-
const footer = '}';
|
|
288
|
-
if (variant === 'types') {
|
|
289
|
-
return `${header}
|
|
290
|
-
export const knightedCss: string
|
|
291
|
-
export const stableSelectors: ${selectorType}
|
|
292
|
-
${footer}
|
|
293
|
-
`;
|
|
294
|
-
}
|
|
295
|
-
const stableLine = ` export const stableSelectors: ${selectorType}`;
|
|
296
|
-
const shared = ` const combined: KnightedCssCombinedModule<Record<string, unknown>>
|
|
297
|
-
export const knightedCss: string
|
|
298
|
-
${stableLine}`;
|
|
299
|
-
if (variant === 'combined') {
|
|
300
|
-
return `${header}
|
|
301
|
-
${shared}
|
|
302
|
-
export default combined
|
|
303
|
-
${footer}
|
|
304
|
-
`;
|
|
305
|
-
}
|
|
306
|
-
return `${header}
|
|
307
|
-
${shared}
|
|
308
|
-
${footer}
|
|
309
|
-
`;
|
|
274
|
+
function buildSelectorModuleManifestKey(resolvedPath) {
|
|
275
|
+
return resolvedPath.split(path.sep).join('/');
|
|
310
276
|
}
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
277
|
+
function buildSelectorModulePath(resolvedPath) {
|
|
278
|
+
return `${resolvedPath}${SELECTOR_MODULE_SUFFIX}`;
|
|
279
|
+
}
|
|
280
|
+
function formatSelectorModuleSource(selectors) {
|
|
281
|
+
const header = '// Generated by @knighted/css/generate-types\n// Do not edit.\n';
|
|
315
282
|
const entries = Array.from(selectors.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
316
|
-
const lines = entries.map(([token, selector]) => `
|
|
317
|
-
|
|
283
|
+
const lines = entries.map(([token, selector]) => ` ${JSON.stringify(token)}: ${JSON.stringify(selector)},`);
|
|
284
|
+
const literal = lines.length > 0
|
|
285
|
+
? `{
|
|
318
286
|
${lines.join('\n')}
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (!normalized || normalized === '') {
|
|
330
|
-
normalized = '.';
|
|
331
|
-
}
|
|
332
|
-
if (normalized === '.') {
|
|
333
|
-
return './';
|
|
334
|
-
}
|
|
335
|
-
if (normalized.startsWith('./') || normalized.startsWith('../')) {
|
|
336
|
-
return normalized;
|
|
337
|
-
}
|
|
338
|
-
if (normalized.startsWith('.')) {
|
|
339
|
-
return normalized;
|
|
340
|
-
}
|
|
341
|
-
return `./${normalized}`;
|
|
342
|
-
}
|
|
343
|
-
function buildCanonicalQuery(query) {
|
|
344
|
-
if (!query) {
|
|
345
|
-
return '';
|
|
346
|
-
}
|
|
347
|
-
const sanitized = buildSanitizedQuery(query);
|
|
348
|
-
const extraParts = sanitized ? sanitized.slice(1).split('&').filter(Boolean) : [];
|
|
349
|
-
const parts = [];
|
|
350
|
-
parts.push('knighted-css');
|
|
351
|
-
if (hasQueryFlag(query, COMBINED_QUERY_FLAG)) {
|
|
352
|
-
parts.push(COMBINED_QUERY_FLAG);
|
|
353
|
-
}
|
|
354
|
-
for (const flag of NAMED_ONLY_QUERY_FLAGS) {
|
|
355
|
-
if (hasQueryFlag(query, flag)) {
|
|
356
|
-
parts.push(flag);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
if (hasQueryFlag(query, TYPES_QUERY_FLAG)) {
|
|
360
|
-
parts.push(TYPES_QUERY_FLAG);
|
|
361
|
-
}
|
|
362
|
-
const merged = [...parts, ...extraParts];
|
|
363
|
-
return merged.length > 0 ? `?${merged.join('&')}` : '';
|
|
287
|
+
} as const`
|
|
288
|
+
: '{} as const';
|
|
289
|
+
return `${header}
|
|
290
|
+
export const stableSelectors = ${literal}
|
|
291
|
+
|
|
292
|
+
export type KnightedCssStableSelectors = typeof stableSelectors
|
|
293
|
+
export type KnightedCssStableSelectorToken = keyof typeof stableSelectors
|
|
294
|
+
|
|
295
|
+
export default stableSelectors
|
|
296
|
+
`;
|
|
364
297
|
}
|
|
365
298
|
function hashContent(content) {
|
|
366
299
|
return crypto.createHash('sha1').update(content).digest('hex');
|
|
@@ -377,13 +310,12 @@ async function readManifest(manifestPath) {
|
|
|
377
310
|
async function writeManifest(manifestPath, manifest) {
|
|
378
311
|
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
379
312
|
}
|
|
380
|
-
async function
|
|
381
|
-
const stale = Object.entries(previous).filter(([
|
|
313
|
+
async function removeStaleSelectorModules(previous, next) {
|
|
314
|
+
const stale = Object.entries(previous).filter(([key]) => !next[key]);
|
|
382
315
|
let removed = 0;
|
|
383
316
|
for (const [, entry] of stale) {
|
|
384
|
-
const targetPath = path.join(outDir, entry.file);
|
|
385
317
|
try {
|
|
386
|
-
await fs.unlink(
|
|
318
|
+
await fs.unlink(entry.file);
|
|
387
319
|
removed += 1;
|
|
388
320
|
}
|
|
389
321
|
catch {
|
|
@@ -392,25 +324,6 @@ async function removeStaleDeclarations(previous, next, outDir) {
|
|
|
392
324
|
}
|
|
393
325
|
return removed;
|
|
394
326
|
}
|
|
395
|
-
async function writeTypesIndex(indexPath, manifest, outDir) {
|
|
396
|
-
const header = '// Generated by @knighted/css/generate-types\n// Do not edit.\n';
|
|
397
|
-
const references = Object.values(manifest)
|
|
398
|
-
.sort((a, b) => a.file.localeCompare(b.file))
|
|
399
|
-
.map(entry => {
|
|
400
|
-
const rel = path
|
|
401
|
-
.relative(path.dirname(indexPath), path.join(outDir, entry.file))
|
|
402
|
-
.split(path.sep)
|
|
403
|
-
.join('/');
|
|
404
|
-
return `/// <reference path="${rel}" />`;
|
|
405
|
-
});
|
|
406
|
-
const content = references.length > 0
|
|
407
|
-
? `${header}
|
|
408
|
-
${references.join('\n')}
|
|
409
|
-
`
|
|
410
|
-
: `${header}
|
|
411
|
-
`;
|
|
412
|
-
await fs.writeFile(indexPath, content, 'utf8');
|
|
413
|
-
}
|
|
414
327
|
function formatErrorMessage(error) {
|
|
415
328
|
if (error instanceof Error && typeof error.message === 'string') {
|
|
416
329
|
return error.message;
|
|
@@ -420,6 +333,23 @@ function formatErrorMessage(error) {
|
|
|
420
333
|
function relativeToRoot(filePath, rootDir) {
|
|
421
334
|
return path.relative(rootDir, filePath) || filePath;
|
|
422
335
|
}
|
|
336
|
+
function isWithinRoot(filePath, rootDir) {
|
|
337
|
+
const relative = path.relative(rootDir, filePath);
|
|
338
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
339
|
+
}
|
|
340
|
+
async function ensureSelectorModule(resolvedPath, selectors, previousManifest, nextManifest) {
|
|
341
|
+
const manifestKey = buildSelectorModuleManifestKey(resolvedPath);
|
|
342
|
+
const targetPath = buildSelectorModulePath(resolvedPath);
|
|
343
|
+
const source = formatSelectorModuleSource(selectors);
|
|
344
|
+
const hash = hashContent(source);
|
|
345
|
+
const previousEntry = previousManifest[manifestKey];
|
|
346
|
+
const needsWrite = previousEntry?.hash !== hash || !(await fileExists(targetPath));
|
|
347
|
+
if (needsWrite) {
|
|
348
|
+
await fs.writeFile(targetPath, source, 'utf8');
|
|
349
|
+
}
|
|
350
|
+
nextManifest[manifestKey] = { file: targetPath, hash };
|
|
351
|
+
return needsWrite;
|
|
352
|
+
}
|
|
423
353
|
async function fileExists(target) {
|
|
424
354
|
try {
|
|
425
355
|
await fs.access(target);
|
|
@@ -543,7 +473,6 @@ export async function runGenerateTypesCli(argv = process.argv.slice(2)) {
|
|
|
543
473
|
rootDir: parsed.rootDir,
|
|
544
474
|
include: parsed.include,
|
|
545
475
|
outDir: parsed.outDir,
|
|
546
|
-
typesRoot: parsed.typesRoot,
|
|
547
476
|
stableNamespace: parsed.stableNamespace,
|
|
548
477
|
});
|
|
549
478
|
reportCliResult(result);
|
|
@@ -558,12 +487,11 @@ function parseCliArgs(argv) {
|
|
|
558
487
|
let rootDir = process.cwd();
|
|
559
488
|
const include = [];
|
|
560
489
|
let outDir;
|
|
561
|
-
let typesRoot;
|
|
562
490
|
let stableNamespace;
|
|
563
491
|
for (let i = 0; i < argv.length; i += 1) {
|
|
564
492
|
const arg = argv[i];
|
|
565
493
|
if (arg === '--help' || arg === '-h') {
|
|
566
|
-
return { rootDir, include, outDir,
|
|
494
|
+
return { rootDir, include, outDir, stableNamespace, help: true };
|
|
567
495
|
}
|
|
568
496
|
if (arg === '--root' || arg === '-r') {
|
|
569
497
|
const value = argv[++i];
|
|
@@ -589,14 +517,6 @@ function parseCliArgs(argv) {
|
|
|
589
517
|
outDir = value;
|
|
590
518
|
continue;
|
|
591
519
|
}
|
|
592
|
-
if (arg === '--types-root') {
|
|
593
|
-
const value = argv[++i];
|
|
594
|
-
if (!value) {
|
|
595
|
-
throw new Error('Missing value for --types-root');
|
|
596
|
-
}
|
|
597
|
-
typesRoot = value;
|
|
598
|
-
continue;
|
|
599
|
-
}
|
|
600
520
|
if (arg === '--stable-namespace') {
|
|
601
521
|
const value = argv[++i];
|
|
602
522
|
if (!value) {
|
|
@@ -610,7 +530,7 @@ function parseCliArgs(argv) {
|
|
|
610
530
|
}
|
|
611
531
|
include.push(arg);
|
|
612
532
|
}
|
|
613
|
-
return { rootDir, include, outDir,
|
|
533
|
+
return { rootDir, include, outDir, stableNamespace };
|
|
614
534
|
}
|
|
615
535
|
function printHelp() {
|
|
616
536
|
console.log(`Usage: knighted-css-generate-types [options]
|
|
@@ -618,20 +538,19 @@ function printHelp() {
|
|
|
618
538
|
Options:
|
|
619
539
|
-r, --root <path> Project root directory (default: cwd)
|
|
620
540
|
-i, --include <path> Additional directories/files to scan (repeatable)
|
|
621
|
-
--out-dir <path>
|
|
622
|
-
--types-root <path> Directory for generated @types entrypoint
|
|
541
|
+
--out-dir <path> Directory to store selector module manifest cache
|
|
623
542
|
--stable-namespace <name> Stable namespace prefix for generated selector maps
|
|
624
543
|
-h, --help Show this help message
|
|
625
544
|
`);
|
|
626
545
|
}
|
|
627
546
|
function reportCliResult(result) {
|
|
628
|
-
if (result.
|
|
629
|
-
console.log('[knighted-css]
|
|
547
|
+
if (result.selectorModulesWritten === 0 && result.selectorModulesRemoved === 0) {
|
|
548
|
+
console.log('[knighted-css] Selector modules are up to date.');
|
|
630
549
|
}
|
|
631
550
|
else {
|
|
632
|
-
console.log(`[knighted-css]
|
|
551
|
+
console.log(`[knighted-css] Selector modules updated: wrote ${result.selectorModulesWritten}, removed ${result.selectorModulesRemoved}.`);
|
|
633
552
|
}
|
|
634
|
-
console.log(`[knighted-css]
|
|
553
|
+
console.log(`[knighted-css] Manifest: ${result.manifestPath}`);
|
|
635
554
|
for (const warning of result.warnings) {
|
|
636
555
|
console.warn(`[knighted-css] ${warning}`);
|
|
637
556
|
}
|
|
@@ -646,17 +565,12 @@ function setImportMetaUrlProvider(provider) {
|
|
|
646
565
|
importMetaUrlProvider = provider ?? getImportMetaUrl;
|
|
647
566
|
}
|
|
648
567
|
export const __generateTypesInternals = {
|
|
649
|
-
writeTypesIndex,
|
|
650
568
|
stripInlineLoader,
|
|
651
569
|
splitResourceAndQuery,
|
|
570
|
+
extractSelectorSourceSpecifier,
|
|
652
571
|
findSpecifierImports,
|
|
653
572
|
resolveImportPath,
|
|
654
573
|
resolvePackageRoot,
|
|
655
|
-
buildDeclarationFileName,
|
|
656
|
-
formatModuleDeclaration,
|
|
657
|
-
formatSelectorType,
|
|
658
|
-
buildDeclarationModuleSpecifier,
|
|
659
|
-
buildCanonicalQuery,
|
|
660
574
|
relativeToRoot,
|
|
661
575
|
collectCandidateFiles,
|
|
662
576
|
normalizeIncludeOptions,
|
|
@@ -672,4 +586,11 @@ export const __generateTypesInternals = {
|
|
|
672
586
|
parseCliArgs,
|
|
673
587
|
printHelp,
|
|
674
588
|
reportCliResult,
|
|
589
|
+
buildSelectorModuleManifestKey,
|
|
590
|
+
buildSelectorModulePath,
|
|
591
|
+
formatSelectorModuleSource,
|
|
592
|
+
ensureSelectorModule,
|
|
593
|
+
removeStaleSelectorModules,
|
|
594
|
+
readManifest,
|
|
595
|
+
writeManifest,
|
|
675
596
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knighted/css",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.12",
|
|
4
4
|
"description": "A build-time utility that traverses JavaScript/TypeScript module dependency graphs to extract, compile, and optimize all imported CSS into a single, in-memory string.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/css.js",
|