@actioneer/ads 0.2.1 → 0.2.2
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/bin/ads-init.mjs +135 -7
- package/manifest.json +1 -1
- package/package.json +1 -1
package/bin/ads-init.mjs
CHANGED
|
@@ -254,6 +254,15 @@ const CANDIDATE_CSS = [
|
|
|
254
254
|
];
|
|
255
255
|
const CANDIDATE_LAYOUT = ["app/layout.tsx", "src/app/layout.tsx", "app/layout.jsx", "src/app/layout.jsx"];
|
|
256
256
|
|
|
257
|
+
// The load-bearing anchor: ADS styles must import into the SAME CSS graph as
|
|
258
|
+
// tailwindcss, right after it (styles.css before tailwindcss leaves @theme
|
|
259
|
+
// inert). Detection keys off this so a non-standard stylesheet is still found.
|
|
260
|
+
const TAILWIND_IMPORT_RE = /@import\s+["']tailwindcss["']/;
|
|
261
|
+
// Dirs the stylesheet scan never descends into.
|
|
262
|
+
const SCAN_IGNORE = new Set([
|
|
263
|
+
"node_modules", ".git", ".next", "dist", "dist-lib", "build", "out", "coverage", ".turbo", ".vercel", ".cache",
|
|
264
|
+
]);
|
|
265
|
+
|
|
257
266
|
function toPosix(p) {
|
|
258
267
|
return p.split(path.sep).join("/");
|
|
259
268
|
}
|
|
@@ -261,6 +270,81 @@ function fileExistsInState(state, relPosix) {
|
|
|
261
270
|
return state._existing?.has(relPosix) ?? false;
|
|
262
271
|
}
|
|
263
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Find the global stylesheet to wire ADS styles into. Because the import order
|
|
275
|
+
* is load-bearing, detection PREFERS the stylesheet that already imports
|
|
276
|
+
* tailwindcss — first among the conventional locations, then ANYWHERE in the
|
|
277
|
+
* project (bounded scan) so a non-standard path (e.g. `src/index.css`,
|
|
278
|
+
* `app/tailwind.css`) is auto-wired instead of degrading to a hand-edit note.
|
|
279
|
+
* Falls back to the first conventional file that exists (→ manual, with anchor
|
|
280
|
+
* guidance), else null.
|
|
281
|
+
* @returns {{path: string|null, content: string|null}}
|
|
282
|
+
*/
|
|
283
|
+
export function detectGlobalCss(cwd) {
|
|
284
|
+
const read = (rel) => {
|
|
285
|
+
const abs = path.join(cwd, rel);
|
|
286
|
+
return fs.existsSync(abs) ? fs.readFileSync(abs, "utf8") : null;
|
|
287
|
+
};
|
|
288
|
+
// 1. Conventional location that already imports tailwindcss (the anchor).
|
|
289
|
+
for (const p of CANDIDATE_CSS) {
|
|
290
|
+
const c = read(p);
|
|
291
|
+
if (c != null && TAILWIND_IMPORT_RE.test(c)) return { path: p, content: c };
|
|
292
|
+
}
|
|
293
|
+
// 2. Any .css in the project importing tailwindcss (covers non-standard paths).
|
|
294
|
+
const scanned = scanForTailwindCss(cwd);
|
|
295
|
+
if (scanned) return scanned;
|
|
296
|
+
// 3. A conventional file exists but has no tailwindcss anchor → manual path.
|
|
297
|
+
for (const p of CANDIDATE_CSS) {
|
|
298
|
+
const c = read(p);
|
|
299
|
+
if (c != null) return { path: p, content: c };
|
|
300
|
+
}
|
|
301
|
+
return { path: null, content: null };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Bounded walk for the shallowest .css that imports tailwindcss. */
|
|
305
|
+
function scanForTailwindCss(cwd, maxDepth = 5) {
|
|
306
|
+
const hits = [];
|
|
307
|
+
const walk = (rel, depth) => {
|
|
308
|
+
if (depth > maxDepth) return;
|
|
309
|
+
let entries;
|
|
310
|
+
try {
|
|
311
|
+
entries = fs.readdirSync(path.join(cwd, rel || "."), { withFileTypes: true });
|
|
312
|
+
} catch {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
for (const e of entries) {
|
|
316
|
+
if (e.name.startsWith(".")) continue;
|
|
317
|
+
const childRel = rel ? `${rel}/${e.name}` : e.name;
|
|
318
|
+
if (e.isDirectory()) {
|
|
319
|
+
if (!SCAN_IGNORE.has(e.name)) walk(childRel, depth + 1);
|
|
320
|
+
} else if (e.name.endsWith(".css")) {
|
|
321
|
+
let c;
|
|
322
|
+
try {
|
|
323
|
+
c = fs.readFileSync(path.join(cwd, childRel), "utf8");
|
|
324
|
+
} catch {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (TAILWIND_IMPORT_RE.test(c)) hits.push({ path: toPosix(childRel), content: c });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
walk("", 0);
|
|
332
|
+
// Shallowest path wins (the app entry over a nested one); stable tiebreak.
|
|
333
|
+
hits.sort((a, b) => a.path.split("/").length - b.path.split("/").length || a.path.localeCompare(b.path));
|
|
334
|
+
return hits[0] ?? null;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Re-check the load-bearing outcome after init: is `@import "…/styles.css"`
|
|
339
|
+
* actually present in the detected global stylesheet? Drives the end-of-run
|
|
340
|
+
* summary so a missed/deferred styles import can't scroll past unnoticed.
|
|
341
|
+
* @returns {{ok: boolean, path: string|null}}
|
|
342
|
+
*/
|
|
343
|
+
export function verifyStylesWired(cwd) {
|
|
344
|
+
const css = detectGlobalCss(cwd);
|
|
345
|
+
return { ok: css.content != null && css.content.includes(STYLE_IMPORT), path: css.path };
|
|
346
|
+
}
|
|
347
|
+
|
|
264
348
|
function readRepoState(cwd) {
|
|
265
349
|
const read = (rel) => {
|
|
266
350
|
const abs = path.join(cwd, rel);
|
|
@@ -276,7 +360,7 @@ function readRepoState(cwd) {
|
|
|
276
360
|
lockfiles: LOCKFILES.filter((f) => fs.existsSync(path.join(cwd, f))),
|
|
277
361
|
});
|
|
278
362
|
|
|
279
|
-
const
|
|
363
|
+
const css = detectGlobalCss(cwd);
|
|
280
364
|
const layoutPath = CANDIDATE_LAYOUT.find((p) => fs.existsSync(path.join(cwd, p))) ?? null;
|
|
281
365
|
|
|
282
366
|
const _existing = new Set();
|
|
@@ -286,8 +370,8 @@ function readRepoState(cwd) {
|
|
|
286
370
|
|
|
287
371
|
return {
|
|
288
372
|
dependencies,
|
|
289
|
-
globalCssPath,
|
|
290
|
-
globalCssContent:
|
|
373
|
+
globalCssPath: css.path,
|
|
374
|
+
globalCssContent: css.content,
|
|
291
375
|
layoutPath,
|
|
292
376
|
layoutContent: layoutPath ? read(layoutPath) : null,
|
|
293
377
|
hasAdsLintConfig:
|
|
@@ -336,6 +420,47 @@ function applyEdit(cwd, edit) {
|
|
|
336
420
|
}
|
|
337
421
|
}
|
|
338
422
|
|
|
423
|
+
/**
|
|
424
|
+
* End-of-run summary. The styles import carries EVERY token (colors, type, and
|
|
425
|
+
* the animation curves/speeds), so its outcome gets promoted out of the
|
|
426
|
+
* scrolling edit log into a verified ✓ / ⚠ block that can't be missed — plus any
|
|
427
|
+
* remaining manual steps and the import hint.
|
|
428
|
+
*/
|
|
429
|
+
function printSummary(cwd, edits) {
|
|
430
|
+
const wired = verifyStylesWired(cwd);
|
|
431
|
+
const bar = "─".repeat(70);
|
|
432
|
+
|
|
433
|
+
console.log("");
|
|
434
|
+
if (wired.ok) {
|
|
435
|
+
console.log(`✓ ADS styles wired — \`${STYLE_IMPORT}\` is in ${wired.path}.`);
|
|
436
|
+
console.log(` That one import carries every ADS token: colors, typography, and the`);
|
|
437
|
+
console.log(` animation curves/speeds.`);
|
|
438
|
+
} else {
|
|
439
|
+
console.log(bar);
|
|
440
|
+
console.log(`⚠ ACTION REQUIRED — ADS styles are NOT imported yet.`);
|
|
441
|
+
console.log(``);
|
|
442
|
+
console.log(` Nothing will be themed until you add this to your global stylesheet,`);
|
|
443
|
+
console.log(` immediately AFTER the tailwindcss import (the order is load-bearing):`);
|
|
444
|
+
console.log(``);
|
|
445
|
+
console.log(` @import "tailwindcss";`);
|
|
446
|
+
console.log(` ${STYLE_IMPORT}`);
|
|
447
|
+
console.log(``);
|
|
448
|
+
console.log(` Without it NO tokens load — colors, typography, and every animation`);
|
|
449
|
+
console.log(` curve/speed will be missing.`);
|
|
450
|
+
console.log(bar);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Surface any non-styles manual step (e.g. the layout <Providers> wrap) too —
|
|
454
|
+
// the styles step is already covered by the block above.
|
|
455
|
+
const otherManual = edits.filter((e) => e.type === "manual" && !/styles\.css/.test(e.title));
|
|
456
|
+
if (otherManual.length) {
|
|
457
|
+
console.log(`\n Remaining manual step(s):`);
|
|
458
|
+
for (const m of otherManual) console.log(` • ${m.title}`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
console.log(`\n Then import components: \`import { Button } from "${PKG}";\``);
|
|
462
|
+
}
|
|
463
|
+
|
|
339
464
|
function main() {
|
|
340
465
|
const cmd = process.argv[2];
|
|
341
466
|
const cwd = process.cwd();
|
|
@@ -349,12 +474,15 @@ function main() {
|
|
|
349
474
|
|
|
350
475
|
if (edits.length === 0) {
|
|
351
476
|
console.log(`${PKG} init: nothing to do — already configured.`);
|
|
352
|
-
|
|
477
|
+
} else {
|
|
478
|
+
console.log(`${PKG} init: applying ${edits.length} edit(s)…`);
|
|
479
|
+
for (const edit of edits) applyEdit(cwd, edit);
|
|
480
|
+
console.log(`\n${PKG} init: done.`);
|
|
353
481
|
}
|
|
354
482
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
483
|
+
// Always verify + summarize the load-bearing styles import (fixes #2: a
|
|
484
|
+
// deferred/missed styles import can no longer scroll past unnoticed).
|
|
485
|
+
printSummary(cwd, edits);
|
|
358
486
|
}
|
|
359
487
|
|
|
360
488
|
/**
|
package/manifest.json
CHANGED