create-projx 1.1.1 → 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 +98 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -592,6 +592,10 @@ var NEVER_OVERWRITE = [
|
|
|
592
592
|
/src\/migrations\/versions\//,
|
|
593
593
|
/\.projx-component$/
|
|
594
594
|
];
|
|
595
|
+
var MERGE_DEPS = [
|
|
596
|
+
/^[^/]+\/package\.json$/,
|
|
597
|
+
/^[^/]+\/pyproject\.toml$/
|
|
598
|
+
];
|
|
595
599
|
function isGitRepo(cwd) {
|
|
596
600
|
try {
|
|
597
601
|
execSync2("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
|
|
@@ -680,13 +684,16 @@ async function update(cwd, localRepo) {
|
|
|
680
684
|
}
|
|
681
685
|
execSync2(`git checkout -b ${branchName}`, { cwd, stdio: "pipe" });
|
|
682
686
|
p3.log.info(`Created branch: ${branchName}`);
|
|
687
|
+
let touchedFiles;
|
|
683
688
|
try {
|
|
684
|
-
await doUpdate(cwd, config, repoDir, pkg.version, componentPaths);
|
|
689
|
+
touchedFiles = await doUpdate(cwd, config, repoDir, pkg.version, componentPaths);
|
|
685
690
|
} finally {
|
|
686
691
|
await cleanupRepo(repoDir, isLocal);
|
|
687
692
|
}
|
|
688
|
-
|
|
689
|
-
|
|
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" });
|
|
690
697
|
p3.outro(
|
|
691
698
|
`Updated on branch: ${branchName}
|
|
692
699
|
|
|
@@ -720,8 +727,15 @@ async function doUpdate(cwd, config, repoDir, version, componentPaths) {
|
|
|
720
727
|
const name = detectProjectName(cwd, config.components, componentPaths);
|
|
721
728
|
const nameSnake = toSnake(name);
|
|
722
729
|
const vars = { projectName: name, components: config.components, paths: componentPaths };
|
|
730
|
+
const touchedFiles = [];
|
|
731
|
+
const usedPaths = /* @__PURE__ */ new Set();
|
|
723
732
|
for (const component of config.components) {
|
|
724
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);
|
|
725
739
|
const spinner6 = p3.spinner();
|
|
726
740
|
spinner6.start(`Updating ${targetDir}/ (${component})`);
|
|
727
741
|
const componentSrc = join4(repoDir, component);
|
|
@@ -738,11 +752,21 @@ async function doUpdate(cwd, config, repoDir, version, componentPaths) {
|
|
|
738
752
|
if (NEVER_OVERWRITE.some((re) => re.test(destRel))) continue;
|
|
739
753
|
const dir = dest.substring(0, dest.lastIndexOf("/"));
|
|
740
754
|
await mkdir3(dir, { recursive: true });
|
|
741
|
-
|
|
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
|
+
}
|
|
742
765
|
}
|
|
743
766
|
await rm2(tmpDest, { recursive: true, force: true });
|
|
744
767
|
if (!existsSync3(join4(cwd, targetDir, ".projx-component"))) {
|
|
745
768
|
await writeComponentMarker(join4(cwd, targetDir), component);
|
|
769
|
+
touchedFiles.push(`${targetDir}/.projx-component`);
|
|
746
770
|
}
|
|
747
771
|
spinner6.stop(`${targetDir}/ updated.`);
|
|
748
772
|
}
|
|
@@ -750,29 +774,24 @@ async function doUpdate(cwd, config, repoDir, version, componentPaths) {
|
|
|
750
774
|
spinner5.start("Updating shared files");
|
|
751
775
|
const hasBackend = config.components.includes("fastapi") || config.components.includes("fastify");
|
|
752
776
|
if (hasBackend || config.components.includes("frontend")) {
|
|
753
|
-
await writeFile3(
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
);
|
|
757
|
-
await writeFile3(
|
|
758
|
-
join4(cwd, "docker-compose.dev.yml"),
|
|
759
|
-
await generateDockerComposeDev(vars)
|
|
760
|
-
);
|
|
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");
|
|
761
781
|
}
|
|
762
782
|
await mkdir3(join4(cwd, ".githooks"), { recursive: true });
|
|
763
|
-
|
|
764
|
-
await writeFile3(join4(cwd, ".githooks/pre-commit"), preCommit);
|
|
783
|
+
await writeFile3(join4(cwd, ".githooks/pre-commit"), await generatePreCommit(vars));
|
|
765
784
|
await chmod2(join4(cwd, ".githooks/pre-commit"), 493);
|
|
785
|
+
touchedFiles.push(".githooks/pre-commit");
|
|
766
786
|
await mkdir3(join4(cwd, ".github/workflows"), { recursive: true });
|
|
767
|
-
await writeFile3(
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
);
|
|
771
|
-
const setupSh = await generateSetupSh(vars);
|
|
772
|
-
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));
|
|
773
790
|
await chmod2(join4(cwd, "setup.sh"), 493);
|
|
791
|
+
touchedFiles.push("setup.sh");
|
|
774
792
|
await mkdir3(join4(cwd, ".vscode"), { recursive: true });
|
|
775
793
|
await writeFile3(join4(cwd, ".vscode/settings.json"), generateVscodeSettings(vars));
|
|
794
|
+
touchedFiles.push(".vscode/settings.json");
|
|
776
795
|
spinner5.stop("Shared files updated.");
|
|
777
796
|
if (config.components.includes("mobile")) {
|
|
778
797
|
const mobilePath = componentPaths.mobile ?? "mobile";
|
|
@@ -790,6 +809,8 @@ async function doUpdate(cwd, config, repoDir, version, componentPaths) {
|
|
|
790
809
|
paths: componentPaths
|
|
791
810
|
};
|
|
792
811
|
await writeFile3(join4(cwd, ".projx"), JSON.stringify(updatedConfig, null, 2));
|
|
812
|
+
touchedFiles.push(".projx");
|
|
813
|
+
return touchedFiles;
|
|
793
814
|
}
|
|
794
815
|
function detectProjectName(cwd, components, componentPaths) {
|
|
795
816
|
for (const component of components) {
|
|
@@ -810,6 +831,63 @@ function detectProjectName(cwd, components, componentPaths) {
|
|
|
810
831
|
}
|
|
811
832
|
return toKebab(cwd.split("/").pop());
|
|
812
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
|
+
}
|
|
813
891
|
|
|
814
892
|
// src/add.ts
|
|
815
893
|
import { copyFileSync as copyFileSync2, existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
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": {
|