@xfilecom/create-xframe 0.1.0
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/bin/create-xframe.js +163 -0
- package/package.json +18 -0
- package/template/nest-cli.json +8 -0
- package/template/package.json +24 -0
- package/template/src/app.controller.ts +9 -0
- package/template/src/app.module.ts +14 -0
- package/template/src/main.ts +10 -0
- package/template/tsconfig.build.json +5 -0
- package/template/tsconfig.json +18 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-xframe — copy template, substitute package name and backend-core spec.
|
|
4
|
+
* Usage: npx create-xframe <project-dir> [--backend-core <npm-spec>]
|
|
5
|
+
* Example: npx create-xframe my-api --backend-core file:../packages/backend-core
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const TEXT_EXT = new Set([
|
|
12
|
+
'.json',
|
|
13
|
+
'.ts',
|
|
14
|
+
'.md',
|
|
15
|
+
'.yml',
|
|
16
|
+
'.yaml',
|
|
17
|
+
'.gitignore',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
function usage() {
|
|
21
|
+
console.log(`Usage: create-xframe <project-directory> [options]
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--backend-core <spec> @xfilecom/backend-core dependency (default: ^1.0.0)
|
|
25
|
+
e.g. file:../packages/backend-core (app next to packages/)
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
npx create-xframe my-api
|
|
29
|
+
npm create xframe@latest -- my-api
|
|
30
|
+
npx create-xframe my-api --backend-core file:../packages/backend-core`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseArgs(argv) {
|
|
34
|
+
const rest = argv.slice(2);
|
|
35
|
+
let projectDir = null;
|
|
36
|
+
let backendCore = '^1.0.0';
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
39
|
+
const a = rest[i];
|
|
40
|
+
if (a === '--help' || a === '-h') {
|
|
41
|
+
usage();
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
if (a === '--backend-core') {
|
|
45
|
+
const v = rest[i + 1];
|
|
46
|
+
if (!v || v.startsWith('-')) {
|
|
47
|
+
console.error('create-xframe: --backend-core requires a value');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
backendCore = v;
|
|
51
|
+
i += 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (a.startsWith('-')) {
|
|
55
|
+
console.error(`create-xframe: unknown option ${a}`);
|
|
56
|
+
usage();
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
if (projectDir !== null) {
|
|
60
|
+
console.error('create-xframe: only one project directory allowed');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
projectDir = a;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { projectDir, backendCore };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isValidNpmName(name) {
|
|
70
|
+
if (!name || typeof name !== 'string') return false;
|
|
71
|
+
if (name.length > 214) return false;
|
|
72
|
+
// unscoped: lowercase, hyphens, digits; no leading/trailing hyphen
|
|
73
|
+
if (name.startsWith('@')) return false;
|
|
74
|
+
return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(name);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function walkFiles(dir, base = dir, out = []) {
|
|
78
|
+
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
79
|
+
const full = path.join(dir, ent.name);
|
|
80
|
+
if (ent.isDirectory()) {
|
|
81
|
+
walkFiles(full, base, out);
|
|
82
|
+
} else {
|
|
83
|
+
out.push(full);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function substitute(content, vars) {
|
|
90
|
+
let s = content;
|
|
91
|
+
for (const [key, val] of Object.entries(vars)) {
|
|
92
|
+
const token = `__${key}__`;
|
|
93
|
+
if (s.includes(token)) {
|
|
94
|
+
s = s.split(token).join(val);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return s;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function processFile(filePath, vars) {
|
|
101
|
+
const ext = path.extname(filePath);
|
|
102
|
+
if (!TEXT_EXT.has(ext)) return;
|
|
103
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
104
|
+
const next = substitute(raw, vars);
|
|
105
|
+
if (next !== raw) {
|
|
106
|
+
fs.writeFileSync(filePath, next, 'utf8');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function main() {
|
|
111
|
+
const { projectDir, backendCore } = parseArgs(process.argv);
|
|
112
|
+
|
|
113
|
+
if (!projectDir) {
|
|
114
|
+
console.error('create-xframe: missing <project-directory>\n');
|
|
115
|
+
usage();
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const packageName = path.basename(path.resolve(process.cwd(), projectDir));
|
|
120
|
+
if (!isValidNpmName(packageName)) {
|
|
121
|
+
console.error(
|
|
122
|
+
`create-xframe: "${packageName}" is not a valid unscoped npm package name (use lowercase, digits, hyphens).`,
|
|
123
|
+
);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const targetRoot = path.resolve(process.cwd(), projectDir);
|
|
128
|
+
if (fs.existsSync(targetRoot)) {
|
|
129
|
+
const entries = fs.readdirSync(targetRoot);
|
|
130
|
+
if (entries.length > 0) {
|
|
131
|
+
console.error(`create-xframe: directory is not empty: ${targetRoot}`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
fs.mkdirSync(targetRoot, { recursive: true });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const templateRoot = path.join(__dirname, '..', 'template');
|
|
139
|
+
if (!fs.existsSync(templateRoot)) {
|
|
140
|
+
console.error(`create-xframe: template missing at ${templateRoot}`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
fs.cpSync(templateRoot, targetRoot, { recursive: true });
|
|
145
|
+
|
|
146
|
+
const vars = {
|
|
147
|
+
PACKAGE_NAME: packageName,
|
|
148
|
+
SERVICE_LABEL: packageName,
|
|
149
|
+
BACKEND_CORE_SPEC: backendCore,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
for (const file of walkFiles(targetRoot)) {
|
|
153
|
+
processFile(file, vars);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log(`Created ${packageName} at ${targetRoot}
|
|
157
|
+
Next:
|
|
158
|
+
cd ${projectDir}
|
|
159
|
+
npm install
|
|
160
|
+
npm run start:dev`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xfilecom/create-xframe",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold a Nest app wired to @xfilecom/backend-core (xframe)",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-xframe": "bin/create-xframe.js"
|
|
8
|
+
},
|
|
9
|
+
"files": ["bin", "template"],
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"registry": "https://registry.npmjs.org/",
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"keywords": ["xframe", "nestjs", "xfilecom", "scaffold", "create-xframe"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PACKAGE_NAME__",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "nest build",
|
|
7
|
+
"start": "nest start",
|
|
8
|
+
"start:dev": "nest start --watch"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@nestjs/common": "^10.0.0",
|
|
12
|
+
"@nestjs/core": "^10.0.0",
|
|
13
|
+
"@nestjs/platform-express": "^10.0.0",
|
|
14
|
+
"@xfilecom/backend-core": "__BACKEND_CORE_SPEC__",
|
|
15
|
+
"reflect-metadata": "^0.2.0",
|
|
16
|
+
"rxjs": "^7.8.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@nestjs/cli": "^10.0.0",
|
|
20
|
+
"@types/express": "^4.17.21",
|
|
21
|
+
"@types/node": "^20.0.0",
|
|
22
|
+
"typescript": "^5.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { CoreModule } from '@xfilecom/backend-core';
|
|
3
|
+
import { AppController } from './app.controller';
|
|
4
|
+
|
|
5
|
+
@Module({
|
|
6
|
+
imports: [
|
|
7
|
+
CoreModule.forHttpApi({
|
|
8
|
+
database: { auto: false },
|
|
9
|
+
guards: { jwt: false },
|
|
10
|
+
}),
|
|
11
|
+
],
|
|
12
|
+
controllers: [AppController],
|
|
13
|
+
})
|
|
14
|
+
export class AppModule {}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { NestFactory } from '@nestjs/core';
|
|
3
|
+
import { AppModule } from './app.module';
|
|
4
|
+
|
|
5
|
+
async function bootstrap() {
|
|
6
|
+
const app = await NestFactory.create(AppModule);
|
|
7
|
+
const port = Number(process.env.PORT) || 3000;
|
|
8
|
+
await app.listen(port);
|
|
9
|
+
}
|
|
10
|
+
bootstrap();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "commonjs",
|
|
4
|
+
"declaration": true,
|
|
5
|
+
"removeComments": true,
|
|
6
|
+
"emitDecoratorMetadata": true,
|
|
7
|
+
"experimentalDecorators": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"target": "ES2021",
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"baseUrl": "./",
|
|
13
|
+
"incremental": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"strictNullChecks": false,
|
|
16
|
+
"noImplicitAny": false
|
|
17
|
+
}
|
|
18
|
+
}
|