create-projx 1.1.0 → 1.1.2
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 +181 -37
- 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
|
);
|
|
@@ -541,6 +592,10 @@ var NEVER_OVERWRITE = [
|
|
|
541
592
|
/src\/migrations\/versions\//,
|
|
542
593
|
/\.projx-component$/
|
|
543
594
|
];
|
|
595
|
+
var MERGE_DEPS = [
|
|
596
|
+
/^[^/]+\/package\.json$/,
|
|
597
|
+
/^[^/]+\/pyproject\.toml$/
|
|
598
|
+
];
|
|
544
599
|
function isGitRepo(cwd) {
|
|
545
600
|
try {
|
|
546
601
|
execSync2("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
|
|
@@ -629,13 +684,16 @@ async function update(cwd, localRepo) {
|
|
|
629
684
|
}
|
|
630
685
|
execSync2(`git checkout -b ${branchName}`, { cwd, stdio: "pipe" });
|
|
631
686
|
p3.log.info(`Created branch: ${branchName}`);
|
|
687
|
+
let touchedFiles;
|
|
632
688
|
try {
|
|
633
|
-
await doUpdate(cwd, config, repoDir, pkg.version, componentPaths);
|
|
689
|
+
touchedFiles = await doUpdate(cwd, config, repoDir, pkg.version, componentPaths);
|
|
634
690
|
} finally {
|
|
635
691
|
await cleanupRepo(repoDir, isLocal);
|
|
636
692
|
}
|
|
637
|
-
|
|
638
|
-
|
|
693
|
+
for (const f of touchedFiles) {
|
|
694
|
+
execSync2(`git add "${f}"`, { cwd, stdio: "pipe" });
|
|
695
|
+
}
|
|
696
|
+
execSync2(`git commit --no-verify -m "projx update to v${pkg.version}"`, { cwd, stdio: "pipe" });
|
|
639
697
|
p3.outro(
|
|
640
698
|
`Updated on branch: ${branchName}
|
|
641
699
|
|
|
@@ -669,8 +727,15 @@ async function doUpdate(cwd, config, repoDir, version, componentPaths) {
|
|
|
669
727
|
const name = detectProjectName(cwd, config.components, componentPaths);
|
|
670
728
|
const nameSnake = toSnake(name);
|
|
671
729
|
const vars = { projectName: name, components: config.components, paths: componentPaths };
|
|
730
|
+
const touchedFiles = [];
|
|
731
|
+
const usedPaths = /* @__PURE__ */ new Set();
|
|
672
732
|
for (const component of config.components) {
|
|
673
733
|
const targetDir = componentPaths[component];
|
|
734
|
+
if (usedPaths.has(targetDir)) {
|
|
735
|
+
p3.log.warn(`${component} shares directory ${targetDir}/ with another component \u2014 skipping overlay to avoid nesting.`);
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
usedPaths.add(targetDir);
|
|
674
739
|
const spinner6 = p3.spinner();
|
|
675
740
|
spinner6.start(`Updating ${targetDir}/ (${component})`);
|
|
676
741
|
const componentSrc = join4(repoDir, component);
|
|
@@ -687,11 +752,21 @@ async function doUpdate(cwd, config, repoDir, version, componentPaths) {
|
|
|
687
752
|
if (NEVER_OVERWRITE.some((re) => re.test(destRel))) continue;
|
|
688
753
|
const dir = dest.substring(0, dest.lastIndexOf("/"));
|
|
689
754
|
await mkdir3(dir, { recursive: true });
|
|
690
|
-
|
|
755
|
+
if (MERGE_DEPS.some((re) => re.test(destRel)) && existsSync3(dest)) {
|
|
756
|
+
const merged = await mergeDeps(dest, src);
|
|
757
|
+
if (merged) {
|
|
758
|
+
await writeFile3(dest, merged);
|
|
759
|
+
touchedFiles.push(destRel);
|
|
760
|
+
}
|
|
761
|
+
} else {
|
|
762
|
+
await cp2(src, dest, { force: true });
|
|
763
|
+
touchedFiles.push(destRel);
|
|
764
|
+
}
|
|
691
765
|
}
|
|
692
766
|
await rm2(tmpDest, { recursive: true, force: true });
|
|
693
767
|
if (!existsSync3(join4(cwd, targetDir, ".projx-component"))) {
|
|
694
768
|
await writeComponentMarker(join4(cwd, targetDir), component);
|
|
769
|
+
touchedFiles.push(`${targetDir}/.projx-component`);
|
|
695
770
|
}
|
|
696
771
|
spinner6.stop(`${targetDir}/ updated.`);
|
|
697
772
|
}
|
|
@@ -699,27 +774,24 @@ async function doUpdate(cwd, config, repoDir, version, componentPaths) {
|
|
|
699
774
|
spinner5.start("Updating shared files");
|
|
700
775
|
const hasBackend = config.components.includes("fastapi") || config.components.includes("fastify");
|
|
701
776
|
if (hasBackend || config.components.includes("frontend")) {
|
|
702
|
-
await writeFile3(
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
);
|
|
706
|
-
await writeFile3(
|
|
707
|
-
join4(cwd, "docker-compose.dev.yml"),
|
|
708
|
-
await generateDockerComposeDev(vars)
|
|
709
|
-
);
|
|
777
|
+
await writeFile3(join4(cwd, "docker-compose.yml"), await generateDockerCompose(vars));
|
|
778
|
+
touchedFiles.push("docker-compose.yml");
|
|
779
|
+
await writeFile3(join4(cwd, "docker-compose.dev.yml"), await generateDockerComposeDev(vars));
|
|
780
|
+
touchedFiles.push("docker-compose.dev.yml");
|
|
710
781
|
}
|
|
711
782
|
await mkdir3(join4(cwd, ".githooks"), { recursive: true });
|
|
712
|
-
|
|
713
|
-
await writeFile3(join4(cwd, ".githooks/pre-commit"), preCommit);
|
|
783
|
+
await writeFile3(join4(cwd, ".githooks/pre-commit"), await generatePreCommit(vars));
|
|
714
784
|
await chmod2(join4(cwd, ".githooks/pre-commit"), 493);
|
|
785
|
+
touchedFiles.push(".githooks/pre-commit");
|
|
715
786
|
await mkdir3(join4(cwd, ".github/workflows"), { recursive: true });
|
|
716
|
-
await writeFile3(
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
);
|
|
720
|
-
const setupSh = await generateSetupSh(vars);
|
|
721
|
-
await writeFile3(join4(cwd, "setup.sh"), setupSh);
|
|
787
|
+
await writeFile3(join4(cwd, ".github/workflows/ci.yml"), await generateCiYml(vars));
|
|
788
|
+
touchedFiles.push(".github/workflows/ci.yml");
|
|
789
|
+
await writeFile3(join4(cwd, "setup.sh"), await generateSetupSh(vars));
|
|
722
790
|
await chmod2(join4(cwd, "setup.sh"), 493);
|
|
791
|
+
touchedFiles.push("setup.sh");
|
|
792
|
+
await mkdir3(join4(cwd, ".vscode"), { recursive: true });
|
|
793
|
+
await writeFile3(join4(cwd, ".vscode/settings.json"), generateVscodeSettings(vars));
|
|
794
|
+
touchedFiles.push(".vscode/settings.json");
|
|
723
795
|
spinner5.stop("Shared files updated.");
|
|
724
796
|
if (config.components.includes("mobile")) {
|
|
725
797
|
const mobilePath = componentPaths.mobile ?? "mobile";
|
|
@@ -737,6 +809,8 @@ async function doUpdate(cwd, config, repoDir, version, componentPaths) {
|
|
|
737
809
|
paths: componentPaths
|
|
738
810
|
};
|
|
739
811
|
await writeFile3(join4(cwd, ".projx"), JSON.stringify(updatedConfig, null, 2));
|
|
812
|
+
touchedFiles.push(".projx");
|
|
813
|
+
return touchedFiles;
|
|
740
814
|
}
|
|
741
815
|
function detectProjectName(cwd, components, componentPaths) {
|
|
742
816
|
for (const component of components) {
|
|
@@ -757,6 +831,63 @@ function detectProjectName(cwd, components, componentPaths) {
|
|
|
757
831
|
}
|
|
758
832
|
return toKebab(cwd.split("/").pop());
|
|
759
833
|
}
|
|
834
|
+
async function mergeDeps(existingPath, templatePath) {
|
|
835
|
+
if (existingPath.endsWith("package.json")) {
|
|
836
|
+
return mergePackageJson(existingPath, templatePath);
|
|
837
|
+
}
|
|
838
|
+
if (existingPath.endsWith("pyproject.toml")) {
|
|
839
|
+
return mergePyprojectToml(existingPath, templatePath);
|
|
840
|
+
}
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
async function mergePackageJson(existingPath, templatePath) {
|
|
844
|
+
const existingRaw = await readFileOrNull(existingPath);
|
|
845
|
+
const templateRaw = await readFileOrNull(templatePath);
|
|
846
|
+
if (!existingRaw || !templateRaw) return null;
|
|
847
|
+
try {
|
|
848
|
+
const existing = JSON.parse(existingRaw);
|
|
849
|
+
const template = JSON.parse(templateRaw);
|
|
850
|
+
if (template.dependencies) {
|
|
851
|
+
existing.dependencies = { ...template.dependencies, ...existing.dependencies };
|
|
852
|
+
}
|
|
853
|
+
if (template.devDependencies) {
|
|
854
|
+
existing.devDependencies = { ...template.devDependencies, ...existing.devDependencies };
|
|
855
|
+
}
|
|
856
|
+
if (template.scripts) {
|
|
857
|
+
existing.scripts = { ...template.scripts, ...existing.scripts };
|
|
858
|
+
}
|
|
859
|
+
return JSON.stringify(existing, null, 2) + "\n";
|
|
860
|
+
} catch {
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
async function mergePyprojectToml(existingPath, templatePath) {
|
|
865
|
+
const existingRaw = await readFileOrNull(existingPath);
|
|
866
|
+
const templateRaw = await readFileOrNull(templatePath);
|
|
867
|
+
if (!existingRaw || !templateRaw) return null;
|
|
868
|
+
const templateDeps = extractTomlDeps(templateRaw);
|
|
869
|
+
if (templateDeps.length === 0) return null;
|
|
870
|
+
const existingDeps = extractTomlDeps(existingRaw);
|
|
871
|
+
const existingNames = new Set(existingDeps.map((d) => d.replace(/[><=!~[].*/, "").trim().toLowerCase()));
|
|
872
|
+
const newDeps = templateDeps.filter((d) => {
|
|
873
|
+
const name = d.replace(/[><=!~[].*/, "").trim().toLowerCase();
|
|
874
|
+
return !existingNames.has(name);
|
|
875
|
+
});
|
|
876
|
+
if (newDeps.length === 0) return null;
|
|
877
|
+
const depsMatch = existingRaw.match(/^dependencies\s*=\s*\[([^\]]*)\]/m);
|
|
878
|
+
if (!depsMatch) return null;
|
|
879
|
+
const closingBracket = existingRaw.indexOf("]", depsMatch.index);
|
|
880
|
+
const before = existingRaw.slice(0, closingBracket);
|
|
881
|
+
const after = existingRaw.slice(closingBracket);
|
|
882
|
+
const indent = " ";
|
|
883
|
+
const newLines = newDeps.map((d) => `${indent}"${d}",`).join("\n");
|
|
884
|
+
return before.trimEnd() + "\n" + newLines + "\n" + after;
|
|
885
|
+
}
|
|
886
|
+
function extractTomlDeps(toml) {
|
|
887
|
+
const match = toml.match(/^dependencies\s*=\s*\[([\s\S]*?)\]/m);
|
|
888
|
+
if (!match) return [];
|
|
889
|
+
return match[1].split("\n").map((l) => l.trim()).filter((l) => l.startsWith('"') || l.startsWith("'")).map((l) => l.replace(/^["']|["'],?$/g, "").trim()).filter(Boolean);
|
|
890
|
+
}
|
|
760
891
|
|
|
761
892
|
// src/add.ts
|
|
762
893
|
import { copyFileSync as copyFileSync2, existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
@@ -1279,7 +1410,7 @@ async function generateSharedFiles(cwd, repoDir, vars) {
|
|
|
1279
1410
|
}
|
|
1280
1411
|
}
|
|
1281
1412
|
}
|
|
1282
|
-
const statics = [".editorconfig"
|
|
1413
|
+
const statics = [".editorconfig"];
|
|
1283
1414
|
for (const file of statics) {
|
|
1284
1415
|
const src = join7(repoDir, file);
|
|
1285
1416
|
const dest = join7(cwd, file);
|
|
@@ -1303,16 +1434,29 @@ async function generateSharedFiles(cwd, repoDir, vars) {
|
|
|
1303
1434
|
}
|
|
1304
1435
|
}
|
|
1305
1436
|
}
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1437
|
+
const vscodeDest = join7(cwd, ".vscode");
|
|
1438
|
+
await mkdir5(vscodeDest, { recursive: true });
|
|
1439
|
+
const settingsPath = join7(vscodeDest, "settings.json");
|
|
1440
|
+
const settingsContent = generateVscodeSettings(vars);
|
|
1441
|
+
const existingSettings = await readFileOrNull(settingsPath);
|
|
1442
|
+
if (existingSettings === null) {
|
|
1443
|
+
await writeFile5(settingsPath, settingsContent);
|
|
1444
|
+
p5.log.success(".vscode/settings.json");
|
|
1445
|
+
} else if (existingSettings !== settingsContent) {
|
|
1446
|
+
const action = await resolveConflict(".vscode/settings.json", existingSettings, settingsContent);
|
|
1447
|
+
if (action === "overwrite") {
|
|
1448
|
+
await writeFile5(settingsPath, settingsContent);
|
|
1449
|
+
p5.log.success(".vscode/settings.json \u2014 overwritten.");
|
|
1312
1450
|
} else {
|
|
1313
|
-
p5.log.info(".vscode/ \u2014
|
|
1451
|
+
p5.log.info(".vscode/settings.json \u2014 kept existing.");
|
|
1314
1452
|
}
|
|
1315
1453
|
}
|
|
1454
|
+
const extSrc = join7(repoDir, ".vscode/extensions.json");
|
|
1455
|
+
const extDest = join7(vscodeDest, "extensions.json");
|
|
1456
|
+
if (existsSync6(extSrc) && !existsSync6(extDest)) {
|
|
1457
|
+
await cp3(extSrc, extDest);
|
|
1458
|
+
p5.log.success(".vscode/extensions.json");
|
|
1459
|
+
}
|
|
1316
1460
|
}
|
|
1317
1461
|
async function resolveConflict(filePath, existing, template) {
|
|
1318
1462
|
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.2",
|
|
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:
|