Skip to content

Commit 20364fa

Browse files
authored
feat: plugin, no prompt, bug fixes (#139)
* high hopes * s * plugin calling * add * eol * more progress * fmt * step 1 * refactor out * /internal route * prg * write handler * consolidate * fix version gen * prototype * out * fix merge * bundle presence and event modules * extrapolate into plugin * pluginify and simplify * fix up typing gen for process env and watch mode * watch * add nonpromptable plugins install * fix regression and clean up publish script * ea
1 parent d581142 commit 20364fa

File tree

13 files changed

+355
-218
lines changed

13 files changed

+355
-218
lines changed

src/commands/build.ts

Lines changed: 65 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import esbuild from 'esbuild';
22
import { getConfig } from '../utilities/getConfig';
3-
import { resolve } from 'node:path';
3+
import p from 'node:path';
44
import { glob } from 'glob';
55
import { configDotenv } from 'dotenv';
66
import assert from 'node:assert';
77
import defaultEsbuild from '../utilities/defaultEsbuildConfig';
88
import { require } from '../utilities/require';
99
import { pathExists, pathExistsSync } from 'find-up';
10-
import { mkdir, writeFile } from 'fs/promises';
10+
import { mkdir, writeFile, readFile } from 'fs/promises';
1111
import * as Preprocessor from '../utilities/preprocessor';
1212
import { bold, magentaBright } from 'colorette';
13-
const validExtensions = ['.ts', '.js', '.json', '.png', '.jpg', '.jpeg', '.webp'];
1413

14+
const VALID_EXTENSIONS = ['.ts', '.js' ];
1515

1616
type BuildOptions = {
1717
/**
@@ -24,10 +24,6 @@ type BuildOptions = {
2424
* default = esm
2525
*/
2626
format?: 'cjs' | 'esm';
27-
/**
28-
* extra esbuild plugins to build with sern.
29-
*/
30-
esbuildPlugins?: esbuild.Plugin[];
3127
/**
3228
* https://esbuild.github.io/api/#drop-labels
3329
**/
@@ -49,77 +45,72 @@ type BuildOptions = {
4945
env?: string;
5046
};
5147

48+
const CommandHandlerPlugin = (buildConfig: Partial<BuildOptions>, ambientFilePath: string, sernTsConfigPath: string) => {
49+
return {
50+
name: "commandhandler",
51+
setup(build) {
52+
53+
const options = build.initialOptions
54+
const defVersion = () => JSON.stringify(require(p.resolve('package.json')).version);
55+
options.define = {
56+
...buildConfig.define ?? {},
57+
__DEV__: `${buildConfig.mode === 'development'}`,
58+
__PROD__: `${buildConfig.mode === 'production'}`,
59+
__VERSION__: `${buildConfig.defineVersion ? `${defVersion()}` : 'undefined'}`
60+
} ?? {}
61+
Preprocessor.writeTsConfig(buildConfig.format!, sernTsConfigPath, writeFile);
62+
Preprocessor.writeAmbientFile(ambientFilePath, options.define!, writeFile);
63+
64+
}
65+
} as esbuild.Plugin
66+
}
67+
const resolveBuildConfig = (path: string|undefined, language: string) => {
68+
if(language === 'javascript') {
69+
return path ?? 'jsconfig.json'
70+
}
71+
return path ?? 'tsconfig.json'
72+
}
73+
5274
export async function build(options: Record<string, any>) {
5375
if (!options.supressWarnings) {
5476
console.info(`${magentaBright('EXPERIMENTAL')}: This API has not been stabilized. add -W or --suppress-warnings flag to suppress`);
5577
}
5678
const sernConfig = await getConfig();
57-
let buildConfig: Partial<BuildOptions> = {};
58-
59-
const entryPoints = await glob(`./src/**/*{${validExtensions.join(',')}}`, {
60-
//for some reason, my ignore glob wasn't registering correctly'
61-
ignore: {
62-
ignored: (p) => p.name.endsWith('.d.ts'),
63-
},
64-
});
65-
66-
const buildConfigPath = resolve(options.project ?? 'sern.build.js');
67-
68-
const resolveBuildConfig = (path: string|undefined, language: string) => {
69-
if(language === 'javascript') {
70-
return path ?? resolve('jsconfig.json')
71-
}
72-
return path ?? resolve('tsconfig.json')
73-
}
79+
let buildConfig: BuildOptions;
80+
const buildConfigPath = p.resolve(options.project ?? 'sern.build.js');
7481

7582
const defaultBuildConfig = {
7683
defineVersion: true,
7784
format: options.format ?? 'esm',
7885
mode: options.mode ?? 'development',
7986
dropLabels: [],
8087
tsconfig: resolveBuildConfig(options.tsconfig, sernConfig.language),
81-
env: options.env ?? resolve('.env'),
88+
env: options.env ?? '.env',
89+
include: []
8290
};
8391
if (pathExistsSync(buildConfigPath)) {
8492
//throwable, buildConfigPath may not exist
85-
buildConfig = {
86-
...defaultBuildConfig,
87-
...(await import('file:///' + buildConfigPath)).default,
88-
};
93+
buildConfig = { ...defaultBuildConfig, ...(await import('file:///' + buildConfigPath)).default };
8994
} else {
9095
buildConfig = defaultBuildConfig;
9196
console.log('No build config found, defaulting');
9297
}
93-
94-
let env = {} as Record<string, string>;
95-
configDotenv({ path: buildConfig.env, processEnv: env });
96-
const modeButNotNodeEnvExists = env.MODE && !env.NODE_ENV;
97-
if (modeButNotNodeEnvExists) {
98-
console.warn('Use NODE_ENV instead of MODE');
99-
console.warn('MODE has no effect.');
100-
console.warn(`https://nodejs.org/en/learn/getting-started/nodejs-the-difference-between-development-and-production`);
101-
}
102-
103-
if (env.NODE_ENV) {
104-
buildConfig.mode = env.NODE_ENV as 'production' | 'development';
98+
configDotenv({ path: buildConfig.env });
99+
100+
if (process.env.NODE_ENV) {
101+
buildConfig.mode = process.env.NODE_ENV as 'production' | 'development';
105102
console.log(magentaBright('NODE_ENV:'), 'Found NODE_ENV variable, setting `mode` to this.');
106103
}
107-
108104
assert(buildConfig.mode === 'development' || buildConfig.mode === 'production', 'Mode is not `production` or `development`');
109-
110-
111105
try {
112-
let config = require(buildConfig.tsconfig!);
106+
let config = JSON.parse(await readFile(buildConfig.tsconfig!, 'utf8'));
113107
config.extends && console.warn("Extend the generated tsconfig")
114108
} catch(e) {
115-
console.warn("no tsconfig / jsconfig found");
116-
console.warn(`Please create a ${sernConfig.language === 'javascript' ? 'jsconfig.json' : 'tsconfig.json' }`);
117-
console.warn("It should have at least extend the generated one sern makes.")
118-
console.warn(`
119-
{
120-
"extends": "./.sern/tsconfig.json",
121-
}`.trim())
122-
throw e;
109+
console.error("no tsconfig / jsconfig found");
110+
console.error(`Please create a ${sernConfig.language === 'javascript' ? 'jsconfig.json' : 'tsconfig.json' }`);
111+
console.error('It should have at least extend the generated one sern makes.\n \
112+
{ "extends": "./.sern/tsconfig.json" }');
113+
throw e;
123114
}
124115

125116
console.log(bold('Building with:'));
@@ -129,41 +120,32 @@ export async function build(options: Record<string, any>) {
129120
console.log(' ', magentaBright('tsconfig'), buildConfig.tsconfig);
130121
console.log(' ', magentaBright('env'), buildConfig.env);
131122

132-
const sernDir = resolve('.sern'),
133-
genDir = resolve(sernDir, 'generated'),
134-
ambientFilePath = resolve(sernDir, 'ambient.d.ts'),
135-
packageJsonPath = resolve('package.json'),
136-
sernTsConfigPath = resolve(sernDir, 'tsconfig.json'),
137-
packageJson = () => require(packageJsonPath);
123+
const sernDir = p.resolve('.sern'),
124+
[ambientFilePath, sernTsConfigPath, genDir] =
125+
['ambient.d.ts', 'tsconfig.json', 'generated'].map(f => p.resolve(sernDir, f));
138126

139127
if (!(await pathExists(genDir))) {
140128
console.log('Making .sern/generated dir, does not exist');
141129
await mkdir(genDir, { recursive: true });
142130
}
131+
132+
const entryPoints = await glob(`src/**/*{${VALID_EXTENSIONS.join(',')}}`,{
133+
ignore: {
134+
ignored: (p) => p.name.endsWith('.d.ts'),
135+
}
136+
});
137+
//https://esbuild.github.io/content-types/#tsconfig-json
138+
const ctx = await esbuild.context({
139+
entryPoints,
140+
plugins: [CommandHandlerPlugin(buildConfig, ambientFilePath, sernTsConfigPath)],
141+
...defaultEsbuild(buildConfig.format!, buildConfig.tsconfig),
142+
dropLabels: [buildConfig.mode === 'production' ? '__DEV__' : '__PROD__', ...buildConfig.dropLabels!],
143+
});
143144

144-
try {
145-
const defVersion = () => JSON.stringify(packageJson().version);
146-
const define = {
147-
...(buildConfig.define ?? {}),
148-
__DEV__: `${buildConfig.mode === 'development'}`,
149-
__PROD__: `${buildConfig.mode === 'production'}`,
150-
} satisfies Record<string, string>;
151-
152-
buildConfig.defineVersion && Object.assign(define, { __VERSION__: defVersion() });
153-
154-
await Preprocessor.writeTsConfig(buildConfig.format!, sernTsConfigPath, writeFile);
155-
await Preprocessor.writeAmbientFile(ambientFilePath, define, writeFile);
156-
157-
//https://esbuild.github.io/content-types/#tsconfig-json
158-
await esbuild.build({
159-
entryPoints,
160-
plugins: [...(buildConfig.esbuildPlugins ?? [])],
161-
...defaultEsbuild(buildConfig.format!, buildConfig.tsconfig),
162-
define,
163-
dropLabels: [buildConfig.mode === 'production' ? '__DEV__' : '__PROD__', ...buildConfig.dropLabels!],
164-
});
165-
} catch (e) {
166-
console.error(e);
167-
process.exit(1);
145+
await ctx.rebuild()
146+
if(options.watch) {
147+
await ctx.watch()
148+
} else {
149+
await ctx.dispose()
168150
}
169151
}

src/commands/plugins.ts

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,71 @@ import { greenBright } from 'colorette';
22
import fs from 'fs';
33
import prompt from 'prompts';
44
import { fetch } from 'undici';
5-
import { pluginsQ } from '../prompts/plugin.js';
65
import { fromCwd } from '../utilities/fromCwd.js';
76
import esbuild from 'esbuild';
8-
import { getLang } from '../utilities/getLang.js';
7+
import { getConfig } from '../utilities/getConfig.js';
8+
import type { PromptObject } from 'prompts';
99
import { resolve } from 'path';
1010
import { require } from '../utilities/require.js';
11+
1112
interface PluginData {
1213
description: string;
1314
hash: string;
1415
name: string;
1516
author: string[];
1617
link: string;
1718
example: string;
18-
version: '1.0.0';
19+
version: string;
20+
}
21+
22+
const link = `https://raw.githubusercontent.com/sern-handler/awesome-plugins/main/pluginlist.json`;
23+
export async function fetchPluginData(): Promise<PluginData[]> {
24+
return fetch(link)
25+
.then(res => res.json())
26+
.then(data => (data as PluginData[]))
27+
.catch(() => [])
1928
}
2029

30+
export function pluginsQ(choices: PluginData[]): PromptObject[] {
31+
return [{
32+
name: 'list',
33+
type: 'autocompleteMultiselect',
34+
message: 'What plugins do you want to install?',
35+
choices: choices.map(e => ({ title: e.name, value: e })),
36+
min: 1,
37+
}];
38+
}
2139
/**
2240
* Installs plugins to project
2341
*/
24-
export async function plugins() {
25-
const e: PluginData[] = (await prompt([await pluginsQ()])).list;
26-
if (!e) process.exit(1);
27-
28-
const lang = await getLang();
29-
for await (const plgData of e) {
42+
export async function plugins(args: string[], opts: Record<string, unknown>) {
43+
const plugins = await fetchPluginData();
44+
let selectedPlugins : PluginData[];
45+
if(args.length) {
46+
const normalizedArgs = args.map(str => str.toLowerCase())
47+
console.log("Trying to find plugins to install...");
48+
const results = plugins.reduce((acc, cur) => {
49+
if(normalizedArgs.includes(cur.name.toLowerCase())) {
50+
return [...acc, cur]
51+
}
52+
return acc;
53+
}, [] as PluginData[]);
54+
selectedPlugins = results;
55+
} else {
56+
selectedPlugins = (await prompt(pluginsQ(plugins))).list;
57+
}
58+
if (!selectedPlugins.length) {
59+
process.exit(1);
60+
}
61+
const { language } = await getConfig();
62+
for await (const plgData of selectedPlugins) {
3063
const pluginText = await download(plgData.link);
3164
const dir = fromCwd('/src/plugins');
3265
const linkNoExtension = `${process.cwd()}/src/plugins/${plgData.name}`;
3366
if (!fs.existsSync(dir)) {
3467
fs.mkdirSync(dir, { recursive: true });
3568
}
36-
if (lang === 'typescript') {
69+
if (language === 'typescript') {
3770
fs.writeFileSync(linkNoExtension + '.ts', pluginText);
3871
} else {
3972
const { type = undefined } = require(resolve('package.json'));
@@ -54,7 +87,7 @@ export async function plugins() {
5487
}
5588
}
5689

57-
const pluginNames = e.map((data) => {
90+
const pluginNames = selectedPlugins.map((data) => {
5891
return 'Installed ' + data.name + ' ' + 'from ' + data.author.join(',');
5992
});
6093
console.log(`Successfully downloaded plugin(s):\n${greenBright(pluginNames.join('\n'))}`);

0 commit comments

Comments
 (0)