Skip to content

Commit

Permalink
add glob matching to excludes list (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
benfc1993 authored Oct 18, 2024
1 parent 94b5d63 commit d02408f
Show file tree
Hide file tree
Showing 12 changed files with 322 additions and 183 deletions.
11 changes: 5 additions & 6 deletions .github/workflows/publish-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Publish Package to npmjs
run: |
yarn
yarn build
npm publish
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ src
tsconfig.build.json
tsconfig.json
jest.config.ts
.prettierrc.json
5 changes: 5 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"singleQuote": true,
"trailingComma": "es5",
"semi": false
}
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,18 @@ In order to change the behaviour of `package.json` scripts and add any other scr
```typescript
type Options =
| {
args?: boolean;
argumentsLabel?: string;
exec?: string;
args?: boolean
argumentsLabel?: string
exec?: string
}
| {
options: Options;
};
options: Options
}

type Config = {
exclude: string[];
options: Options;
};
exclude: string[]
options: Options
}
```
For each option entry you can provide the following:
Expand Down Expand Up @@ -76,29 +76,30 @@ For each option entry you can provide the following:
export default {
// This list will exclude scripts with this name from being added at the top level by default.
// Adding them manually to the options will allow them to still be selected.
exclude: ["db:create:migration", "db:migrate:latest", "scripts"],
// excludes can also be globs where a single `*` is treated as a wildcard
exclude: ['scripts', 'db:*'],
// These are the options to be presented.
// For package.json scripts the key needs to be the same as in package.json
options: {
test: {
args: true, // This will ask the user to provide arguments then run 'npm run test' followed by any provided arguments.
},
"Create test file": {
'Create test file': {
args: true, // This will ask the user for any arguments
argsLabel: "File name", // This will be the label used when asking for arguments input.
exec: "./scripts/create-testfile.sh", // This will then run ./script/create-testfile.sh followed by any arguments provided.
argsLabel: 'File name', // This will be the label used when asking for arguments input.
exec: './scripts/create-testfile.sh', // This will then run ./script/create-testfile.sh followed by any arguments provided.
},
db: {
options: {
// This will mean whenever 'db' is selected from the list nothing will be run
// but a new list consisting of 'create migration' and 'migrate latest' will show.
"create migration": {
// but a new list consisting of 'create migration' and 'db:migrate:latest' will show.
'create migration': {
args: true,
exec: "npm run db:create:migration",
exec: 'npm run db:create:migration',
},
"db:migrate:latest": {}, // As this key matches a script in package.json selecting this will run 'npm run db:migrate:latest'
'db:migrate:latest': {}, // As this key matches a script in package.json selecting this will run 'npm run db:migrate:latest'
},
},
},
};
}
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "scripts-cli",
"version": "2.1.4",
"version": "2.2.0",
"description": "CLI to run package json scripts and any other scripts in an npm project.",
"main": "./dist/index.js",
"types": "./dist/types.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/global.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
declare module "fuzzy-search";
declare module 'fuzzy-search'
63 changes: 27 additions & 36 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,44 @@
#!/usr/bin/env node

import child_process from "child_process";
import { Command } from "commander";
import { readFileSync } from "fs";
import path from "path";
import { cwd } from "process";
import { Config, Options } from "./types";
import { presentOptions } from "./presentOptions";

const program = new Command();

async function run(options: Record<string, string | boolean | string[]> = {}) {
const { config: configPath = "." } = options;
import child_process from 'child_process'
import { Command } from 'commander'
import { readFileSync } from 'fs'
import path from 'path'
import { cwd } from 'process'
import { Config } from './types'
import { presentOptions } from './presentOptions'
import { parseOptions } from './parseOptions'

const program = new Command()

async function run(
cliOptions: Record<string, string | boolean | string[]> = {}
) {
const { config: configPath = '.' } = cliOptions

const { default: config } = (await import(
path.join(
path.resolve(cwd(), configPath as string),
"/.scriptscli.config.mjs",
'/.scriptscli.config.mjs'
)
).catch(() => ({ options: {} }))) as { default: Config };

const packageJson = readFileSync(path.join(`${cwd()}/package.json`));
const data = JSON.parse(packageJson.toString());
).catch(() => ({ options: {} }))) as { default: Config | undefined }

const npmScripts = Object.keys(data.scripts)
.filter((script) => {
if (!config) return true;
return !config.exclude?.includes(script);
})
.reduce((acc: Options, script) => {
acc[script] = {};
return acc;
}, {});
const packageJson = readFileSync(path.join(`${cwd()}/package.json`))
const data = JSON.parse(packageJson.toString())

try {
const { cmd, args } = await presentOptions({
...npmScripts,
...config?.options,
});
child_process.spawn(cmd + " " + args, { stdio: "inherit", shell: true });
const options = parseOptions(config, data.scripts)
const { cmd, args } = await presentOptions(options)
child_process.spawn(cmd + ' ' + args, { stdio: 'inherit', shell: true })
} catch (e) {
return;
return
}
}

program
.action(run)
.option(
"-c,--config <FilePath>",
"location of config file other than project root",
);
program.parse(process.argv);
'-c,--config <FilePath>',
'location of config file other than project root'
)
program.parse(process.argv)
91 changes: 91 additions & 0 deletions src/parseOptions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { parseOptions } from './parseOptions'
import { Config } from './types'

const npmScripts = { first: 'test', second: 'test' }

describe('parseOptions', () => {
describe('options', () => {
it('should return all npm scripts if no options override', () => {
const config: Config = { options: {} }

const result = parseOptions(config, npmScripts)

expect(result).toStrictEqual({ first: {}, second: {} })
})

it('should allow config to be undefined', () => {
const result = parseOptions(undefined, npmScripts)

expect(result).toStrictEqual({ first: {}, second: {} })
})

it('should combine config and npm scripts', () => {
const config: Config = { options: { third: { exec: 'testing' } } }

const result = parseOptions(config, npmScripts)

expect(result).toStrictEqual({
first: {},
second: {},
third: { exec: 'testing' },
})
})

it('should use config to override npm scripts', () => {
const config: Config = { options: { second: { exec: 'testing' } } }

const result = parseOptions(config, npmScripts)

expect(result).toStrictEqual({
first: {},
second: { exec: 'testing' },
})
})
})
describe('exclude', () => {
it('should not exclude any when exclude is omitted', () => {
const config: Config = {}

const result = parseOptions(config, npmScripts)

expect(result).toStrictEqual({
first: {},
second: {},
})
})

it('should exclude full name matches', () => {
const config: Config = { exclude: ['first'] }

const result = parseOptions(config, npmScripts)

expect(result).toStrictEqual({
second: {},
})
})

it('should exclude glob matches', () => {
const config: Config = { exclude: ['fi*'] }

const result = parseOptions(config, npmScripts)

expect(result).toStrictEqual({
second: {},
})
})

it('should not filter out config options', () => {
const config: Config = {
exclude: ['fi*'],
options: { final: { exec: 'testing' } },
}

const result = parseOptions(config, npmScripts)

expect(result).toStrictEqual({
second: {},
final: { exec: 'testing' },
})
})
})
})
36 changes: 36 additions & 0 deletions src/parseOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Config, Options } from './types'

export function parseOptions(
config: Config | undefined,
scripts: Record<string, string>
): Options {
const { names, globs } = (config?.exclude ?? []).reduce(
(acc: { names: string[]; globs: string[] }, exc) => {
if (exc.includes('*')) acc.globs.push(exc.replace(/\*/g, ''))
else acc.names.push(exc)
return acc
},
{ names: [], globs: [] }
)

const npmScripts = Object.keys(scripts)
.filter((script) => {
if (!config || !config.exclude) return true

let globMatch = false
for (const glob of globs) {
globMatch = script.startsWith(glob)
if (globMatch) break
}

if (globMatch) return false

return !names.includes(script)
})
.reduce((acc: Options, script) => {
acc[script] = {}
return acc
}, {})

return { ...npmScripts, ...config?.options }
}
Loading

0 comments on commit d02408f

Please sign in to comment.