stackpatch 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/bin/stackpatch.ts +182 -41
- package/package.json +1 -1
package/bin/stackpatch.ts
CHANGED
|
@@ -303,53 +303,56 @@ async function copyFiles(src: string, dest: string): Promise<{ success: boolean;
|
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
// Track files from SOURCE (boilerplate) before copying
|
|
307
|
+
// This ensures we only track files that are actually from StackPatch
|
|
308
|
+
function trackSourceFiles(srcDir: string, baseDir: string, targetBase: string): void {
|
|
309
|
+
if (!fs.existsSync(srcDir)) return;
|
|
310
|
+
|
|
311
|
+
const files = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
312
|
+
for (const file of files) {
|
|
313
|
+
const srcFilePath = path.join(srcDir, file.name);
|
|
314
|
+
if (file.isDirectory()) {
|
|
315
|
+
trackSourceFiles(srcFilePath, baseDir, targetBase);
|
|
316
|
+
} else {
|
|
317
|
+
const relativePath = path.relative(baseDir, srcFilePath);
|
|
318
|
+
const targetPath = targetBase
|
|
319
|
+
? path.join(targetBase, relativePath).replace(/\\/g, "/")
|
|
320
|
+
: relativePath.replace(/\\/g, "/");
|
|
321
|
+
addedFiles.push(targetPath);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
306
326
|
// Copy files with smart app directory handling
|
|
307
327
|
for (const entry of entries) {
|
|
308
328
|
const srcPath = path.join(src, entry.name);
|
|
309
329
|
|
|
310
330
|
if (entry.name === "app") {
|
|
331
|
+
// Track files from SOURCE boilerplate before copying
|
|
332
|
+
trackSourceFiles(srcPath, srcPath, appDir);
|
|
333
|
+
|
|
311
334
|
// Copy app directory contents to the detected app directory location
|
|
312
335
|
await fse.ensureDir(appDirPath);
|
|
313
336
|
await fse.copy(srcPath, appDirPath, { overwrite: true });
|
|
314
|
-
|
|
315
|
-
// Track all files in app directory
|
|
316
|
-
function trackFiles(dir: string, baseDir: string) {
|
|
317
|
-
const files = fs.readdirSync(dir, { withFileTypes: true });
|
|
318
|
-
for (const file of files) {
|
|
319
|
-
const filePath = path.join(dir, file.name);
|
|
320
|
-
if (file.isDirectory()) {
|
|
321
|
-
trackFiles(filePath, baseDir);
|
|
322
|
-
} else {
|
|
323
|
-
const relativePath = path.relative(baseDir, filePath);
|
|
324
|
-
addedFiles.push(path.join(appDir, relativePath).replace(/\\/g, "/"));
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
trackFiles(appDirPath, appDirPath);
|
|
329
337
|
} else if (entry.name === "components") {
|
|
338
|
+
// Track files from SOURCE boilerplate before copying
|
|
339
|
+
trackSourceFiles(srcPath, srcPath, componentsDir);
|
|
340
|
+
|
|
330
341
|
// Copy components directory to the detected components directory location
|
|
331
342
|
await fse.ensureDir(componentsDirPath);
|
|
332
343
|
await fse.copy(srcPath, componentsDirPath, { overwrite: true });
|
|
333
|
-
|
|
334
|
-
// Track all files in components directory
|
|
335
|
-
function trackFiles(dir: string, baseDir: string) {
|
|
336
|
-
const files = fs.readdirSync(dir, { withFileTypes: true });
|
|
337
|
-
for (const file of files) {
|
|
338
|
-
const filePath = path.join(dir, file.name);
|
|
339
|
-
if (file.isDirectory()) {
|
|
340
|
-
trackFiles(filePath, baseDir);
|
|
341
|
-
} else {
|
|
342
|
-
const relativePath = path.relative(baseDir, filePath);
|
|
343
|
-
addedFiles.push(path.join(componentsDir, relativePath).replace(/\\/g, "/"));
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
trackFiles(componentsDirPath, componentsDirPath);
|
|
348
344
|
} else {
|
|
345
|
+
// For root-level files/directories, track from source
|
|
346
|
+
const srcStat = fs.statSync(srcPath);
|
|
347
|
+
if (srcStat.isDirectory()) {
|
|
348
|
+
trackSourceFiles(srcPath, srcPath, "");
|
|
349
|
+
} else {
|
|
350
|
+
addedFiles.push(entry.name);
|
|
351
|
+
}
|
|
352
|
+
|
|
349
353
|
// Copy other files/directories (middleware, etc.) to root
|
|
350
354
|
const destPath = path.join(dest, entry.name);
|
|
351
355
|
await fse.copy(srcPath, destPath, { overwrite: true });
|
|
352
|
-
addedFiles.push(entry.name);
|
|
353
356
|
}
|
|
354
357
|
}
|
|
355
358
|
|
|
@@ -1790,9 +1793,66 @@ async function main() {
|
|
|
1790
1793
|
let failedRestorations: string[] = [];
|
|
1791
1794
|
const directoriesToCheck: Set<string> = new Set();
|
|
1792
1795
|
|
|
1793
|
-
// Step 1:
|
|
1796
|
+
// Step 1: Get list of valid StackPatch files from boilerplate
|
|
1797
|
+
// This ensures we only remove files that are actually from StackPatch
|
|
1798
|
+
const boilerplatePath = path.join(BOILERPLATE_ROOT, manifest.patchName === "auth-ui" ? "auth" : manifest.patchName);
|
|
1799
|
+
const validStackPatchFiles = new Set<string>();
|
|
1800
|
+
|
|
1801
|
+
function collectBoilerplateFiles(srcDir: string, baseDir: string, targetBase: string): void {
|
|
1802
|
+
if (!fs.existsSync(srcDir)) return;
|
|
1803
|
+
|
|
1804
|
+
const files = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
1805
|
+
for (const file of files) {
|
|
1806
|
+
const srcFilePath = path.join(srcDir, file.name);
|
|
1807
|
+
if (file.isDirectory()) {
|
|
1808
|
+
collectBoilerplateFiles(srcFilePath, baseDir, targetBase);
|
|
1809
|
+
} else {
|
|
1810
|
+
const relativePath = path.relative(baseDir, srcFilePath);
|
|
1811
|
+
const targetPath = targetBase
|
|
1812
|
+
? path.join(targetBase, relativePath).replace(/\\/g, "/")
|
|
1813
|
+
: relativePath.replace(/\\/g, "/");
|
|
1814
|
+
validStackPatchFiles.add(targetPath);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
// Collect files from boilerplate app directory
|
|
1820
|
+
const appDir = detectAppDirectory(target);
|
|
1821
|
+
const componentsDir = detectComponentsDirectory(target);
|
|
1822
|
+
const boilerplateAppPath = path.join(boilerplatePath, "app");
|
|
1823
|
+
const boilerplateComponentsPath = path.join(boilerplatePath, "components");
|
|
1824
|
+
|
|
1825
|
+
if (fs.existsSync(boilerplateAppPath)) {
|
|
1826
|
+
collectBoilerplateFiles(boilerplateAppPath, boilerplateAppPath, appDir);
|
|
1827
|
+
}
|
|
1828
|
+
if (fs.existsSync(boilerplateComponentsPath)) {
|
|
1829
|
+
collectBoilerplateFiles(boilerplateComponentsPath, boilerplateComponentsPath, componentsDir);
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
// Collect root-level files
|
|
1833
|
+
if (fs.existsSync(boilerplatePath)) {
|
|
1834
|
+
const entries = fs.readdirSync(boilerplatePath, { withFileTypes: true });
|
|
1835
|
+
for (const entry of entries) {
|
|
1836
|
+
if (entry.name !== "app" && entry.name !== "components") {
|
|
1837
|
+
const srcPath = path.join(boilerplatePath, entry.name);
|
|
1838
|
+
if (entry.isDirectory()) {
|
|
1839
|
+
collectBoilerplateFiles(srcPath, srcPath, "");
|
|
1840
|
+
} else {
|
|
1841
|
+
validStackPatchFiles.add(entry.name);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
// Step 1: Remove added files (only if they're actually from StackPatch boilerplate)
|
|
1794
1848
|
console.log(chalk.white("๐ Removing added files..."));
|
|
1795
1849
|
for (const filePath of manifest.files.added) {
|
|
1850
|
+
// Only remove if this file is actually in the boilerplate
|
|
1851
|
+
if (!validStackPatchFiles.has(filePath)) {
|
|
1852
|
+
console.log(chalk.gray(` โ Skipped (not in boilerplate): ${filePath}`));
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1796
1856
|
const fullPath = path.join(target, filePath);
|
|
1797
1857
|
if (fs.existsSync(fullPath)) {
|
|
1798
1858
|
try {
|
|
@@ -1851,24 +1911,95 @@ async function main() {
|
|
|
1851
1911
|
}
|
|
1852
1912
|
}
|
|
1853
1913
|
|
|
1854
|
-
// Step 3: Restore modified files from
|
|
1914
|
+
// Step 3: Restore modified files from originalContent in manifest
|
|
1915
|
+
// This is more reliable than backups since it contains the true original content
|
|
1855
1916
|
console.log(chalk.white("\n๐ Restoring modified files..."));
|
|
1856
1917
|
for (const modified of manifest.files.modified) {
|
|
1857
|
-
const backupPath = path.join(target, ".stackpatch", "backups", modified.path.replace(/\//g, "_").replace(/\\/g, "_"));
|
|
1858
1918
|
const originalPath = path.join(target, modified.path);
|
|
1859
1919
|
|
|
1860
|
-
if (
|
|
1920
|
+
if (modified.originalContent !== undefined) {
|
|
1861
1921
|
try {
|
|
1862
|
-
|
|
1922
|
+
// Restore from originalContent in manifest (most reliable)
|
|
1923
|
+
const originalDir = path.dirname(originalPath);
|
|
1924
|
+
if (!fs.existsSync(originalDir)) {
|
|
1925
|
+
fs.mkdirSync(originalDir, { recursive: true });
|
|
1926
|
+
}
|
|
1927
|
+
fs.writeFileSync(originalPath, modified.originalContent, "utf-8");
|
|
1863
1928
|
console.log(chalk.green(` โ Restored: ${modified.path}`));
|
|
1864
1929
|
restoredCount++;
|
|
1865
1930
|
} catch (error) {
|
|
1866
|
-
|
|
1867
|
-
|
|
1931
|
+
// Fallback to backup file if originalContent restore fails
|
|
1932
|
+
const backupPath = path.join(target, ".stackpatch", "backups", modified.path.replace(/\//g, "_").replace(/\\/g, "_"));
|
|
1933
|
+
if (fs.existsSync(backupPath)) {
|
|
1934
|
+
try {
|
|
1935
|
+
restoreFile(backupPath, originalPath);
|
|
1936
|
+
console.log(chalk.green(` โ Restored (from backup): ${modified.path}`));
|
|
1937
|
+
restoredCount++;
|
|
1938
|
+
} catch (backupError) {
|
|
1939
|
+
console.log(chalk.yellow(` โ Could not restore: ${modified.path}`));
|
|
1940
|
+
failedRestorations.push(modified.path);
|
|
1941
|
+
}
|
|
1942
|
+
} else {
|
|
1943
|
+
console.log(chalk.yellow(` โ Could not restore: ${modified.path} (no backup found)`));
|
|
1944
|
+
failedRestorations.push(modified.path);
|
|
1945
|
+
}
|
|
1868
1946
|
}
|
|
1869
1947
|
} else {
|
|
1870
|
-
|
|
1871
|
-
|
|
1948
|
+
// Fallback: try to restore from backup file
|
|
1949
|
+
const backupPath = path.join(target, ".stackpatch", "backups", modified.path.replace(/\//g, "_").replace(/\\/g, "_"));
|
|
1950
|
+
if (fs.existsSync(backupPath)) {
|
|
1951
|
+
try {
|
|
1952
|
+
restoreFile(backupPath, originalPath);
|
|
1953
|
+
console.log(chalk.green(` โ Restored (from backup): ${modified.path}`));
|
|
1954
|
+
restoredCount++;
|
|
1955
|
+
} catch (error) {
|
|
1956
|
+
console.log(chalk.yellow(` โ Could not restore: ${modified.path}`));
|
|
1957
|
+
failedRestorations.push(modified.path);
|
|
1958
|
+
}
|
|
1959
|
+
} else {
|
|
1960
|
+
console.log(chalk.yellow(` โ Backup not found and no originalContent: ${modified.path}`));
|
|
1961
|
+
failedRestorations.push(modified.path);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
// Safety check: If file still contains StackPatch components after restore, manually remove them
|
|
1966
|
+
if (fs.existsSync(originalPath) && modified.path.includes("layout.tsx")) {
|
|
1967
|
+
try {
|
|
1968
|
+
let content = fs.readFileSync(originalPath, "utf-8");
|
|
1969
|
+
let needsUpdate = false;
|
|
1970
|
+
|
|
1971
|
+
// Remove AuthSessionProvider import
|
|
1972
|
+
if (content.includes("AuthSessionProvider") && content.includes("session-provider")) {
|
|
1973
|
+
content = content.replace(/import\s*{\s*AuthSessionProvider\s*}\s*from\s*["'][^"']*session-provider[^"']*["'];\s*\n?/g, "");
|
|
1974
|
+
needsUpdate = true;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
// Remove Toaster import
|
|
1978
|
+
if (content.includes("Toaster") && content.includes("toaster")) {
|
|
1979
|
+
content = content.replace(/import\s*{\s*Toaster\s*}\s*from\s*["'][^"']*toaster[^"']*["'];\s*\n?/g, "");
|
|
1980
|
+
needsUpdate = true;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// Remove AuthSessionProvider wrapper
|
|
1984
|
+
if (content.includes("<AuthSessionProvider>") && content.includes("</AuthSessionProvider>")) {
|
|
1985
|
+
content = content.replace(/<AuthSessionProvider>\s*/g, "");
|
|
1986
|
+
content = content.replace(/\s*<\/AuthSessionProvider>/g, "");
|
|
1987
|
+
needsUpdate = true;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
// Remove Toaster component
|
|
1991
|
+
if (content.includes("<Toaster")) {
|
|
1992
|
+
content = content.replace(/<Toaster\s*\/?>\s*\n?\s*/g, "");
|
|
1993
|
+
needsUpdate = true;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
if (needsUpdate) {
|
|
1997
|
+
fs.writeFileSync(originalPath, content, "utf-8");
|
|
1998
|
+
console.log(chalk.green(` โ Cleaned up StackPatch components from: ${modified.path}`));
|
|
1999
|
+
}
|
|
2000
|
+
} catch (error) {
|
|
2001
|
+
// Ignore errors in cleanup
|
|
2002
|
+
}
|
|
1872
2003
|
}
|
|
1873
2004
|
}
|
|
1874
2005
|
|
|
@@ -1884,23 +2015,33 @@ async function main() {
|
|
|
1884
2015
|
}
|
|
1885
2016
|
}
|
|
1886
2017
|
|
|
1887
|
-
// Step 5: Clean up empty directories
|
|
2018
|
+
// Step 5: Clean up empty directories (only if they only contained StackPatch files)
|
|
1888
2019
|
console.log(chalk.white("\n๐งน Cleaning up empty directories..."));
|
|
1889
2020
|
const sortedDirs = Array.from(directoriesToCheck).sort((a, b) => b.length - a.length); // Sort by depth (deepest first)
|
|
2021
|
+
let removedDirCount = 0;
|
|
2022
|
+
|
|
1890
2023
|
for (const dir of sortedDirs) {
|
|
1891
2024
|
if (fs.existsSync(dir)) {
|
|
1892
2025
|
try {
|
|
1893
2026
|
const entries = fs.readdirSync(dir);
|
|
1894
2027
|
if (entries.length === 0) {
|
|
2028
|
+
// Only remove if directory is empty
|
|
2029
|
+
// We know it was created by StackPatch because we're tracking it
|
|
1895
2030
|
fs.rmdirSync(dir);
|
|
2031
|
+
removedDirCount++;
|
|
1896
2032
|
console.log(chalk.green(` โ Removed empty directory: ${path.relative(target, dir)}`));
|
|
1897
2033
|
}
|
|
2034
|
+
// If directory has other files, we don't remove it (silently skip)
|
|
1898
2035
|
} catch {
|
|
1899
2036
|
// Ignore errors
|
|
1900
2037
|
}
|
|
1901
2038
|
}
|
|
1902
2039
|
}
|
|
1903
2040
|
|
|
2041
|
+
if (removedDirCount === 0) {
|
|
2042
|
+
console.log(chalk.gray(" โ No empty directories to remove"));
|
|
2043
|
+
}
|
|
2044
|
+
|
|
1904
2045
|
// Step 6: Remove manifest and backups
|
|
1905
2046
|
console.log(chalk.white("\n๐๏ธ Removing StackPatch tracking files..."));
|
|
1906
2047
|
const stackpatchDir = path.join(target, ".stackpatch");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stackpatch",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Composable frontend features for modern React & Next.js apps - Add authentication, UI components, and more with zero configuration",
|
|
5
5
|
"main": "bin/stackpatch.ts",
|
|
6
6
|
"type": "module",
|