Skip to content

Commit 11bcf46

Browse files
committed
ci: npm package
1 parent 5103093 commit 11bcf46

25 files changed

Lines changed: 923 additions & 1125 deletions

.github/workflows/publish.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
name: Publish to npm
22

33
on:
4-
release:
5-
types: [published]
4+
# Triggers the workflow on push or pull request events but only for the main branch
5+
push:
6+
branches:
7+
- release
8+
69
workflow_dispatch:
710
inputs:
811
dry-run:
@@ -20,7 +23,7 @@ jobs:
2023
id-token: write
2124
steps:
2225
- name: Checkout
23-
uses: actions/checkout@v4
26+
uses: actions/checkout@v5
2427

2528
- name: Setup Bun
2629
uses: oven-sh/setup-bun@v2
@@ -30,8 +33,7 @@ jobs:
3033
- name: Setup Node.js
3134
uses: actions/setup-node@v4
3235
with:
33-
node-version: '20'
34-
registry-url: 'https://registry.npmjs.org'
36+
node-version: '24'
3537

3638
- name: Install dependencies
3739
run: bun install
@@ -40,7 +42,7 @@ jobs:
4042
run: bun run cli:build
4143

4244
- name: Verify CLI
43-
run: node bin/buner.js --version
45+
run: node dist/buner.js --version
4446

4547
- name: Publish (dry run)
4648
if: ${{ github.event.inputs.dry-run == 'true' }}

.npmrc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
engine-strict=true
2-
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
1+
engine-strict=true

cli/buner.ts

Lines changed: 92 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,68 @@
11
/* eslint-disable no-console */
22

3+
import type { PackageManager } from './helpers/install.js';
4+
35
import path from 'path';
4-
import { execSync, spawn, SpawnOptions } from 'child_process';
6+
import { fileURLToPath } from 'url';
57

68
import { Command } from 'commander';
79
import chalk from 'chalk';
810
import fetch from 'node-fetch';
911
import prompts from 'prompts';
12+
import { build } from 'vite';
1013

1114
import packageJson from '../package.json';
15+
import { runStyles } from '../styles.js';
16+
import { runStates } from '../states.js';
17+
import { runServer } from '../server.js';
18+
import { runPrerender } from '../prerender.js';
19+
import { runIntegration } from '../integration.js';
1220

13-
import { createApp } from './create-app.js';
1421
import { validateNpmName } from './helpers/validate-pkg.js';
22+
import { createApp } from './create-app.js';
1523

1624
const { green, yellow, bold, cyan, red } = chalk;
1725
const packageName = 'buner';
1826

19-
const run = (cmd: string, args: string[] = [], options: SpawnOptions = {}) => {
20-
return new Promise<void>((resolve, reject) => {
21-
const child = spawn(cmd, args, {
22-
stdio: 'inherit',
23-
shell: true,
24-
cwd: process.cwd(),
25-
...options,
26-
});
27+
// Package's own directory — after compilation, buner.js lives in dist/
28+
// so __dirname equivalent is the dist/ folder itself
29+
const packageDir = path.dirname(fileURLToPath(import.meta.url));
2730

28-
child.on('close', (code) => {
29-
if (code !== 0) {
30-
reject(new Error(`Command "${cmd} ${args.join(' ')}" exited with code ${code}`));
31-
} else {
32-
resolve();
33-
}
34-
});
31+
/** Resolve the vite config path */
32+
const viteConfigPath = () => path.resolve(packageDir, '..', 'vite.config.ts');
3533

36-
child.on('error', reject);
37-
});
38-
};
34+
/** Run vite build with the given options */
35+
const viteBuild = async (opts: { outDir: string; ssr?: string; mode?: string; watch?: boolean }) => {
36+
// Set BUNER_MODE so xpack/paths.ts picks up the correct mode
37+
// (it can't read --mode from process.argv when using the build() API)
38+
const prevMode = process.env.BUNER_MODE;
39+
40+
if (opts.mode) {
41+
process.env.BUNER_MODE = opts.mode;
42+
}
3943

40-
const runSync = (cmd: string) => {
41-
execSync(cmd, { stdio: 'inherit', cwd: process.cwd() });
44+
const config: Record<string, unknown> = {
45+
configFile: viteConfigPath(),
46+
build: {
47+
outDir: opts.outDir,
48+
...(opts.ssr ? { ssr: opts.ssr } : {}),
49+
...(opts.watch ? { watch: {} } : {}),
50+
},
51+
};
52+
53+
if (opts.mode) {
54+
config.mode = opts.mode;
55+
}
56+
57+
try {
58+
await build(config);
59+
} finally {
60+
if (prevMode === undefined) {
61+
delete process.env.BUNER_MODE;
62+
} else {
63+
process.env.BUNER_MODE = prevMode;
64+
}
65+
}
4266
};
4367

4468
const onPromptState = (state: { value?: string; aborted?: boolean }) => {
@@ -83,6 +107,8 @@ program
83107
.argument('[project-directory]', 'the project name', '')
84108
.description('Scaffold a new frontend project')
85109
.action(async (projectPath: string) => {
110+
let packageManager: PackageManager = 'npm';
111+
86112
if (!projectPath) {
87113
const validation = validateNpmName('my-app');
88114

@@ -108,6 +134,24 @@ program
108134
}
109135
}
110136

137+
const packageManagerPrompt = await prompts({
138+
onState: onPromptState,
139+
type: 'select',
140+
name: 'packageManager',
141+
message: 'Which package manager do you want to use?',
142+
initial: 0,
143+
choices: [
144+
{ title: 'npm', value: 'npm' },
145+
{ title: 'pnpm', value: 'pnpm' },
146+
{ title: 'yarn', value: 'yarn' },
147+
{ title: 'bun', value: 'bun' },
148+
],
149+
});
150+
151+
if (packageManagerPrompt.packageManager) {
152+
packageManager = packageManagerPrompt.packageManager as PackageManager;
153+
}
154+
111155
if (!projectPath) {
112156
console.log(
113157
'\nPlease specify the project directory:\n' +
@@ -120,7 +164,7 @@ program
120164

121165
const resolvedProjectPath = path.resolve(projectPath);
122166

123-
await createApp({ appPath: resolvedProjectPath });
167+
await createApp({ appPath: resolvedProjectPath, packageManager });
124168
await notifyUpdate();
125169
});
126170

@@ -129,14 +173,11 @@ program
129173
.command('dev')
130174
.description('Start development mode with all watchers')
131175
.action(async () => {
132-
await run('npx', [
133-
'concurrently',
134-
'--kill-others',
135-
'"bun styles.ts --watch"',
136-
'"bun states.ts --watch"',
137-
'"cross-env scriptOnly=true npx vite build --mode development --watch"',
138-
'"bun server.ts --mode development"',
139-
]);
176+
runStyles({ watch: true });
177+
runStates({ watch: true });
178+
process.env.scriptOnly = 'true';
179+
viteBuild({ outDir: 'dist/static', mode: 'development', watch: true });
180+
runServer({ mode: 'development' });
140181
});
141182

142183
// buner serve
@@ -145,16 +186,16 @@ program
145186
.description('Start the SSR dev server')
146187
.option('--mode <mode>', 'server mode', 'development')
147188
.action(async (opts) => {
148-
await run('bun', ['server.ts', '--mode', opts.mode]);
189+
runServer({ mode: opts.mode });
149190
});
150191

151192
// buner build
152193
program
153194
.command('build')
154195
.description('Build the project (static + SSR)')
155196
.action(async () => {
156-
runSync('npx vite build --outDir dist/static');
157-
runSync('npx vite build --ssr src/entry-server.tsx --outDir dist/server');
197+
await viteBuild({ outDir: 'dist/static' });
198+
await viteBuild({ outDir: 'dist/server', ssr: 'src/entry-server.tsx' });
158199
});
159200

160201
// buner generate
@@ -163,40 +204,35 @@ program
163204
.description('Full static site generation (states + styles + build + prerender)')
164205
.option('--mode <mode>', 'build mode', 'production')
165206
.action(async (opts) => {
166-
runSync('bun states.ts');
167-
runSync('bun styles.ts');
168-
if (opts.mode === 'production') {
169-
runSync('npx vite build --outDir dist/static');
170-
runSync('npx vite build --ssr src/entry-server.tsx --outDir dist/server');
171-
} else {
172-
runSync(`npx vite build --outDir dist/static --mode ${opts.mode}`);
173-
runSync(`npx vite build --ssr src/entry-server.tsx --outDir dist/server --mode ${opts.mode}`);
174-
}
175-
runSync(`bun prerender.ts --add-hash --mode ${opts.mode}`);
207+
runStates();
208+
runStyles();
209+
await viteBuild({ outDir: 'dist/static', mode: opts.mode !== 'production' ? opts.mode : undefined });
210+
await viteBuild({ outDir: 'dist/server', ssr: 'src/entry-server.tsx', mode: opts.mode !== 'production' ? opts.mode : undefined });
211+
await runPrerender({ addHash: true, mode: opts.mode });
176212
});
177213

178214
// buner eshn
179215
program
180216
.command('eshn')
181217
.description('Generate with --mode eshn')
182218
.action(async () => {
183-
runSync('bun states.ts');
184-
runSync('bun styles.ts');
185-
runSync('npx vite build --outDir dist/static --mode eshn');
186-
runSync('npx vite build --ssr src/entry-server.tsx --outDir dist/server --mode eshn');
187-
runSync('bun prerender.ts --add-hash --mode eshn');
219+
runStates();
220+
runStyles();
221+
await viteBuild({ outDir: 'dist/static', mode: 'eshn' });
222+
await viteBuild({ outDir: 'dist/server', ssr: 'src/entry-server.tsx', mode: 'eshn' });
223+
await runPrerender({ addHash: true, mode: 'eshn' });
188224
});
189225

190226
// buner inte
191227
program
192228
.command('inte')
193229
.description('Build and integrate with backend (styles + build + prerender + integration)')
194230
.action(async () => {
195-
runSync('bun styles.ts');
196-
runSync('npx vite build --outDir dist/static');
197-
runSync('npx vite build --ssr src/entry-server.tsx --outDir dist/server');
198-
runSync('bun prerender.ts');
199-
runSync('bun integration.ts');
231+
runStyles();
232+
await viteBuild({ outDir: 'dist/static' });
233+
await viteBuild({ outDir: 'dist/server', ssr: 'src/entry-server.tsx' });
234+
await runPrerender();
235+
runIntegration();
200236
});
201237

202238
// buner styles
@@ -205,10 +241,7 @@ program
205241
.description('Compile SCSS')
206242
.option('--watch', 'Watch for changes')
207243
.action(async (opts) => {
208-
const args = ['styles.ts'];
209-
210-
if (opts.watch) args.push('--watch');
211-
await run('bun', args);
244+
runStyles({ watch: opts.watch });
212245
});
213246

214247
// buner prerender
@@ -218,11 +251,7 @@ program
218251
.option('--add-hash', 'Add content hashes to asset URLs')
219252
.option('--mode <mode>', 'build mode', 'production')
220253
.action(async (opts) => {
221-
const args = ['prerender.ts'];
222-
223-
if (opts.addHash) args.push('--add-hash');
224-
args.push('--mode', opts.mode);
225-
await run('bun', args);
254+
await runPrerender({ addHash: opts.addHash, mode: opts.mode });
226255
});
227256

228257
program.parseAsync(process.argv).catch(async (error) => {

cli/create-app.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/* eslint-disable no-console */
2+
import type { PackageManager } from './helpers/install.js';
3+
24
import path from 'path';
35

46
import chalk from 'chalk';
@@ -11,12 +13,13 @@ import { installTemplate } from './install-template.js';
1113

1214
interface Props {
1315
appPath: string;
16+
packageManager: PackageManager;
1417
}
1518

1619
const { green } = chalk;
1720

1821
const createApp = async (model: Props) => {
19-
const { appPath } = model;
22+
const { appPath, packageManager } = model;
2023
const root = path.resolve(appPath);
2124

2225
if (!(await isWriteable(path.dirname(root)))) {
@@ -45,6 +48,7 @@ const createApp = async (model: Props) => {
4548
await installTemplate({
4649
appName,
4750
root,
51+
packageManager,
4852
});
4953

5054
console.log('\nInitializing a git repository.');

cli/helpers/copy.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,26 @@ interface CopyOption {
99

1010
const excludeFiles = [
1111
'.git',
12+
'.github',
13+
'.npmrc',
1214
'bin',
15+
'cli',
16+
'xpack',
1317
'.vscode',
1418
'.build',
19+
'node_modules',
20+
'server.ts',
21+
'prerender.ts',
22+
'integration.ts',
23+
'styles.ts',
24+
'scripts.ts',
25+
'states.ts',
26+
'migrate-scss.ts',
27+
'vite.config.ts',
28+
'vite.cli.config.ts',
29+
'src',
1530
'public/samples',
1631
'public/assets/vendors',
17-
'src/_api/!(_base.ts)',
18-
'src/_data',
19-
'src/assets/scripts/!(color-mode|main|mock-api|pl-states|root|theme-critical).entry.ts',
20-
'src/atoms',
21-
'src/mocks/avatar',
22-
'src/mocks/user',
23-
'src/molecules',
24-
'src/organisms/!(root|header|footer)/*',
25-
'src/pages/!(Root|Home).tsx',
26-
'src/templates/!(root|home)/*',
27-
'!cli',
28-
'!vite.cli.config.ts',
2932
];
3033

3134
const copy = async (src: string | string[], dest: string, { cwd }: CopyOption) => {
@@ -39,10 +42,6 @@ const copy = async (src: string | string[], dest: string, { cwd }: CopyOption) =
3942

4043
const destRelativeToCwd = path.resolve(dest);
4144

42-
await fs.mkdir(path.join(destRelativeToCwd, 'src/atoms'), { recursive: true });
43-
await fs.mkdir(path.join(destRelativeToCwd, 'src/molecules'), { recursive: true });
44-
await fs.mkdir(path.join(destRelativeToCwd, 'src/mocks/example'), { recursive: true });
45-
4645
return Promise.all(
4746
sourceFiles.map(async (p) => {
4847
const dirname = path.dirname(p);

0 commit comments

Comments
 (0)