diff --git a/plugins/stack/_shared/stylelint/mod.test.ts b/plugins/stack/_shared/stylelint/mod.test.ts new file mode 100644 index 0000000..6609699 --- /dev/null +++ b/plugins/stack/_shared/stylelint/mod.test.ts @@ -0,0 +1,57 @@ +import { context } from "../../../../src/plugin/mod.ts"; +import { assertEquals, deepMerge, WalkEntry } from "../../../../deps.ts"; + +import { introspect } from "./mod.ts"; + +const fakeContext = ( + { + withStylelint = true, + devDependencies = true, + } = {}, +) => { + return deepMerge( + context, + { + files: { + each: async function* (glob: string): AsyncIterableIterator { + if (glob === "**/package.json") { + yield { + name: "package.json", + path: "fake-path", + isFile: true, + isSymlink: false, + isDirectory: false, + }; + } + return; + }, + // deno-lint-ignore require-await + readJSON: async (path: string): Promise> => { + const deps = { stylelint: "1.0.0" }; + if (!withStylelint) return {}; + if (path === "fake-path") { + return devDependencies + ? { devDependencies: deps } + : { dependencies: deps }; + } + return {}; + }, + }, + }, + ); +}; + +Deno.test("Plugins > _shared > Stylelint - at devDependecies", async () => { + const result = await introspect(fakeContext()); + assertEquals(result, { name: "stylelint" }); +}); + +Deno.test("Plugins > _shared > Stylelint - at dependecies", async () => { + const result = await introspect(fakeContext({ devDependencies: false })); + assertEquals(result, { name: "stylelint" }); +}); + +Deno.test("Plugins > _shared > Stylelint - not present", async () => { + const result = await introspect(fakeContext({ withStylelint: false })); + assertEquals(result, null); +}); diff --git a/plugins/stack/_shared/stylelint/mod.ts b/plugins/stack/_shared/stylelint/mod.ts new file mode 100644 index 0000000..28a5834 --- /dev/null +++ b/plugins/stack/_shared/stylelint/mod.ts @@ -0,0 +1,25 @@ +import { IntrospectFn } from "../../deps.ts"; + +export interface Stylelint { + name: "stylelint"; +} + +export const introspect: IntrospectFn = async (context) => { + for await (const file of context.files.each("**/package.json")) { + const packageJson = await context.files.readJSON(file.path); + const packages = { + ...(packageJson?.dependencies || {}), + ...(packageJson?.devDependencies || {}), + }; + const hasStylelint = Object.keys(packages).some((name) => + name === "stylelint" + ); + if (hasStylelint) { + return { + "name": "stylelint", + }; + } + } + + return null; +}; diff --git a/plugins/stack/css/mod.ts b/plugins/stack/css/mod.ts new file mode 100644 index 0000000..a959e86 --- /dev/null +++ b/plugins/stack/css/mod.ts @@ -0,0 +1,76 @@ +import { Introspector } from "../deps.ts"; +import { + introspect as introspectFormatter, + Prettier, +} from "../_shared/prettier/mod.ts"; +import { + introspect as introspectLinter, + Stylelint, +} from "../_shared/stylelint/mod.ts"; +import { + introspect as introspectPackageManager, + NodePackageManager, +} from "../_shared/node_package_manager/mod.ts"; + +// Available package managers +type PackageManager = NodePackageManager | null; +// Available code formatters +type Formatter = Prettier | null; +// Available linters +type Linter = Stylelint | null; + +/** + * Introspected information about a project with JavaScript + */ +export default interface CssProject { + /** + * Which package manager is used in the project + */ + packageManager?: PackageManager; + /** + * Which formatter the project uses, if any + */ + formatter?: Formatter; + /** + * Which linter the project uses, if any + */ + linter?: Linter; +} + +export const introspector: Introspector = { + detect: async (context) => { + return await context.files.includes("**/*.{c,sc,sa,le}ss"); + }, + introspect: async (context) => { + const logger = context.getLogger("css"); + + // Package manager + logger.debug("detecting package manager"); + const packageManager = await introspectPackageManager(context); + logger.debug(`detected package manager "${packageManager.name}"`); + + // Formatter + logger.debug("detecting formatter"); + const formatter = await introspectFormatter(context); + if (formatter !== null) { + logger.debug(`detected formatter "${formatter.name}"`); + } else { + logger.debug("no supported formatter detected"); + } + + // Linter + logger.debug("detecting linter"); + const linter = await introspectLinter(context); + if (linter !== null) { + logger.debug(`detected linter "${linter.name}"`); + } else { + logger.debug("no supported linter detected"); + } + + return { + packageManager, + formatter, + linter, + }; + }, +}; diff --git a/plugins/stack/mod.ts b/plugins/stack/mod.ts index 6b799f8..8a2d083 100644 --- a/plugins/stack/mod.ts +++ b/plugins/stack/mod.ts @@ -1,16 +1,20 @@ +import { introspector as CssIntrospector } from "./css/mod.ts"; import { introspector as JavaScriptIntrospector } from "./javascript/mod.ts"; import { introspector as PythonIntrospector } from "./python/mod.ts"; +import type CSSProject from "./css/mod.ts"; import type JavaScriptProject from "./javascript/mod.ts"; import type PythonProject from "./python/mod.ts"; export type ProjectData = + | CSSProject | JavaScriptProject | PythonProject; -export type { JavaScriptProject, PythonProject }; +export type { CSSProject, JavaScriptProject, PythonProject }; export const introspectors = [ + { name: "css", ...CssIntrospector }, { name: "javascript", ...JavaScriptIntrospector }, { name: "python", ...PythonIntrospector }, ]; diff --git a/templates/github/css/format.yaml b/templates/github/css/format.yaml new file mode 100644 index 0000000..cfe88d5 --- /dev/null +++ b/templates/github/css/format.yaml @@ -0,0 +1,30 @@ +<% if (it.formatter) { -%> +name: Format CSS +on: + pull_request: + paths: + - '**.css' + - '**.scss' + - '**.sass' + - '**.less' +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + cache: '<%= it.packageManager.name %>' + + <%_ let installCmd; let runPrefix; -%> + <%_ if (it.packageManager.name === "npm") { -%> + <%_ installCmd = "npm ci" %> + <%_ runPrefix = "npx" %> + <%_ } else { -%> + <%_ installCmd = "yarn" %> + <%_ runPrefix = "yarn" %> + <% } -%> + + - run: <%= installCmd %> + - run: <%= runPrefix %> prettier --no-error-on-unmatched-pattern --check "**.css" "**.scss" "**.sass" "**.less" +<% } -%> diff --git a/templates/github/css/lint.yaml b/templates/github/css/lint.yaml new file mode 100644 index 0000000..4c706bd --- /dev/null +++ b/templates/github/css/lint.yaml @@ -0,0 +1,30 @@ +<% if (it.formatter) { -%> +name: Lint CSS +on: + pull_request: + paths: + - '**.css' + - '**.scss' + - '**.sass' + - '**.less' +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + cache: '<%= it.packageManager.name %>' + + <%_ let installCmd; let runPrefix; -%> + <%_ if (it.packageManager.name === "npm") { -%> + <%_ installCmd = "npm ci" %> + <%_ runPrefix = "npx" %> + <%_ } else { -%> + <%_ installCmd = "yarn" %> + <%_ runPrefix = "yarn" %> + <% } -%> + + - run: <%= installCmd %> + - run: <%= runPrefix %> stylelint "**/*.css" "**/*.scss" "**/*.sass" "**/*.less" +<% } -%>