create-projx 1.1.0 → 1.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/dist/index.js +83 -17
- package/package.json +1 -1
- package/src/templates/ci.yml.ejs +72 -0
package/dist/index.js
CHANGED
|
@@ -122,7 +122,7 @@ async function copyComponent(repoDir, component, dest) {
|
|
|
122
122
|
async function copyStaticFiles(repoDir, dest) {
|
|
123
123
|
const manifest = [];
|
|
124
124
|
const tpl = repoDir;
|
|
125
|
-
const statics = [".editorconfig"
|
|
125
|
+
const statics = [".editorconfig"];
|
|
126
126
|
for (const file of statics) {
|
|
127
127
|
const src = join(tpl, file);
|
|
128
128
|
if (existsSync(src)) {
|
|
@@ -135,10 +135,11 @@ async function copyStaticFiles(repoDir, dest) {
|
|
|
135
135
|
await cp(gitignore, join(dest, ".gitignore"));
|
|
136
136
|
manifest.push(".gitignore");
|
|
137
137
|
}
|
|
138
|
-
const
|
|
139
|
-
if (existsSync(
|
|
140
|
-
await
|
|
141
|
-
|
|
138
|
+
const extensionsJson = join(tpl, ".vscode/extensions.json");
|
|
139
|
+
if (existsSync(extensionsJson)) {
|
|
140
|
+
await mkdir(join(dest, ".vscode"), { recursive: true });
|
|
141
|
+
await cp(extensionsJson, join(dest, ".vscode/extensions.json"));
|
|
142
|
+
manifest.push(".vscode/extensions.json");
|
|
142
143
|
}
|
|
143
144
|
const scripts = join(tpl, "scripts");
|
|
144
145
|
if (existsSync(scripts)) {
|
|
@@ -185,9 +186,24 @@ async function readFileOrNull(path) {
|
|
|
185
186
|
}
|
|
186
187
|
}
|
|
187
188
|
async function writeComponentMarker(dir, component) {
|
|
189
|
+
const markerPath = join(dir, COMPONENT_MARKER);
|
|
190
|
+
let components = [component];
|
|
191
|
+
const existing = await readFileOrNull(markerPath);
|
|
192
|
+
if (existing) {
|
|
193
|
+
try {
|
|
194
|
+
const data = JSON.parse(existing);
|
|
195
|
+
const prev = data.components ?? (data.component ? [data.component] : []);
|
|
196
|
+
if (!prev.includes(component)) {
|
|
197
|
+
components = [...prev, component];
|
|
198
|
+
} else {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
188
204
|
await writeFile(
|
|
189
|
-
|
|
190
|
-
JSON.stringify({
|
|
205
|
+
markerPath,
|
|
206
|
+
JSON.stringify({ components }, null, 2) + "\n"
|
|
191
207
|
);
|
|
192
208
|
}
|
|
193
209
|
async function discoverComponentPaths(cwd, components) {
|
|
@@ -203,8 +219,11 @@ async function discoverComponentPaths(cwd, components) {
|
|
|
203
219
|
if (existsSync(marker)) {
|
|
204
220
|
try {
|
|
205
221
|
const data = JSON.parse(await readFile(marker, "utf-8"));
|
|
206
|
-
|
|
207
|
-
|
|
222
|
+
const markerComponents = data.components ?? (data.component ? [data.component] : []);
|
|
223
|
+
for (const mc of markerComponents) {
|
|
224
|
+
if (components.includes(mc)) {
|
|
225
|
+
paths[mc] = entry.name;
|
|
226
|
+
}
|
|
208
227
|
}
|
|
209
228
|
} catch {
|
|
210
229
|
}
|
|
@@ -331,6 +350,36 @@ async function generateCiYml(vars) {
|
|
|
331
350
|
async function generateReadme(vars) {
|
|
332
351
|
return renderShared("README.md.ejs", vars);
|
|
333
352
|
}
|
|
353
|
+
function generateVscodeSettings(vars) {
|
|
354
|
+
const settings = {};
|
|
355
|
+
if (vars.components.includes("fastapi")) {
|
|
356
|
+
settings["[python]"] = {
|
|
357
|
+
"editor.defaultFormatter": "charliermarsh.ruff",
|
|
358
|
+
"editor.codeActionsOnSave": { "source.fixAll.ruff": "explicit" }
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
settings["[typescript]"] = { "editor.defaultFormatter": "esbenp.prettier-vscode" };
|
|
362
|
+
settings["[typescriptreact]"] = { "editor.defaultFormatter": "esbenp.prettier-vscode" };
|
|
363
|
+
settings["[javascript]"] = { "editor.defaultFormatter": "esbenp.prettier-vscode" };
|
|
364
|
+
settings["[json]"] = { "editor.defaultFormatter": "esbenp.prettier-vscode" };
|
|
365
|
+
settings["[css]"] = { "editor.defaultFormatter": "esbenp.prettier-vscode" };
|
|
366
|
+
settings["[yaml]"] = { "editor.defaultFormatter": "esbenp.prettier-vscode" };
|
|
367
|
+
settings["editor.formatOnSave"] = true;
|
|
368
|
+
settings["editor.codeActionsOnSave"] = { "source.fixAll.eslint": "explicit" };
|
|
369
|
+
settings["eslint.useFlatConfig"] = true;
|
|
370
|
+
const prettierComponent = ["frontend", "fastify", "e2e"].find(
|
|
371
|
+
(c) => vars.components.includes(c)
|
|
372
|
+
);
|
|
373
|
+
if (prettierComponent) {
|
|
374
|
+
settings["prettier.configPath"] = `${vars.paths[prettierComponent]}/.prettierrc`;
|
|
375
|
+
}
|
|
376
|
+
if (vars.components.includes("fastapi")) {
|
|
377
|
+
settings["ruff.lineLength"] = 120;
|
|
378
|
+
settings["python.analysis.extraPaths"] = [`${vars.paths.fastapi}/src`];
|
|
379
|
+
settings["python.analysis.importFormat"] = "absolute";
|
|
380
|
+
}
|
|
381
|
+
return JSON.stringify(settings, null, 2) + "\n";
|
|
382
|
+
}
|
|
334
383
|
|
|
335
384
|
// src/scaffold.ts
|
|
336
385
|
async function scaffold(opts, dest, localRepo) {
|
|
@@ -380,6 +429,8 @@ async function doScaffold(opts, dest, repoDir, name, nameSnake, vars) {
|
|
|
380
429
|
await writeFile2(join3(dest, "setup.sh"), await generateSetupSh(vars));
|
|
381
430
|
await chmod(join3(dest, "setup.sh"), 493);
|
|
382
431
|
await copyStaticFiles(repoDir, dest);
|
|
432
|
+
await mkdir2(join3(dest, ".vscode"), { recursive: true });
|
|
433
|
+
await writeFile2(join3(dest, ".vscode/settings.json"), generateVscodeSettings(vars));
|
|
383
434
|
const pkg = JSON.parse(
|
|
384
435
|
await readFile3(join3(repoDir, "cli/package.json"), "utf-8")
|
|
385
436
|
);
|
|
@@ -720,6 +771,8 @@ async function doUpdate(cwd, config, repoDir, version, componentPaths) {
|
|
|
720
771
|
const setupSh = await generateSetupSh(vars);
|
|
721
772
|
await writeFile3(join4(cwd, "setup.sh"), setupSh);
|
|
722
773
|
await chmod2(join4(cwd, "setup.sh"), 493);
|
|
774
|
+
await mkdir3(join4(cwd, ".vscode"), { recursive: true });
|
|
775
|
+
await writeFile3(join4(cwd, ".vscode/settings.json"), generateVscodeSettings(vars));
|
|
723
776
|
spinner5.stop("Shared files updated.");
|
|
724
777
|
if (config.components.includes("mobile")) {
|
|
725
778
|
const mobilePath = componentPaths.mobile ?? "mobile";
|
|
@@ -1279,7 +1332,7 @@ async function generateSharedFiles(cwd, repoDir, vars) {
|
|
|
1279
1332
|
}
|
|
1280
1333
|
}
|
|
1281
1334
|
}
|
|
1282
|
-
const statics = [".editorconfig"
|
|
1335
|
+
const statics = [".editorconfig"];
|
|
1283
1336
|
for (const file of statics) {
|
|
1284
1337
|
const src = join7(repoDir, file);
|
|
1285
1338
|
const dest = join7(cwd, file);
|
|
@@ -1303,16 +1356,29 @@ async function generateSharedFiles(cwd, repoDir, vars) {
|
|
|
1303
1356
|
}
|
|
1304
1357
|
}
|
|
1305
1358
|
}
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1359
|
+
const vscodeDest = join7(cwd, ".vscode");
|
|
1360
|
+
await mkdir5(vscodeDest, { recursive: true });
|
|
1361
|
+
const settingsPath = join7(vscodeDest, "settings.json");
|
|
1362
|
+
const settingsContent = generateVscodeSettings(vars);
|
|
1363
|
+
const existingSettings = await readFileOrNull(settingsPath);
|
|
1364
|
+
if (existingSettings === null) {
|
|
1365
|
+
await writeFile5(settingsPath, settingsContent);
|
|
1366
|
+
p5.log.success(".vscode/settings.json");
|
|
1367
|
+
} else if (existingSettings !== settingsContent) {
|
|
1368
|
+
const action = await resolveConflict(".vscode/settings.json", existingSettings, settingsContent);
|
|
1369
|
+
if (action === "overwrite") {
|
|
1370
|
+
await writeFile5(settingsPath, settingsContent);
|
|
1371
|
+
p5.log.success(".vscode/settings.json \u2014 overwritten.");
|
|
1312
1372
|
} else {
|
|
1313
|
-
p5.log.info(".vscode/ \u2014
|
|
1373
|
+
p5.log.info(".vscode/settings.json \u2014 kept existing.");
|
|
1314
1374
|
}
|
|
1315
1375
|
}
|
|
1376
|
+
const extSrc = join7(repoDir, ".vscode/extensions.json");
|
|
1377
|
+
const extDest = join7(vscodeDest, "extensions.json");
|
|
1378
|
+
if (existsSync6(extSrc) && !existsSync6(extDest)) {
|
|
1379
|
+
await cp3(extSrc, extDest);
|
|
1380
|
+
p5.log.success(".vscode/extensions.json");
|
|
1381
|
+
}
|
|
1316
1382
|
}
|
|
1317
1383
|
async function resolveConflict(filePath, existing, template) {
|
|
1318
1384
|
let action = await p5.select({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-projx",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Scaffold production-grade projects. Pick your stack (FastAPI, Fastify, React, Flutter), get a fully wired template with auth, database, CI/CD, and E2E tests.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/templates/ci.yml.ejs
CHANGED
|
@@ -7,9 +7,66 @@ on:
|
|
|
7
7
|
branches: [main]
|
|
8
8
|
|
|
9
9
|
jobs:
|
|
10
|
+
changes:
|
|
11
|
+
name: Detect changes
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
permissions:
|
|
14
|
+
pull-requests: read
|
|
15
|
+
outputs:
|
|
16
|
+
<% if (components.includes('fastapi')) { %>
|
|
17
|
+
fastapi: ${{ steps.filter.outputs.<%= paths.fastapi %> }}
|
|
18
|
+
<% } %>
|
|
19
|
+
<% if (components.includes('fastify')) { %>
|
|
20
|
+
fastify: ${{ steps.filter.outputs.<%= paths.fastify %> }}
|
|
21
|
+
<% } %>
|
|
22
|
+
<% if (components.includes('frontend')) { %>
|
|
23
|
+
frontend: ${{ steps.filter.outputs.<%= paths.frontend %> }}
|
|
24
|
+
<% } %>
|
|
25
|
+
<% if (components.includes('mobile')) { %>
|
|
26
|
+
mobile: ${{ steps.filter.outputs.<%= paths.mobile %> }}
|
|
27
|
+
<% } %>
|
|
28
|
+
<% if (components.includes('e2e')) { %>
|
|
29
|
+
e2e: ${{ steps.filter.outputs.<%= paths.e2e %> }}
|
|
30
|
+
<% } %>
|
|
31
|
+
<% if (components.includes('infra')) { %>
|
|
32
|
+
infra: ${{ steps.filter.outputs.<%= paths.infra %> }}
|
|
33
|
+
<% } %>
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v5
|
|
36
|
+
- uses: dorny/paths-filter@v3
|
|
37
|
+
id: filter
|
|
38
|
+
with:
|
|
39
|
+
filters: |
|
|
40
|
+
<% if (components.includes('fastapi')) { %>
|
|
41
|
+
<%= paths.fastapi %>:
|
|
42
|
+
- '<%= paths.fastapi %>/**'
|
|
43
|
+
<% } %>
|
|
44
|
+
<% if (components.includes('fastify')) { %>
|
|
45
|
+
<%= paths.fastify %>:
|
|
46
|
+
- '<%= paths.fastify %>/**'
|
|
47
|
+
<% } %>
|
|
48
|
+
<% if (components.includes('frontend')) { %>
|
|
49
|
+
<%= paths.frontend %>:
|
|
50
|
+
- '<%= paths.frontend %>/**'
|
|
51
|
+
<% } %>
|
|
52
|
+
<% if (components.includes('mobile')) { %>
|
|
53
|
+
<%= paths.mobile %>:
|
|
54
|
+
- '<%= paths.mobile %>/**'
|
|
55
|
+
<% } %>
|
|
56
|
+
<% if (components.includes('e2e')) { %>
|
|
57
|
+
<%= paths.e2e %>:
|
|
58
|
+
- '<%= paths.e2e %>/**'
|
|
59
|
+
<% } %>
|
|
60
|
+
<% if (components.includes('infra')) { %>
|
|
61
|
+
<%= paths.infra %>:
|
|
62
|
+
- '<%= paths.infra %>/**'
|
|
63
|
+
<% } %>
|
|
10
64
|
<% if (components.includes('fastapi')) { %>
|
|
65
|
+
|
|
11
66
|
fastapi:
|
|
12
67
|
name: FastAPI (format + lint)
|
|
68
|
+
needs: changes
|
|
69
|
+
if: needs.changes.outputs.fastapi == 'true'
|
|
13
70
|
runs-on: ubuntu-latest
|
|
14
71
|
defaults:
|
|
15
72
|
run:
|
|
@@ -22,8 +79,11 @@ jobs:
|
|
|
22
79
|
- run: uv run ruff check src tests
|
|
23
80
|
<% } %>
|
|
24
81
|
<% if (components.includes('fastify')) { %>
|
|
82
|
+
|
|
25
83
|
fastify:
|
|
26
84
|
name: Fastify (format + lint + typecheck)
|
|
85
|
+
needs: changes
|
|
86
|
+
if: needs.changes.outputs.fastify == 'true'
|
|
27
87
|
runs-on: ubuntu-latest
|
|
28
88
|
defaults:
|
|
29
89
|
run:
|
|
@@ -45,8 +105,11 @@ jobs:
|
|
|
45
105
|
- run: npx tsc --noEmit
|
|
46
106
|
<% } %>
|
|
47
107
|
<% if (components.includes('frontend')) { %>
|
|
108
|
+
|
|
48
109
|
frontend:
|
|
49
110
|
name: Frontend (format + lint + typecheck)
|
|
111
|
+
needs: changes
|
|
112
|
+
if: needs.changes.outputs.frontend == 'true'
|
|
50
113
|
runs-on: ubuntu-latest
|
|
51
114
|
defaults:
|
|
52
115
|
run:
|
|
@@ -64,8 +127,11 @@ jobs:
|
|
|
64
127
|
- run: npx tsc --noEmit
|
|
65
128
|
<% } %>
|
|
66
129
|
<% if (components.includes('mobile')) { %>
|
|
130
|
+
|
|
67
131
|
mobile:
|
|
68
132
|
name: Flutter (format + analyze)
|
|
133
|
+
needs: changes
|
|
134
|
+
if: needs.changes.outputs.mobile == 'true'
|
|
69
135
|
runs-on: ubuntu-latest
|
|
70
136
|
defaults:
|
|
71
137
|
run:
|
|
@@ -81,8 +147,11 @@ jobs:
|
|
|
81
147
|
- run: dart analyze --fatal-infos
|
|
82
148
|
<% } %>
|
|
83
149
|
<% if (components.includes('e2e')) { %>
|
|
150
|
+
|
|
84
151
|
e2e:
|
|
85
152
|
name: E2E (format + lint + typecheck)
|
|
153
|
+
needs: changes
|
|
154
|
+
if: needs.changes.outputs.e2e == 'true'
|
|
86
155
|
runs-on: ubuntu-latest
|
|
87
156
|
defaults:
|
|
88
157
|
run:
|
|
@@ -100,8 +169,11 @@ jobs:
|
|
|
100
169
|
- run: npx tsc --noEmit
|
|
101
170
|
<% } %>
|
|
102
171
|
<% if (components.includes('infra')) { %>
|
|
172
|
+
|
|
103
173
|
infra:
|
|
104
174
|
name: Terraform (fmt + validate)
|
|
175
|
+
needs: changes
|
|
176
|
+
if: needs.changes.outputs.infra == 'true'
|
|
105
177
|
runs-on: ubuntu-latest
|
|
106
178
|
defaults:
|
|
107
179
|
run:
|