Skip to content
Merged
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ An every-growing collection of JavaScript utility packages targeting Node.js and
<!-- pkg-list-start -->
* [@bust/config](packages/config)
* Schema-driven configuration management for Node.js or the browser
* [@bust/eslint-config](packages/eslint-config)
* Shared ESLint configuration for Busticated JS/TS projects
* [@bust/numeric-id-hasher](packages/numeric-id-hasher)
* Encode / decode bigints as string hashes for use in shortened urls
* [@bust/periodical](packages/periodical)
Expand Down
10 changes: 5 additions & 5 deletions bin/create-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ try {
licenseMD,
packageJSON,
readmeMD,
tsconfigJSON
tsconfigJSON,
} = await loadTemplates();

await Promise.all([
Expand All @@ -35,7 +35,7 @@ try {
fs.outputFile(path.join(pkg.path, 'package.json'), packageJSON(pkg)),
fs.outputFile(path.join(pkg.path, 'tsconfig.json'), tsconfigJSON(pkg)),
fs.outputFile(path.join(pkg.path, 'src', 'index.test.ts'), indexTestTS(pkg)),
fs.outputFile(path.join(pkg.path, 'src', 'index.ts'), indexTS(pkg))
fs.outputFile(path.join(pkg.path, 'src', 'index.ts'), indexTS(pkg)),
]);

await execa('npm', ['install'], { stdio: 'inherit' }); // so root lockfile includes new package
Expand Down Expand Up @@ -73,14 +73,14 @@ async function promptForPackageInfo(cli){
return `Directory "${pkg.path}" already exists! please choose a different name.`;
}
return true;
}
},
},
{
type: 'input',
name: 'description',
message: 'What does your package do?',
result: (description) => description || ''
}
result: (description) => description || '',
},
]);

return new Pkg({ name, description });
Expand Down
9 changes: 9 additions & 0 deletions bin/lib/git.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,14 @@ export class Git {
async log(args, options) {
return this.exec(['log', ...args], options);
}

async hasTag(tag, options) {
try {
await this.exec(['rev-parse', '--verify', '--quiet', `refs/tags/${tag}`], options);
return true;
} catch {
return false;
}
}
}

4 changes: 2 additions & 2 deletions bin/lib/load-templates.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export const loadTemplates = async () => {
templates[key] = _.template(tmplData, {
imports: {
camelCase: _.camelCase,
upperFirst: _.upperFirst
}
upperFirst: _.upperFirst,
},
});
}

Expand Down
2 changes: 1 addition & 1 deletion bin/lib/log.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const logBanner = () => {
' _ | \\___ \\\\ \\ / /| | | |/ _ \\',
' | |_| |___) |\\ V / | | | | __/',
' \\___/|____/ \\_/ |_|_|_|\\___|',
''
'',
].forEach(log);
};

Expand Down
35 changes: 27 additions & 8 deletions bin/lib/pkg.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ const README_MARKER_START = '<!-- api-docs-start -->';
const README_MARKER_END = '<!-- api-docs-end -->';



export class Pkg {
static async createFromDisk(name) {
const pkg = new Pkg({ name });
Expand All @@ -34,6 +33,7 @@ export class Pkg {
this.readmePath = path.join(this.path, 'README.md');
this.jsonPath = path.join(this.path, 'package.json');
this.json = null;
this.publishedTag = undefined;
this.git = new Git();
}

Expand All @@ -55,17 +55,36 @@ export class Pkg {
return fs.readJson(this.jsonPath);
}

async getUnpublishedChanges() {
const { name, basename } = this;
// the tag the last published release is on, or `null` when this repo has
// none - a package that has never been published, or one whose earlier
// versions were published from somewhere else
async getPublishedTag() {
if (this.publishedTag !== undefined) {
return this.publishedTag;
}

let version;

try {
({ stdout: version } = await execa('npm', ['view', name, 'version']));
({ stdout: version } = await execa('npm', ['view', this.name, 'version']));
} catch {
version = '0.0.0';
this.publishedTag = null;
return this.publishedTag;
}

const range = `${name}@${version}..HEAD`;
const tag = `${this.name}@${version}`;

this.publishedTag = await this.git.hasTag(tag) ? tag : null;

return this.publishedTag;
}

async getUnpublishedChanges() {
const { basename } = this;
const tag = await this.getPublishedTag();
// without a published release to diff against, every commit touching
// this package is unpublished
const range = tag ? `${tag}..HEAD` : 'HEAD';
const query = `--grep=\\[${basename}\\]`;
const fmt = '--pretty=format:%B';
const prefix = `[${basename}]`;
Expand Down Expand Up @@ -106,7 +125,7 @@ export class Pkg {
lines.push('\n');

return this.writeChangelog(
content.replace(ptn, lines.join(''))
content.replace(ptn, lines.join('')),
);
}

Expand All @@ -132,7 +151,7 @@ export class Pkg {
];

return this.writeReadme(
content.replace(ptn, link.join('\n'))
content.replace(ptn, link.join('\n')),
);
}
}
8 changes: 8 additions & 0 deletions bin/show-unpublished-changes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,21 @@ import { Pkg } from './lib/pkg.mjs';
try {
const args = process.argv.slice(2);
const pkg = await Pkg.createFromDisk(args[0].trim());
const tag = await pkg.getPublishedTag();
const changes = await pkg.getUnpublishedChanges();

logTitle(`${pkg.name} - Unpublished Changes`);
log(tag
? `:::: since ${tag}`
: ':::: no published release tagged in this repo - showing every change so far');

for (const change of changes) {
log(`* ${change}`);
}

if (!changes.length) {
log('* none');
}
} catch (error) {
logErrorAndExit(error);
}
Expand Down
2 changes: 1 addition & 1 deletion bin/update-readme.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const PKGLIST_MARKER_END = '<!-- pkg-list-end -->';


try {
logTitle(`Updating package list in top-level README...`);
logTitle('Updating package list in top-level README...');
const pkgs = await getPkgs(PACKAGES_DIR);
const listMarkdown = renderPackageList(pkgs);
const readme = await fs.readFile(README_PATH, 'utf8');
Expand Down
129 changes: 19 additions & 110 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,123 +1,32 @@
import path from 'node:path';
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import globals from 'globals';
import { includeIgnoreFile } from '@eslint/compat';
import bust from '@bust/eslint-config';

export default tseslint.config(
export default [
includeIgnoreFile(path.join(import.meta.dirname, '.gitignore')),

...bust({ nodeTest: true, ignores: ['**/docs/**'] }),

{
ignores: ['**/docs/**']
},
js.configs.recommended,
tseslint.configs.recommended,
{
languageOptions: {
ecmaVersion: 2024,
globals: {
...globals['shared-node-browser'],
...globals.commonjs,
process: true
}
},
name: 'jsville/tests',
files: ['**/*.{spec,test,e2e,integration}.{js,mjs,ts,mts}'],
rules: {
'array-bracket-spacing': ['error', 'never'],
'arrow-spacing': 'error',
'block-scoped-var': 'error',
'block-spacing': 'error',
'brace-style': ['error', '1tbs'],
'camelcase': ['error', {
properties: 'never'
}],
'constructor-super': 'error',
'curly': 'error',
'eol-last': 'error',
'eqeqeq': ['error', 'smart'],
'func-names': ['error', 'as-needed'],
'func-style': ['error', 'declaration', {
allowArrowFunctions: true
}],
'indent': ['error', 'tab', {
SwitchCase: 1,
ObjectExpression: 'first',
MemberExpression: 1,
ignoredNodes: ['TemplateLiteral *']
}],
'keyword-spacing': ['error', {
before: true,
after: true,
}],
'max-depth': ['warn', 3],
'max-len': ['error', {
code: 120,
ignoreComments: true,
ignoreTrailingComments: true,
ignoreUrls: true,
ignoreStrings: true,
ignoreTemplateLiterals: true
}],
'max-statements': ['warn', 30],
'new-cap': 'warn',
'no-class-assign': 'error',
'no-cond-assign': ['error', 'except-parens'],
'no-console': 'error',
'no-const-assign': 'error',
'no-dupe-class-members': 'error',
'no-extend-native': 'error',
'no-extra-parens': ['error', 'functions'],
'no-mixed-spaces-and-tabs': 'error',
'no-multi-spaces': ['error', {
exceptions: {
VariableDeclarator: true,
}
}],
'no-nested-ternary': 'error',
'no-prototype-builtins': 'off',
'no-this-before-super': 'error',
'no-trailing-spaces': 'error',
'no-undef': 'error',
'no-underscore-dangle': 'off',
'no-use-before-define': ['error', {
functions: false,
classes: false
}],
'no-whitespace-before-property': 'error',
'no-var': 'error',
'object-curly-spacing': ['error', 'always'],
'prefer-const': 'error',
'quotes': ['error', 'single', {
avoidEscape: true,
allowTemplateLiterals: true
}],
'semi': ['error', 'always'],
'space-in-parens': ['error', 'never'],
'space-infix-ops': 'error',
'space-unary-ops': ['error', {
words: true,
nonwords: false
}],
'strict': ['error', 'global']
}
'func-names': 'off',
},
},

{
files: [
'**/*.spec.js',
'**/*.test.js',
'**/*.e2e.js',
'**/*.integration.js',
'**/*.spec.ts',
'**/*.test.ts',
'**/*.e2e.ts',
'**/*.integration.ts',
],
name: 'jsville/prototype-builtins',
rules: {
'func-names': 'off'
}
'no-prototype-builtins': 'off',
},
},

{
name: 'jsville/logger',
files: ['bin/lib/log.mjs'],
rules: {
'no-console': 'off'
}
}
);
'no-console': 'off',
},
},
];
Loading