create-bcp-app 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 chidchanun
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # create-bcp-app
2
+
3
+ Create a new BCP Framework project.
4
+
5
+ ```bash
6
+ npx create-bcp-app my-app
7
+ cd my-app
8
+ npm run dev
9
+ ```
10
+
11
+ ## Options
12
+
13
+ ```text
14
+ --no-install Create files without running npm install
15
+ --bcp <specifier> Override dependencies.bcp
16
+ -h, --help Show help
17
+ ```
18
+
19
+ The `--bcp` option is primarily useful for prerelease and local package testing, for example:
20
+
21
+ ```bash
22
+ npx create-bcp-app my-app --bcp file:../bcp-0.1.0.tgz
23
+ ```
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from "node:path";
4
+
5
+ import {
6
+ createBcpApp,
7
+ } from "../src/index.mjs";
8
+
9
+ const args =
10
+ process.argv.slice(2);
11
+
12
+ if (
13
+ args.includes("--help") ||
14
+ args.includes("-h")
15
+ ) {
16
+ printHelp();
17
+ process.exit(0);
18
+ }
19
+
20
+ let projectDirectory =
21
+ null;
22
+ let install =
23
+ true;
24
+ let bcpPackage =
25
+ undefined;
26
+
27
+ for (
28
+ let index = 0;
29
+ index < args.length;
30
+ index++
31
+ ) {
32
+ const value =
33
+ args[index];
34
+
35
+ if (
36
+ value === "--no-install"
37
+ ) {
38
+ install =
39
+ false;
40
+ continue;
41
+ }
42
+
43
+ if (
44
+ value === "--bcp"
45
+ ) {
46
+ const nextValue =
47
+ args[
48
+ index + 1
49
+ ];
50
+
51
+ if (!nextValue) {
52
+ fail(
53
+ "--bcp requires a package specifier."
54
+ );
55
+ }
56
+
57
+ bcpPackage =
58
+ nextValue;
59
+ index++;
60
+ continue;
61
+ }
62
+
63
+ if (
64
+ value.startsWith("-")
65
+ ) {
66
+ fail(
67
+ `Unknown option: ${value}`
68
+ );
69
+ }
70
+
71
+ if (
72
+ projectDirectory !==
73
+ null
74
+ ) {
75
+ fail(
76
+ "Only one project directory may be provided."
77
+ );
78
+ }
79
+
80
+ projectDirectory =
81
+ value;
82
+ }
83
+
84
+ if (!projectDirectory) {
85
+ printHelp();
86
+ process.exitCode =
87
+ 1;
88
+ } else {
89
+ try {
90
+ const result =
91
+ await createBcpApp({
92
+ projectDirectory,
93
+ install,
94
+ bcpPackage,
95
+ packageManager:
96
+ "npm",
97
+ });
98
+
99
+ const relative =
100
+ path.relative(
101
+ process.cwd(),
102
+ result.targetDirectory
103
+ ) || ".";
104
+
105
+ console.log("");
106
+ console.log(
107
+ `Created BCP app: ${result.projectName}`
108
+ );
109
+ console.log(
110
+ `Location: ${result.targetDirectory}`
111
+ );
112
+ console.log("");
113
+ console.log(
114
+ "Next steps:"
115
+ );
116
+ console.log(
117
+ ` cd ${quoteIfNeeded(relative)}`
118
+ );
119
+
120
+ if (!install) {
121
+ console.log(
122
+ " npm install"
123
+ );
124
+ }
125
+
126
+ console.log(
127
+ " npm run dev"
128
+ );
129
+ console.log("");
130
+ } catch (error) {
131
+ fail(
132
+ error instanceof Error
133
+ ? error.message
134
+ : String(error)
135
+ );
136
+ }
137
+ }
138
+
139
+ function printHelp() {
140
+ console.log(`
141
+ create-bcp-app
142
+
143
+ Usage:
144
+ npx create-bcp-app <project-name> [options]
145
+
146
+ Options:
147
+ --no-install Create files without running npm install
148
+ --bcp <specifier> Override the package specifier stored under dependencies.bcp
149
+ -h, --help Show this help message
150
+
151
+ Examples:
152
+ npx create-bcp-app my-app
153
+ npx create-bcp-app my-app --no-install
154
+ npx create-bcp-app my-app --bcp file:../bcp-0.1.0.tgz
155
+ `);
156
+ }
157
+
158
+ function quoteIfNeeded(
159
+ value
160
+ ) {
161
+ return /\s/.test(
162
+ value
163
+ )
164
+ ? JSON.stringify(
165
+ value
166
+ )
167
+ : value;
168
+ }
169
+
170
+ function fail(
171
+ message
172
+ ) {
173
+ console.error(
174
+ `create-bcp-app: ${message}`
175
+ );
176
+ process.exit(1);
177
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "create-bcp-app",
3
+ "version": "0.1.1",
4
+ "description": "Create a new BCP Framework application.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "create-bcp-app": "bin/create-bcp-app.mjs"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "src",
13
+ "template",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "engines": {
18
+ "node": ">=24.11.0"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/chidchanun/bcp-freamwork.git",
23
+ "directory": "create-bcp-app"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "bcpFrameworkPackage": "@chidchanun/bcp"
29
+ }
package/src/index.mjs ADDED
@@ -0,0 +1,405 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ spawn,
5
+ } from "node:child_process";
6
+ import {
7
+ fileURLToPath,
8
+ } from "node:url";
9
+
10
+ const packageRoot =
11
+ path.resolve(
12
+ path.dirname(
13
+ fileURLToPath(
14
+ import.meta.url
15
+ )
16
+ ),
17
+ ".."
18
+ );
19
+
20
+ export async function createBcpApp(
21
+ options
22
+ ) {
23
+ const targetDirectory =
24
+ path.resolve(
25
+ options.projectDirectory
26
+ );
27
+
28
+ const projectName =
29
+ validateProjectName(
30
+ path.basename(
31
+ targetDirectory
32
+ )
33
+ );
34
+
35
+ ensureTargetDirectory(
36
+ targetDirectory
37
+ );
38
+
39
+ const templateDirectory =
40
+ path.join(
41
+ packageRoot,
42
+ "template"
43
+ );
44
+
45
+ copyTemplate(
46
+ templateDirectory,
47
+ targetDirectory
48
+ );
49
+
50
+ const packageMetadata =
51
+ readPackageMetadata();
52
+
53
+ const packageSpecifier =
54
+ options.bcpPackage ??
55
+ createDefaultBcpPackageSpecifier(
56
+ packageMetadata.frameworkPackage,
57
+ packageMetadata.version
58
+ );
59
+
60
+ const packageJson = {
61
+ name:
62
+ projectName,
63
+ version:
64
+ "0.1.0",
65
+ private:
66
+ true,
67
+ type:
68
+ "module",
69
+ scripts: {
70
+ dev:
71
+ "bcp dev",
72
+ build:
73
+ "bcp build",
74
+ start:
75
+ "bcp start",
76
+ routes:
77
+ "bcp routes",
78
+ typecheck:
79
+ "tsc --noEmit",
80
+ },
81
+ dependencies: {
82
+ bcp:
83
+ packageSpecifier,
84
+ react:
85
+ "^19.2.8",
86
+ "react-dom":
87
+ "^19.2.8",
88
+ },
89
+ devDependencies: {
90
+ "@types/node":
91
+ "^24.13.3",
92
+ "@types/react":
93
+ "^19.2.18",
94
+ "@types/react-dom":
95
+ "^19.2.4",
96
+ typescript:
97
+ "^5.9.3",
98
+ },
99
+ engines: {
100
+ node:
101
+ ">=24.11.0",
102
+ },
103
+ };
104
+
105
+ fs.writeFileSync(
106
+ path.join(
107
+ targetDirectory,
108
+ "package.json"
109
+ ),
110
+ `${JSON.stringify(
111
+ packageJson,
112
+ null,
113
+ 2
114
+ )}\n`,
115
+ "utf8"
116
+ );
117
+
118
+ if (
119
+ options.install !== false
120
+ ) {
121
+ await runInstall(
122
+ targetDirectory,
123
+ options.packageManager ??
124
+ "npm"
125
+ );
126
+ }
127
+
128
+ return {
129
+ targetDirectory,
130
+ projectName,
131
+ packageSpecifier,
132
+ };
133
+ }
134
+
135
+ export function validateProjectName(
136
+ value
137
+ ) {
138
+ const name =
139
+ String(
140
+ value ?? ""
141
+ ).trim();
142
+
143
+ if (
144
+ !name ||
145
+ name === "." ||
146
+ name === ".."
147
+ ) {
148
+ throw new Error(
149
+ "Project name is required."
150
+ );
151
+ }
152
+
153
+ if (
154
+ name !==
155
+ name.toLowerCase()
156
+ ) {
157
+ throw new Error(
158
+ "Project name must use lowercase characters."
159
+ );
160
+ }
161
+
162
+ if (
163
+ !/^[a-z0-9][a-z0-9._-]*$/.test(
164
+ name
165
+ )
166
+ ) {
167
+ throw new Error(
168
+ "Project name may contain lowercase letters, numbers, dots, underscores and hyphens."
169
+ );
170
+ }
171
+
172
+ if (
173
+ name === "node_modules" ||
174
+ name === "favicon.ico"
175
+ ) {
176
+ throw new Error(
177
+ `Project name "${name}" is reserved.`
178
+ );
179
+ }
180
+
181
+ return name;
182
+ }
183
+
184
+ function ensureTargetDirectory(
185
+ targetDirectory
186
+ ) {
187
+ if (
188
+ fs.existsSync(
189
+ targetDirectory
190
+ )
191
+ ) {
192
+ const entries =
193
+ fs.readdirSync(
194
+ targetDirectory
195
+ );
196
+
197
+ if (
198
+ entries.length > 0
199
+ ) {
200
+ throw new Error(
201
+ `Target directory is not empty: ${targetDirectory}`
202
+ );
203
+ }
204
+
205
+ return;
206
+ }
207
+
208
+ fs.mkdirSync(
209
+ targetDirectory,
210
+ {
211
+ recursive: true,
212
+ }
213
+ );
214
+ }
215
+
216
+ function copyTemplate(
217
+ sourceDirectory,
218
+ targetDirectory
219
+ ) {
220
+ fs.cpSync(
221
+ sourceDirectory,
222
+ targetDirectory,
223
+ {
224
+ recursive: true,
225
+ }
226
+ );
227
+
228
+ const gitignoreTemplate =
229
+ path.join(
230
+ targetDirectory,
231
+ "gitignore"
232
+ );
233
+
234
+ if (
235
+ fs.existsSync(
236
+ gitignoreTemplate
237
+ )
238
+ ) {
239
+ fs.renameSync(
240
+ gitignoreTemplate,
241
+ path.join(
242
+ targetDirectory,
243
+ ".gitignore"
244
+ )
245
+ );
246
+ }
247
+ }
248
+
249
+ function readPackageMetadata() {
250
+ const packageJson =
251
+ JSON.parse(
252
+ fs.readFileSync(
253
+ path.join(
254
+ packageRoot,
255
+ "package.json"
256
+ ),
257
+ "utf8"
258
+ )
259
+ );
260
+
261
+ if (
262
+ typeof packageJson.version !==
263
+ "string" ||
264
+ packageJson.version.trim() ===
265
+ ""
266
+ ) {
267
+ throw new Error(
268
+ "create-bcp-app package version is missing."
269
+ );
270
+ }
271
+
272
+ const frameworkPackage =
273
+ typeof packageJson.bcpFrameworkPackage ===
274
+ "string" &&
275
+ packageJson.bcpFrameworkPackage.trim() !==
276
+ ""
277
+ ? packageJson.bcpFrameworkPackage.trim()
278
+ : "bcp";
279
+
280
+ return {
281
+ version:
282
+ packageJson.version,
283
+ frameworkPackage,
284
+ };
285
+ }
286
+
287
+ function createDefaultBcpPackageSpecifier(
288
+ frameworkPackage,
289
+ version
290
+ ) {
291
+ if (
292
+ frameworkPackage === "bcp"
293
+ ) {
294
+ return `^${version}`;
295
+ }
296
+
297
+ return `npm:${frameworkPackage}@^${version}`;
298
+ }
299
+
300
+ function runInstall(
301
+ targetDirectory,
302
+ packageManager
303
+ ) {
304
+ if (
305
+ packageManager !== "npm"
306
+ ) {
307
+ throw new Error(
308
+ `Unsupported package manager: ${packageManager}. Only npm is supported in this release.`
309
+ );
310
+ }
311
+
312
+ const invocation =
313
+ resolveNpmInvocation([
314
+ "install",
315
+ ]);
316
+
317
+ return new Promise(
318
+ (
319
+ resolve,
320
+ reject
321
+ ) => {
322
+ const child =
323
+ spawn(
324
+ invocation.command,
325
+ invocation.args,
326
+ {
327
+ cwd:
328
+ targetDirectory,
329
+ stdio:
330
+ "inherit",
331
+ env:
332
+ process.env,
333
+ }
334
+ );
335
+
336
+ child.once(
337
+ "error",
338
+ reject
339
+ );
340
+ child.once(
341
+ "exit",
342
+ (
343
+ code,
344
+ signal
345
+ ) => {
346
+ if (
347
+ code === 0
348
+ ) {
349
+ resolve();
350
+ return;
351
+ }
352
+
353
+ reject(
354
+ new Error(
355
+ signal
356
+ ? `npm install stopped by ${signal}.`
357
+ : `npm install exited with code ${code ?? 1}.`
358
+ )
359
+ );
360
+ }
361
+ );
362
+ }
363
+ );
364
+ }
365
+
366
+ function resolveNpmInvocation(
367
+ args
368
+ ) {
369
+ const npmExecPath =
370
+ process.env.npm_execpath;
371
+
372
+ if (npmExecPath) {
373
+ return {
374
+ command:
375
+ process.execPath,
376
+ args: [
377
+ npmExecPath,
378
+ ...args,
379
+ ],
380
+ };
381
+ }
382
+
383
+ if (
384
+ process.platform === "win32"
385
+ ) {
386
+ return {
387
+ command:
388
+ process.env.ComSpec ??
389
+ "cmd.exe",
390
+ args: [
391
+ "/d",
392
+ "/s",
393
+ "/c",
394
+ "npm",
395
+ ...args,
396
+ ],
397
+ };
398
+ }
399
+
400
+ return {
401
+ command:
402
+ "npm",
403
+ args,
404
+ };
405
+ }
@@ -0,0 +1,2 @@
1
+ BCP_PUBLIC_APP_NAME=BCP App
2
+ BCP_SERVER_LABEL=local
@@ -0,0 +1,23 @@
1
+ # BCP App
2
+
3
+ This project was created with `create-bcp-app`.
4
+
5
+ ## Development
6
+
7
+ ```bash
8
+ npm run dev
9
+ ```
10
+
11
+ Open `http://localhost:3000`.
12
+
13
+ ## Commands
14
+
15
+ ```bash
16
+ npm run dev
17
+ npm run routes
18
+ npm run typecheck
19
+ npm run build
20
+ npm start
21
+ ```
22
+
23
+ The production build is written to `.bcp-framework/build` and runs as a standalone Node.js server.
@@ -0,0 +1,6 @@
1
+ export function GET() {
2
+ return Response.json({
3
+ message:
4
+ "Hello from BCP Framework",
5
+ });
6
+ }
@@ -0,0 +1,47 @@
1
+ import type {
2
+ ReactNode,
3
+ } from "react";
4
+
5
+ import {
6
+ Link,
7
+ type Metadata,
8
+ } from "bcp";
9
+
10
+ export const metadata:
11
+ Metadata = {
12
+ title:
13
+ "BCP App",
14
+ description:
15
+ "Created with BCP Framework.",
16
+ };
17
+
18
+ export default function RootLayout(
19
+ {
20
+ children,
21
+ }: {
22
+ children:
23
+ ReactNode;
24
+ }
25
+ ) {
26
+ return (
27
+ <div>
28
+ <header>
29
+ <strong>
30
+ BCP App
31
+ </strong>
32
+
33
+ {" · "}
34
+
35
+ <Link href="/">
36
+ Home
37
+ </Link>
38
+ </header>
39
+
40
+ <hr />
41
+
42
+ <main>
43
+ {children}
44
+ </main>
45
+ </div>
46
+ );
47
+ }
@@ -0,0 +1,37 @@
1
+ import type {
2
+ Metadata,
3
+ } from "bcp";
4
+
5
+ export const metadata:
6
+ Metadata = {
7
+ title:
8
+ "Home | BCP App",
9
+ };
10
+
11
+ export default function HomePage() {
12
+ return (
13
+ <section>
14
+ <h1>
15
+ Welcome to BCP Framework
16
+ </h1>
17
+
18
+ <p>
19
+ Edit
20
+ {" "}
21
+ <code>
22
+ app/page.tsx
23
+ </code>
24
+ {" "}
25
+ to get started.
26
+ </p>
27
+
28
+ <p>
29
+ API example:
30
+ {" "}
31
+ <a href="/api/hello">
32
+ /api/hello
33
+ </a>
34
+ </p>
35
+ </section>
36
+ );
37
+ }
@@ -0,0 +1,37 @@
1
+ import {
2
+ defineConfig,
3
+ } from "bcp/config";
4
+
5
+ export default defineConfig({
6
+ server: {
7
+ port: 3000,
8
+ hostname:
9
+ "localhost",
10
+ bodyLimit:
11
+ 1024 * 1024,
12
+ },
13
+
14
+ compression: true,
15
+
16
+ build: {
17
+ minify: true,
18
+ sourceMaps: false,
19
+ },
20
+
21
+ cache: {
22
+ response: true,
23
+ },
24
+
25
+ security: {
26
+ poweredByHeader:
27
+ false,
28
+ contentSecurityPolicy:
29
+ false,
30
+ frameOptions:
31
+ "SAMEORIGIN",
32
+ referrerPolicy:
33
+ "strict-origin-when-cross-origin",
34
+ permissionsPolicy:
35
+ "camera=(), microphone=(), geolocation=()",
36
+ },
37
+ });
@@ -0,0 +1,9 @@
1
+ node_modules/
2
+ .bcp-framework/
3
+ dist/
4
+ .env
5
+ .env.local
6
+ .env.development.local
7
+ .env.production.local
8
+ npm-debug.log*
9
+ .DS_Store
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "skipLibCheck": true,
12
+ "types": [
13
+ "node",
14
+ "react",
15
+ "react-dom"
16
+ ]
17
+ },
18
+ "include": [
19
+ "app/**/*.ts",
20
+ "app/**/*.tsx",
21
+ "bcp.config.ts"
22
+ ],
23
+ "exclude": [
24
+ "node_modules",
25
+ ".bcp-framework"
26
+ ]
27
+ }