diff --git a/packages/postcss-tape/CHANGELOG.md b/packages/postcss-tape/CHANGELOG.md index 03ad1687b..74866efa8 100644 --- a/packages/postcss-tape/CHANGELOG.md +++ b/packages/postcss-tape/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Tape +### Unreleased (patch) + +- Improve stack traces for issues in `package.json` of postcss plugins. + ### 4.1.1 _December 28, 2023_ diff --git a/packages/postcss-tape/dist/index.cjs b/packages/postcss-tape/dist/index.cjs index 85d598782..91b43c279 100644 --- a/packages/postcss-tape/dist/index.cjs +++ b/packages/postcss-tape/dist/index.cjs @@ -1,3 +1,3 @@ /* node:coverage disable */ -"use strict";var e=require("node:assert/strict"),t=require("fs/promises"),s=require("fs"),n=require("path"),o=require("postcss"),i=require("postcss-8.4"),r=require("node:test");const noopPlugin=()=>({postcssPlugin:"noop-plugin",Rule(){}});async function fileContentsOrEmptyString(e){try{return await t.readFile(e,"utf8")}catch(e){return""}}function reduceInformationInCssSyntaxError(e){process.env.DEBUG||(delete e.source,e.input&&delete e.input.source,delete e.postcssNode)}noopPlugin.postcss=!0;const a={postcssPlugin:"declaration-cloner",Declaration(e){"to-clone"===e.prop&&e.cloneBefore({prop:"cloned"})}},c={postcssPlugin:"rule-cloner",prepare(){const e=new WeakSet;return{RuleExit(t){e.has(t)||"to-clone"===t.selector&&(e.add(t),t.cloneBefore({selector:"cloned"}))}}}},p={postcssPlugin:"at-rule-cloner",prepare(){const e=new WeakSet;return{AtRuleExit(t){if(!e.has(t))return"to-clone"===t.params?(e.add(t),void t.cloneBefore({params:"cloned"})):"to-clone"===t.name?(e.add(t),void t.cloneBefore({name:"cloned"})):void 0}}}};exports.atRuleClonerPlugin=p,exports.declarationClonerPlugin=a,exports.postcssTape=function postcssTape(a,c){c=c??{},r("`postcss` flag is set on exported plugin creator",(()=>{e.strictEqual(a.postcss,!0)})),r("exported plugin creator is a function",(()=>{e.strictEqual(typeof a,"function")})),r("`postcssPlugin` is set on a plugin instance",(()=>{const t=a();e.ok(t.postcssPlugin),e.strictEqual(typeof t.postcssPlugin,"string")})),r("package.json",(async s=>{const n=await t.readFile("./package.json","utf-8"),o=JSON.parse(n);await s.test('includes "postcss-plugin" keyword',(()=>{e.ok(o.keywords),e.ok(o.keywords.includes("postcss-plugin"))})),await s.test('name starts with "postcss-"',{skip:c?.skipPackageNameCheck},(()=>{let t=o.name;if(t.startsWith("@")){t=o.name.split("/").slice(1).join("/")}e.ok(t.startsWith("postcss-"),`package name "${t}" does not start with "postcss-"`)})),await s.test("`postcss` is a peer dependency and not a direct dependency",{skip:"postcssTapeSelfTest"in a},(()=>{e.ok(o.peerDependencies),e.ok(Object.keys(Object(o.peerDependencies)).includes("postcss")),e.ok(!Object.keys(Object(o.dependencies)).includes("postcss"))}))}));const p=a().postcssPlugin;return async c=>{await r(p,(async r=>{for(const p in c)await r.test(p,(async r=>{const l=c[p];l.before&&await l.before();const u=n.join(".","test",...p.split(":")[0].split(n.posix.sep)),d=n.join(".","test",...p.replace(/:/g,".").split(n.posix.sep)),g="css";let f=`${u}.${g}`,w=`${d}.expect.${g}`,m=`${d}.result.${g}`;l.source&&(f=n.join(".","test",l.source)),l.expect&&(w=n.join(".","test",l.expect)),l.result&&(m=n.join(".","test",l.result));const y=l.plugins??[a(l.options)],k=await fileContentsOrEmptyString(f),S=await fileContentsOrEmptyString(w);let x,E=!1;try{x=await o(y).process(k,{from:f,to:m,map:{inline:!1,annotation:!1}})}catch(t){if(reduceInformationInCssSyntaxError(t),E=!0,l.exception&&l.exception.test(t.message))return;e.ifError(t)}e.notEqual(!E,l.exception,"expected an exception but got none");const h=x.css.toString();{const e=[t.writeFile(m,h,"utf8")];process.env.REWRITE_EXPECTS&&e.push(t.writeFile(w,h,"utf8")),await Promise.all(e)}S||e.ok(s.existsSync(w),`Missing expect file: "${w}"`),await r.test("has expected output",(()=>{e.strictEqual(h,S),e.deepStrictEqual(x.warnings().length,l.warnings??0,"Unexpected number warnings")})),await r.test("sourcemaps",(async()=>{e.ok(!x.map.toJSON().sources.includes(""),'Sourcemap is broken. This is most likely a newly created PostCSS AST Node without a value for "source". See: https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#24-set-nodesource-for-new-nodes')})),l.after&&await l.after(),await r.test("output is parsable with PostCSS",(async()=>{const t=await fileContentsOrEmptyString(m),s=await o([noopPlugin()]).process(t,{from:m,to:m,map:{inline:!1,annotation:!1}});e.deepStrictEqual(s.warnings(),[],"Unexpected warnings on second pass")})),await r.test("The oldest and current PostCSS version produce the same result",{skip:o([noopPlugin()]).version===i([noopPlugin()]).version},(async()=>{const t=await i(y).process(k,{from:f,to:m,map:{inline:!1,annotation:!1}});e.strictEqual(t.css.toString(),h)}))}))}))}},exports.ruleClonerPlugin=c; +"use strict";var e=require("node:assert/strict"),s=require("node:fs/promises"),t=require("node:fs"),n=require("node:path"),o=require("postcss"),r=require("postcss-8.4"),i=require("node:test"),a=require("node:url");const noopPlugin=()=>({postcssPlugin:"noop-plugin",Rule(){}});async function fileContentsOrEmptyString(e){try{return await s.readFile(e,"utf8")}catch(e){return""}}function reduceInformationInCssSyntaxError(e){process.env.DEBUG||(delete e.source,e.input&&delete e.input.source,delete e.postcssNode)}noopPlugin.postcss=!0;const c={postcssPlugin:"declaration-cloner",Declaration(e){"to-clone"===e.prop&&e.cloneBefore({prop:"cloned"})}},p={postcssPlugin:"rule-cloner",prepare(){const e=new WeakSet;return{RuleExit(s){e.has(s)||"to-clone"===s.selector&&(e.add(s),s.cloneBefore({selector:"cloned"}))}}}},l={postcssPlugin:"at-rule-cloner",prepare(){const e=new WeakSet;return{AtRuleExit(s){if(!e.has(s))return"to-clone"===s.params?(e.add(s),void s.cloneBefore({params:"cloned"})):"to-clone"===s.name?(e.add(s),void s.cloneBefore({name:"cloned"})):void 0}}}};class PackageDescriptionError extends Error{constructor(e,s){super(e),this.name="PackageDescriptionError",this.stack=`${this.name}: ${this.message}\n at "${s}" (${a.pathToFileURL(n.resolve("package.json")).pathname})`}}exports.atRuleClonerPlugin=l,exports.declarationClonerPlugin=c,exports.postcssTape=function postcssTape(a,c){c=c??{},i("`postcss` flag is set on exported plugin creator",(()=>{e.strictEqual(a.postcss,!0)})),i("exported plugin creator is a function",(()=>{e.strictEqual(typeof a,"function")})),i("`postcssPlugin` is set on a plugin instance",(()=>{const s=a();e.ok(s.postcssPlugin),e.strictEqual(typeof s.postcssPlugin,"string")})),i("package.json",(async t=>{const n=await s.readFile("./package.json","utf-8"),o=JSON.parse(n);await t.test('includes "postcss-plugin" keyword',(()=>{e.ok(o.keywords?.includes("postcss-plugin"),new PackageDescriptionError('Missing "postcss-plugin" keyword in package.json',"keywords"))})),await t.test('name starts with "postcss-"',{skip:c?.skipPackageNameCheck},(()=>{let s=o.name;if(s.startsWith("@")){s=o.name.split("/").slice(1).join("/")}e.ok(s.startsWith("postcss-"),new PackageDescriptionError(`package name "${s}" does not start with "postcss-"`,"name"))})),await t.test("`postcss` is a peer dependency and not a direct dependency",{skip:"postcssTapeSelfTest"in a},(()=>{e.ok(Object.keys(Object(o.peerDependencies)).includes("postcss"),new PackageDescriptionError('"postcss" is not listed in "peerDependencies"',"peerDependencies")),e.ok(!Object.keys(Object(o.dependencies)).includes("postcss"),new PackageDescriptionError('"postcss" must not be listed in "dependencies"',"dependencies"))}))}));const p=a().postcssPlugin;return async c=>{await i(p,(async i=>{for(const p in c)await i.test(p,(async i=>{const l=c[p];l.before&&await l.before();const u=n.join(".","test",...p.split(":")[0].split(n.posix.sep)),d=n.join(".","test",...p.replace(/:/g,".").split(n.posix.sep)),g="css";let w=`${u}.${g}`,f=`${d}.expect.${g}`,m=`${d}.result.${g}`;l.source&&(w=n.join(".","test",l.source)),l.expect&&(f=n.join(".","test",l.expect)),l.result&&(m=n.join(".","test",l.result));const k=l.plugins??[a(l.options)],y=await fileContentsOrEmptyString(w),E=await fileContentsOrEmptyString(f);let h,x=!1;try{h=await o(k).process(y,{from:w,to:m,map:{inline:!1,annotation:!1}})}catch(s){if(reduceInformationInCssSyntaxError(s),x=!0,l.exception&&l.exception.test(s.message))return;e.ifError(s)}e.notEqual(!x,l.exception,"expected an exception but got none");const S=h.css.toString();{const e=[s.writeFile(m,S,"utf8")];process.env.REWRITE_EXPECTS&&e.push(s.writeFile(f,S,"utf8")),await Promise.all(e)}E||e.ok(t.existsSync(f),`Missing expect file: "${f}"`),await i.test("has expected output",(()=>{e.strictEqual(S,E),e.deepStrictEqual(h.warnings().length,l.warnings??0,"Unexpected number warnings")})),await i.test("sourcemaps",(async()=>{e.ok(!h.map.toJSON().sources.includes(""),'Sourcemap is broken. This is most likely a newly created PostCSS AST Node without a value for "source". See: https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#24-set-nodesource-for-new-nodes')})),l.after&&await l.after(),await i.test("output is parsable with PostCSS",(async()=>{const s=await fileContentsOrEmptyString(m),t=await o([noopPlugin()]).process(s,{from:m,to:m,map:{inline:!1,annotation:!1}});e.deepStrictEqual(t.warnings(),[],"Unexpected warnings on second pass")})),await i.test("The oldest and current PostCSS version produce the same result",{skip:o([noopPlugin()]).version===r([noopPlugin()]).version},(async()=>{const s=await r(k).process(y,{from:w,to:m,map:{inline:!1,annotation:!1}});e.strictEqual(s.css.toString(),S)}))}))}))}},exports.ruleClonerPlugin=p; /* node:coverage enable */ diff --git a/packages/postcss-tape/dist/index.mjs b/packages/postcss-tape/dist/index.mjs index 5b8a10acb..8ecf1f720 100644 --- a/packages/postcss-tape/dist/index.mjs +++ b/packages/postcss-tape/dist/index.mjs @@ -1,3 +1,3 @@ /* node:coverage disable */ -import e from"node:assert/strict";import t from"fs/promises";import s from"fs";import o from"path";import n from"postcss";import i from"postcss-8.4";import r from"node:test";const noopPlugin=()=>({postcssPlugin:"noop-plugin",Rule(){}});async function fileContentsOrEmptyString(e){try{return await t.readFile(e,"utf8")}catch(e){return""}}function reduceInformationInCssSyntaxError(e){process.env.DEBUG||(delete e.source,e.input&&delete e.input.source,delete e.postcssNode)}function postcssTape(a,c){c=c??{},r("`postcss` flag is set on exported plugin creator",(()=>{e.strictEqual(a.postcss,!0)})),r("exported plugin creator is a function",(()=>{e.strictEqual(typeof a,"function")})),r("`postcssPlugin` is set on a plugin instance",(()=>{const t=a();e.ok(t.postcssPlugin),e.strictEqual(typeof t.postcssPlugin,"string")})),r("package.json",(async s=>{const o=await t.readFile("./package.json","utf-8"),n=JSON.parse(o);await s.test('includes "postcss-plugin" keyword',(()=>{e.ok(n.keywords),e.ok(n.keywords.includes("postcss-plugin"))})),await s.test('name starts with "postcss-"',{skip:c?.skipPackageNameCheck},(()=>{let t=n.name;if(t.startsWith("@")){t=n.name.split("/").slice(1).join("/")}e.ok(t.startsWith("postcss-"),`package name "${t}" does not start with "postcss-"`)})),await s.test("`postcss` is a peer dependency and not a direct dependency",{skip:"postcssTapeSelfTest"in a},(()=>{e.ok(n.peerDependencies),e.ok(Object.keys(Object(n.peerDependencies)).includes("postcss")),e.ok(!Object.keys(Object(n.dependencies)).includes("postcss"))}))}));const p=a().postcssPlugin;return async c=>{await r(p,(async r=>{for(const p in c)await r.test(p,(async r=>{const l=c[p];l.before&&await l.before();const u=o.join(".","test",...p.split(":")[0].split(o.posix.sep)),d=o.join(".","test",...p.replace(/:/g,".").split(o.posix.sep)),f="css";let m=`${u}.${f}`,g=`${d}.expect.${f}`,w=`${d}.result.${f}`;l.source&&(m=o.join(".","test",l.source)),l.expect&&(g=o.join(".","test",l.expect)),l.result&&(w=o.join(".","test",l.result));const y=l.plugins??[a(l.options)],k=await fileContentsOrEmptyString(m),S=await fileContentsOrEmptyString(g);let x,E=!1;try{x=await n(y).process(k,{from:m,to:w,map:{inline:!1,annotation:!1}})}catch(t){if(reduceInformationInCssSyntaxError(t),E=!0,l.exception&&l.exception.test(t.message))return;e.ifError(t)}e.notEqual(!E,l.exception,"expected an exception but got none");const h=x.css.toString();{const e=[t.writeFile(w,h,"utf8")];process.env.REWRITE_EXPECTS&&e.push(t.writeFile(g,h,"utf8")),await Promise.all(e)}S||e.ok(s.existsSync(g),`Missing expect file: "${g}"`),await r.test("has expected output",(()=>{e.strictEqual(h,S),e.deepStrictEqual(x.warnings().length,l.warnings??0,"Unexpected number warnings")})),await r.test("sourcemaps",(async()=>{e.ok(!x.map.toJSON().sources.includes(""),'Sourcemap is broken. This is most likely a newly created PostCSS AST Node without a value for "source". See: https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#24-set-nodesource-for-new-nodes')})),l.after&&await l.after(),await r.test("output is parsable with PostCSS",(async()=>{const t=await fileContentsOrEmptyString(w),s=await n([noopPlugin()]).process(t,{from:w,to:w,map:{inline:!1,annotation:!1}});e.deepStrictEqual(s.warnings(),[],"Unexpected warnings on second pass")})),await r.test("The oldest and current PostCSS version produce the same result",{skip:n([noopPlugin()]).version===i([noopPlugin()]).version},(async()=>{const t=await i(y).process(k,{from:m,to:w,map:{inline:!1,annotation:!1}});e.strictEqual(t.css.toString(),h)}))}))}))}}noopPlugin.postcss=!0;const a={postcssPlugin:"declaration-cloner",Declaration(e){"to-clone"===e.prop&&e.cloneBefore({prop:"cloned"})}},c={postcssPlugin:"rule-cloner",prepare(){const e=new WeakSet;return{RuleExit(t){e.has(t)||"to-clone"===t.selector&&(e.add(t),t.cloneBefore({selector:"cloned"}))}}}},p={postcssPlugin:"at-rule-cloner",prepare(){const e=new WeakSet;return{AtRuleExit(t){if(!e.has(t))return"to-clone"===t.params?(e.add(t),void t.cloneBefore({params:"cloned"})):"to-clone"===t.name?(e.add(t),void t.cloneBefore({name:"cloned"})):void 0}}}};export{p as atRuleClonerPlugin,a as declarationClonerPlugin,postcssTape,c as ruleClonerPlugin}; +import e from"node:assert/strict";import t from"node:fs/promises";import s from"node:fs";import o from"node:path";import n from"postcss";import i from"postcss-8.4";import r from"node:test";import a from"node:url";const noopPlugin=()=>({postcssPlugin:"noop-plugin",Rule(){}});async function fileContentsOrEmptyString(e){try{return await t.readFile(e,"utf8")}catch(e){return""}}function reduceInformationInCssSyntaxError(e){process.env.DEBUG||(delete e.source,e.input&&delete e.input.source,delete e.postcssNode)}function postcssTape(a,c){c=c??{},r("`postcss` flag is set on exported plugin creator",(()=>{e.strictEqual(a.postcss,!0)})),r("exported plugin creator is a function",(()=>{e.strictEqual(typeof a,"function")})),r("`postcssPlugin` is set on a plugin instance",(()=>{const t=a();e.ok(t.postcssPlugin),e.strictEqual(typeof t.postcssPlugin,"string")})),r("package.json",(async s=>{const o=await t.readFile("./package.json","utf-8"),n=JSON.parse(o);await s.test('includes "postcss-plugin" keyword',(()=>{e.ok(n.keywords?.includes("postcss-plugin"),new PackageDescriptionError('Missing "postcss-plugin" keyword in package.json',"keywords"))})),await s.test('name starts with "postcss-"',{skip:c?.skipPackageNameCheck},(()=>{let t=n.name;if(t.startsWith("@")){t=n.name.split("/").slice(1).join("/")}e.ok(t.startsWith("postcss-"),new PackageDescriptionError(`package name "${t}" does not start with "postcss-"`,"name"))})),await s.test("`postcss` is a peer dependency and not a direct dependency",{skip:"postcssTapeSelfTest"in a},(()=>{e.ok(Object.keys(Object(n.peerDependencies)).includes("postcss"),new PackageDescriptionError('"postcss" is not listed in "peerDependencies"',"peerDependencies")),e.ok(!Object.keys(Object(n.dependencies)).includes("postcss"),new PackageDescriptionError('"postcss" must not be listed in "dependencies"',"dependencies"))}))}));const p=a().postcssPlugin;return async c=>{await r(p,(async r=>{for(const p in c)await r.test(p,(async r=>{const l=c[p];l.before&&await l.before();const u=o.join(".","test",...p.split(":")[0].split(o.posix.sep)),d=o.join(".","test",...p.replace(/:/g,".").split(o.posix.sep)),m="css";let g=`${u}.${m}`,f=`${d}.expect.${m}`,w=`${d}.result.${m}`;l.source&&(g=o.join(".","test",l.source)),l.expect&&(f=o.join(".","test",l.expect)),l.result&&(w=o.join(".","test",l.result));const k=l.plugins??[a(l.options)],y=await fileContentsOrEmptyString(g),E=await fileContentsOrEmptyString(f);let h,S=!1;try{h=await n(k).process(y,{from:g,to:w,map:{inline:!1,annotation:!1}})}catch(t){if(reduceInformationInCssSyntaxError(t),S=!0,l.exception&&l.exception.test(t.message))return;e.ifError(t)}e.notEqual(!S,l.exception,"expected an exception but got none");const x=h.css.toString();{const e=[t.writeFile(w,x,"utf8")];process.env.REWRITE_EXPECTS&&e.push(t.writeFile(f,x,"utf8")),await Promise.all(e)}E||e.ok(s.existsSync(f),`Missing expect file: "${f}"`),await r.test("has expected output",(()=>{e.strictEqual(x,E),e.deepStrictEqual(h.warnings().length,l.warnings??0,"Unexpected number warnings")})),await r.test("sourcemaps",(async()=>{e.ok(!h.map.toJSON().sources.includes(""),'Sourcemap is broken. This is most likely a newly created PostCSS AST Node without a value for "source". See: https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#24-set-nodesource-for-new-nodes')})),l.after&&await l.after(),await r.test("output is parsable with PostCSS",(async()=>{const t=await fileContentsOrEmptyString(w),s=await n([noopPlugin()]).process(t,{from:w,to:w,map:{inline:!1,annotation:!1}});e.deepStrictEqual(s.warnings(),[],"Unexpected warnings on second pass")})),await r.test("The oldest and current PostCSS version produce the same result",{skip:n([noopPlugin()]).version===i([noopPlugin()]).version},(async()=>{const t=await i(k).process(y,{from:g,to:w,map:{inline:!1,annotation:!1}});e.strictEqual(t.css.toString(),x)}))}))}))}}noopPlugin.postcss=!0;const c={postcssPlugin:"declaration-cloner",Declaration(e){"to-clone"===e.prop&&e.cloneBefore({prop:"cloned"})}},p={postcssPlugin:"rule-cloner",prepare(){const e=new WeakSet;return{RuleExit(t){e.has(t)||"to-clone"===t.selector&&(e.add(t),t.cloneBefore({selector:"cloned"}))}}}},l={postcssPlugin:"at-rule-cloner",prepare(){const e=new WeakSet;return{AtRuleExit(t){if(!e.has(t))return"to-clone"===t.params?(e.add(t),void t.cloneBefore({params:"cloned"})):"to-clone"===t.name?(e.add(t),void t.cloneBefore({name:"cloned"})):void 0}}}};class PackageDescriptionError extends Error{constructor(e,t){super(e),this.name="PackageDescriptionError",this.stack=`${this.name}: ${this.message}\n at "${t}" (${a.pathToFileURL(o.resolve("package.json")).pathname})`}}export{l as atRuleClonerPlugin,c as declarationClonerPlugin,postcssTape,p as ruleClonerPlugin}; /* node:coverage enable */ diff --git a/packages/postcss-tape/src/file-contents-or-empty-string.ts b/packages/postcss-tape/src/file-contents-or-empty-string.ts index 0183a57d2..253681e97 100644 --- a/packages/postcss-tape/src/file-contents-or-empty-string.ts +++ b/packages/postcss-tape/src/file-contents-or-empty-string.ts @@ -1,4 +1,4 @@ -import fs from 'fs/promises'; +import fs from 'node:fs/promises'; export async function fileContentsOrEmptyString(path: string): Promise { try { diff --git a/packages/postcss-tape/src/index.ts b/packages/postcss-tape/src/index.ts index 8552d3190..e74f67ccc 100644 --- a/packages/postcss-tape/src/index.ts +++ b/packages/postcss-tape/src/index.ts @@ -28,13 +28,14 @@ */ import assert from 'node:assert/strict'; -import fs from 'fs/promises'; -import fsSync from 'fs'; +import fs from 'node:fs/promises'; +import fsSync from 'node:fs'; import noopPlugin from './noop-plugin'; -import path from 'path'; +import path from 'node:path'; import postcss from 'postcss'; import postcssOldestSupported from 'postcss-8.4'; import test from 'node:test'; +import url from 'node:url'; import { fileContentsOrEmptyString } from './file-contents-or-empty-string'; import { reduceInformationInCssSyntaxError } from './reduce-css-syntax-error'; @@ -100,8 +101,10 @@ export function postcssTape(pluginCreator: PluginCreator, runOptions?: // https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#54-include-postcss-plugin-keyword-in-packagejson // Include postcss-plugin keyword in package.json - assert.ok(packageInfo.keywords); - assert.ok(packageInfo.keywords.includes('postcss-plugin')); + assert.ok( + packageInfo.keywords?.includes('postcss-plugin'), + new PackageDescriptionError('Missing "postcss-plugin" keyword in package.json', 'keywords'), + ); }); await t.test('name starts with "postcss-"', { skip: runOptions?.skipPackageNameCheck }, () => { @@ -114,16 +117,24 @@ export function postcssTape(pluginCreator: PluginCreator, runOptions?: packageName = parts.slice(1).join('/'); } - assert.ok(packageName.startsWith('postcss-'), `package name "${packageName}" does not start with "postcss-"`); + assert.ok( + packageName.startsWith('postcss-'), + new PackageDescriptionError(`package name "${packageName}" does not start with "postcss-"`, 'name'), + ); }); await t.test('`postcss` is a peer dependency and not a direct dependency', { skip: ('postcssTapeSelfTest' in pluginCreator) }, () => { // https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#14-keep-postcss-to-peerdependencies // Keep postcss to peerDependencies - assert.ok(packageInfo.peerDependencies); - assert.ok(Object.keys(Object(packageInfo.peerDependencies)).includes('postcss')); - assert.ok(!Object.keys(Object(packageInfo.dependencies)).includes('postcss')); + assert.ok( + Object.keys(Object(packageInfo.peerDependencies)).includes('postcss'), + new PackageDescriptionError('"postcss" is not listed in "peerDependencies"', 'peerDependencies'), + ); + assert.ok( + !Object.keys(Object(packageInfo.dependencies)).includes('postcss'), + new PackageDescriptionError('"postcss" must not be listed in "dependencies"', 'dependencies'), + ); }); }); @@ -329,3 +340,12 @@ export const atRuleClonerPlugin = { }; }, }; + +class PackageDescriptionError extends Error { + constructor(message: string, key: string) { + super(message); + + this.name = 'PackageDescriptionError'; + this.stack = `${this.name}: ${this.message}\n at "${key}" (${url.pathToFileURL(path.resolve('package.json')).pathname})`; + } +} diff --git a/rollup/configs/externals.mjs b/rollup/configs/externals.mjs index 002eac4a3..ddef6307b 100644 --- a/rollup/configs/externals.mjs +++ b/rollup/configs/externals.mjs @@ -1,11 +1,24 @@ export const externalsForCLI = [ + 'assert', + 'assert/strict', 'fs', 'fs/promises', 'https', - 'node:assert/strict', - 'node:test', + 'module', 'path', + 'process', + 'test', 'url', + 'node:assert', + 'node:assert/strict', + 'node:fs', + 'node:fs/promises', + 'node:https', + 'node:module', + 'node:path', + 'node:process', + 'node:test', + 'node:url', 'postcss', /^postcss-\d\.\d$/, @@ -92,14 +105,25 @@ export const externalsForCLI = [ export const externalsForPlugin = [ 'assert', + 'assert/strict', 'fs', 'fs/promises', 'https', 'module', - 'node:assert/strict', - 'node:test', 'path', + 'process', + 'test', 'url', + 'node:assert', + 'node:assert/strict', + 'node:fs', + 'node:fs/promises', + 'node:https', + 'node:module', + 'node:path', + 'node:process', + 'node:test', + 'node:url', 'postcss', /^postcss-\d\.\d$/,