security-migrate 1.0.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/dist/cli.d.ts +2 -0
- package/dist/cli.js +170 -0
- package/dist/discover.d.ts +8 -0
- package/dist/discover.js +115 -0
- package/dist/log.d.ts +11 -0
- package/dist/log.js +24 -0
- package/dist/migrate.d.ts +17 -0
- package/dist/migrate.js +62 -0
- package/dist/patterns.d.ts +7 -0
- package/dist/patterns.js +51 -0
- package/package.json +18 -0
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { resolve, join } from "node:path";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import { discoverSecurityFiles } from "./discover.js";
|
|
6
|
+
import { migrateFiles } from "./migrate.js";
|
|
7
|
+
import { banner, bold, dim, error, yellow, green } from "./log.js";
|
|
8
|
+
const VERSION = "1.0.0";
|
|
9
|
+
const HELP = `
|
|
10
|
+
${bold("security-migrate")} — Migrate gitignored security files to git worktrees
|
|
11
|
+
|
|
12
|
+
${bold("Usage:")}
|
|
13
|
+
security-migrate <target-worktree> [options]
|
|
14
|
+
|
|
15
|
+
${bold("Options:")}
|
|
16
|
+
--copy Copy files instead of symlinking (default: symlink)
|
|
17
|
+
--dry-run Show what would be done without doing it
|
|
18
|
+
--force Overwrite existing files in target
|
|
19
|
+
--source <dir> Source worktree (default: current git root)
|
|
20
|
+
--help Show this help
|
|
21
|
+
--version Show version
|
|
22
|
+
|
|
23
|
+
${bold("Examples:")}
|
|
24
|
+
security-migrate ../worktrees/feature-x
|
|
25
|
+
security-migrate ../worktrees/feature-x --dry-run
|
|
26
|
+
security-migrate ../worktrees/feature-x --copy --force
|
|
27
|
+
security-migrate ../worktrees/feature-x --source /path/to/main
|
|
28
|
+
|
|
29
|
+
${bold("Manifest:")}
|
|
30
|
+
Add a ${dim(".security-migrate")} file to your project root with extra
|
|
31
|
+
glob patterns (one per line, # for comments).
|
|
32
|
+
`.trim();
|
|
33
|
+
function parseArgs(argv) {
|
|
34
|
+
const args = argv.slice(2);
|
|
35
|
+
let target = "";
|
|
36
|
+
let source = null;
|
|
37
|
+
let copy = false;
|
|
38
|
+
let dryRun = false;
|
|
39
|
+
let force = false;
|
|
40
|
+
for (let i = 0; i < args.length; i++) {
|
|
41
|
+
const arg = args[i];
|
|
42
|
+
if (arg === "--help" || arg === "-h") {
|
|
43
|
+
console.log(HELP);
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
if (arg === "--version" || arg === "-v") {
|
|
47
|
+
console.log(VERSION);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
if (arg === "--copy") {
|
|
51
|
+
copy = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (arg === "--dry-run") {
|
|
55
|
+
dryRun = true;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (arg === "--force") {
|
|
59
|
+
force = true;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (arg === "--source") {
|
|
63
|
+
source = args[++i];
|
|
64
|
+
if (!source) {
|
|
65
|
+
error("--source requires a path argument");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (arg.startsWith("-")) {
|
|
71
|
+
error(`Unknown option: ${arg}`);
|
|
72
|
+
console.log(`Run ${dim("security-migrate --help")} for usage.`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
if (!target) {
|
|
76
|
+
target = arg;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
error(`Unexpected argument: ${arg}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!target) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return { target, source, copy, dryRun, force };
|
|
87
|
+
}
|
|
88
|
+
function getGitRoot() {
|
|
89
|
+
try {
|
|
90
|
+
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
error("Not inside a git repository.");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function isGitWorktreeOrRepo(path) {
|
|
98
|
+
// .git can be a file (worktree) or directory (main repo)
|
|
99
|
+
return existsSync(join(path, ".git"));
|
|
100
|
+
}
|
|
101
|
+
function main() {
|
|
102
|
+
const parsed = parseArgs(process.argv);
|
|
103
|
+
if (!parsed) {
|
|
104
|
+
console.log(HELP);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
banner(VERSION);
|
|
108
|
+
const sourceRoot = resolve(parsed.source ?? getGitRoot());
|
|
109
|
+
const targetRoot = resolve(parsed.target);
|
|
110
|
+
// Validate source
|
|
111
|
+
if (!existsSync(sourceRoot)) {
|
|
112
|
+
error(`Source directory does not exist: ${sourceRoot}`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
if (!isGitWorktreeOrRepo(sourceRoot)) {
|
|
116
|
+
error(`Source is not a git repository or worktree: ${sourceRoot}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
// Validate target
|
|
120
|
+
if (!existsSync(targetRoot)) {
|
|
121
|
+
error(`Target directory does not exist: ${targetRoot}`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
if (!isGitWorktreeOrRepo(targetRoot)) {
|
|
125
|
+
error(`Target is not a git repository or worktree: ${targetRoot}`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
console.log(`Source: ${dim(sourceRoot)}`);
|
|
129
|
+
console.log(`Target: ${dim(targetRoot)}`);
|
|
130
|
+
console.log();
|
|
131
|
+
if (parsed.dryRun) {
|
|
132
|
+
console.log(yellow("(dry run — no changes will be made)"));
|
|
133
|
+
console.log();
|
|
134
|
+
}
|
|
135
|
+
// Discover
|
|
136
|
+
console.log("Discovering security files...");
|
|
137
|
+
const files = discoverSecurityFiles({ sourceRoot });
|
|
138
|
+
if (files.length === 0) {
|
|
139
|
+
console.log("No gitignored security files found.");
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
console.log(`Found ${bold(String(files.length))} gitignored security file${files.length === 1 ? "" : "s"}`);
|
|
143
|
+
console.log();
|
|
144
|
+
// Migrate
|
|
145
|
+
const result = migrateFiles({
|
|
146
|
+
sourceRoot,
|
|
147
|
+
targetRoot,
|
|
148
|
+
files,
|
|
149
|
+
copy: parsed.copy,
|
|
150
|
+
force: parsed.force,
|
|
151
|
+
dryRun: parsed.dryRun,
|
|
152
|
+
});
|
|
153
|
+
// Summary
|
|
154
|
+
console.log();
|
|
155
|
+
const parts = [];
|
|
156
|
+
if (result.linked > 0)
|
|
157
|
+
parts.push(green(`${result.linked} symlinked`));
|
|
158
|
+
if (result.copied > 0)
|
|
159
|
+
parts.push(green(`${result.copied} copied`));
|
|
160
|
+
if (result.skipped > 0)
|
|
161
|
+
parts.push(yellow(`${result.skipped} skipped`));
|
|
162
|
+
if (parsed.dryRun) {
|
|
163
|
+
const total = result.linked + result.copied;
|
|
164
|
+
console.log(`Dry run complete. ${bold(String(total))} file${total === 1 ? "" : "s"} would be ${parsed.copy ? "copied" : "symlinked"}, ${result.skipped} skipped.`);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
console.log(`Done! ${parts.join(", ")}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
main();
|
package/dist/discover.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { readdirSync } from "node:fs";
|
|
2
|
+
import { join, relative, basename } from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { DEFAULT_PATTERNS, loadManifestPatterns, getSkipDirs } from "./patterns.js";
|
|
5
|
+
/**
|
|
6
|
+
* Minimal glob matcher supporting:
|
|
7
|
+
* * — matches anything except /
|
|
8
|
+
* ** — matches any number of path segments (including zero)
|
|
9
|
+
* ? — matches a single char except /
|
|
10
|
+
*/
|
|
11
|
+
function matchGlob(pattern, filePath) {
|
|
12
|
+
// Normalize separators
|
|
13
|
+
const p = pattern.replace(/\\/g, "/");
|
|
14
|
+
const f = filePath.replace(/\\/g, "/");
|
|
15
|
+
// Convert glob to regex
|
|
16
|
+
let regex = "";
|
|
17
|
+
let i = 0;
|
|
18
|
+
while (i < p.length) {
|
|
19
|
+
if (p[i] === "*" && p[i + 1] === "*") {
|
|
20
|
+
// ** matches any number of path segments
|
|
21
|
+
if (p[i + 2] === "/") {
|
|
22
|
+
regex += "(?:.+/)?";
|
|
23
|
+
i += 3;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
regex += ".*";
|
|
27
|
+
i += 2;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else if (p[i] === "*") {
|
|
31
|
+
regex += "[^/]*";
|
|
32
|
+
i++;
|
|
33
|
+
}
|
|
34
|
+
else if (p[i] === "?") {
|
|
35
|
+
regex += "[^/]";
|
|
36
|
+
i++;
|
|
37
|
+
}
|
|
38
|
+
else if (p[i] === ".") {
|
|
39
|
+
regex += "\\.";
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
regex += p[i];
|
|
44
|
+
i++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return new RegExp(`^${regex}$`).test(f);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Walk the source tree recursively, collecting all file paths
|
|
51
|
+
* relative to sourceRoot. Skips known non-content directories.
|
|
52
|
+
*/
|
|
53
|
+
function walkDir(dir, sourceRoot, skipDirs) {
|
|
54
|
+
const results = [];
|
|
55
|
+
let entries;
|
|
56
|
+
try {
|
|
57
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return results;
|
|
61
|
+
}
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
if (skipDirs.has(entry.name))
|
|
64
|
+
continue;
|
|
65
|
+
const fullPath = join(dir, entry.name);
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
results.push(...walkDir(fullPath, sourceRoot, skipDirs));
|
|
68
|
+
}
|
|
69
|
+
else if (entry.isFile() || entry.isSymbolicLink()) {
|
|
70
|
+
results.push(relative(sourceRoot, fullPath));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return results;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if a file is gitignored by running `git check-ignore`.
|
|
77
|
+
*/
|
|
78
|
+
function isGitIgnored(filePath, cwd) {
|
|
79
|
+
try {
|
|
80
|
+
execSync(`git check-ignore -q ${JSON.stringify(filePath)}`, {
|
|
81
|
+
cwd,
|
|
82
|
+
stdio: "ignore",
|
|
83
|
+
});
|
|
84
|
+
return true; // exit code 0 = ignored
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false; // exit code 1 = not ignored
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Discover gitignored security files in the source tree.
|
|
92
|
+
* Returns relative paths from sourceRoot.
|
|
93
|
+
*/
|
|
94
|
+
export function discoverSecurityFiles(opts) {
|
|
95
|
+
const { sourceRoot } = opts;
|
|
96
|
+
const skipDirs = getSkipDirs();
|
|
97
|
+
// Combine default + manifest patterns
|
|
98
|
+
const patterns = [...DEFAULT_PATTERNS, ...loadManifestPatterns(sourceRoot)];
|
|
99
|
+
// Walk the tree
|
|
100
|
+
const allFiles = walkDir(sourceRoot, sourceRoot, skipDirs);
|
|
101
|
+
// Filter by pattern match
|
|
102
|
+
const matched = allFiles.filter((filePath) => patterns.some((pattern) => {
|
|
103
|
+
// Match against the full relative path
|
|
104
|
+
if (matchGlob(pattern, filePath))
|
|
105
|
+
return true;
|
|
106
|
+
// Also match against just the filename for non-** patterns
|
|
107
|
+
if (!pattern.includes("/") && !pattern.startsWith("**/")) {
|
|
108
|
+
return matchGlob(pattern, basename(filePath));
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}));
|
|
112
|
+
// Filter to only gitignored files
|
|
113
|
+
const gitignored = matched.filter((filePath) => isGitIgnored(filePath, sourceRoot));
|
|
114
|
+
return gitignored.sort();
|
|
115
|
+
}
|
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const green: (s: string) => string;
|
|
2
|
+
export declare const yellow: (s: string) => string;
|
|
3
|
+
export declare const red: (s: string) => string;
|
|
4
|
+
export declare const dim: (s: string) => string;
|
|
5
|
+
export declare const bold: (s: string) => string;
|
|
6
|
+
export declare const cyan: (s: string) => string;
|
|
7
|
+
export declare function banner(version: string): void;
|
|
8
|
+
export declare function info(label: string, msg: string): void;
|
|
9
|
+
export declare function skip(msg: string): void;
|
|
10
|
+
export declare function warn(msg: string): void;
|
|
11
|
+
export declare function error(msg: string): void;
|
package/dist/log.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const isColorSupported = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
2
|
+
const wrap = (code) => isColorSupported ? (s) => `\x1b[${code}m${s}\x1b[0m` : (s) => s;
|
|
3
|
+
export const green = wrap("32");
|
|
4
|
+
export const yellow = wrap("33");
|
|
5
|
+
export const red = wrap("31");
|
|
6
|
+
export const dim = wrap("2");
|
|
7
|
+
export const bold = wrap("1");
|
|
8
|
+
export const cyan = wrap("36");
|
|
9
|
+
export function banner(version) {
|
|
10
|
+
console.log(bold(`security-migrate v${version}`));
|
|
11
|
+
console.log();
|
|
12
|
+
}
|
|
13
|
+
export function info(label, msg) {
|
|
14
|
+
console.log(` ${green(label.padEnd(6))} ${msg}`);
|
|
15
|
+
}
|
|
16
|
+
export function skip(msg) {
|
|
17
|
+
console.log(` ${yellow("SKIP".padEnd(6))} ${msg}`);
|
|
18
|
+
}
|
|
19
|
+
export function warn(msg) {
|
|
20
|
+
console.log(` ${yellow("WARN".padEnd(6))} ${msg}`);
|
|
21
|
+
}
|
|
22
|
+
export function error(msg) {
|
|
23
|
+
console.error(`${red("Error:")} ${msg}`);
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface MigrateOptions {
|
|
2
|
+
sourceRoot: string;
|
|
3
|
+
targetRoot: string;
|
|
4
|
+
files: string[];
|
|
5
|
+
copy: boolean;
|
|
6
|
+
force: boolean;
|
|
7
|
+
dryRun: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface MigrateResult {
|
|
10
|
+
linked: number;
|
|
11
|
+
copied: number;
|
|
12
|
+
skipped: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Migrate discovered security files from source to target worktree.
|
|
16
|
+
*/
|
|
17
|
+
export declare function migrateFiles(opts: MigrateOptions): MigrateResult;
|
package/dist/migrate.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, symlinkSync, copyFileSync, lstatSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { join, dirname, resolve } from "node:path";
|
|
3
|
+
import { info, skip } from "./log.js";
|
|
4
|
+
/**
|
|
5
|
+
* Migrate discovered security files from source to target worktree.
|
|
6
|
+
*/
|
|
7
|
+
export function migrateFiles(opts) {
|
|
8
|
+
const { sourceRoot, targetRoot, files, copy, force, dryRun } = opts;
|
|
9
|
+
const result = { linked: 0, copied: 0, skipped: 0 };
|
|
10
|
+
const mode = copy ? "COPY" : "LINK";
|
|
11
|
+
for (const relPath of files) {
|
|
12
|
+
const sourcePath = resolve(sourceRoot, relPath);
|
|
13
|
+
const targetPath = join(targetRoot, relPath);
|
|
14
|
+
const targetDir = dirname(targetPath);
|
|
15
|
+
// Check if target already exists
|
|
16
|
+
if (existsSync(targetPath) || isSymlink(targetPath)) {
|
|
17
|
+
if (!force) {
|
|
18
|
+
if (!dryRun) {
|
|
19
|
+
skip(`${relPath} (already exists, use --force)`);
|
|
20
|
+
}
|
|
21
|
+
result.skipped++;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
// Force mode: remove existing
|
|
25
|
+
if (!dryRun) {
|
|
26
|
+
unlinkSync(targetPath);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (dryRun) {
|
|
30
|
+
const arrow = copy ? "←" : "→";
|
|
31
|
+
info(mode, `${relPath} ${arrow} ${sourcePath}`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// Ensure parent directory exists
|
|
35
|
+
mkdirSync(targetDir, { recursive: true });
|
|
36
|
+
if (copy) {
|
|
37
|
+
copyFileSync(sourcePath, targetPath);
|
|
38
|
+
info("COPY", relPath);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
symlinkSync(sourcePath, targetPath);
|
|
42
|
+
info("LINK", `${relPath} → ${sourcePath}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (copy) {
|
|
46
|
+
result.copied++;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
result.linked++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
function isSymlink(path) {
|
|
55
|
+
try {
|
|
56
|
+
lstatSync(path);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const DEFAULT_PATTERNS: string[];
|
|
2
|
+
export declare function getSkipDirs(): Set<string>;
|
|
3
|
+
/**
|
|
4
|
+
* Load additional patterns from a `.security-migrate` manifest file
|
|
5
|
+
* in the source root, if it exists. One glob per line, # for comments.
|
|
6
|
+
*/
|
|
7
|
+
export declare function loadManifestPatterns(sourceRoot: string): string[];
|
package/dist/patterns.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
export const DEFAULT_PATTERNS = [
|
|
4
|
+
// Environment files
|
|
5
|
+
".env",
|
|
6
|
+
".env.*",
|
|
7
|
+
"**/.env",
|
|
8
|
+
"**/.env.*",
|
|
9
|
+
// Firebase
|
|
10
|
+
"**/serviceAccountKey*.json",
|
|
11
|
+
"**/service-account*.json",
|
|
12
|
+
"**/GoogleService-Info.plist",
|
|
13
|
+
"**/google-services.json",
|
|
14
|
+
"**/.firebaserc",
|
|
15
|
+
// Certificates & keys
|
|
16
|
+
"**/*.p8",
|
|
17
|
+
"**/*.p12",
|
|
18
|
+
"**/*.pem",
|
|
19
|
+
"**/*.key",
|
|
20
|
+
// Common secrets
|
|
21
|
+
"**/*.secret",
|
|
22
|
+
"**/secrets.json",
|
|
23
|
+
"**/secrets.yaml",
|
|
24
|
+
];
|
|
25
|
+
const SKIP_DIRS = new Set([
|
|
26
|
+
"node_modules",
|
|
27
|
+
".git",
|
|
28
|
+
"dist",
|
|
29
|
+
"build",
|
|
30
|
+
".build",
|
|
31
|
+
".next",
|
|
32
|
+
"Pods",
|
|
33
|
+
"DerivedData",
|
|
34
|
+
]);
|
|
35
|
+
export function getSkipDirs() {
|
|
36
|
+
return SKIP_DIRS;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Load additional patterns from a `.security-migrate` manifest file
|
|
40
|
+
* in the source root, if it exists. One glob per line, # for comments.
|
|
41
|
+
*/
|
|
42
|
+
export function loadManifestPatterns(sourceRoot) {
|
|
43
|
+
const manifestPath = join(sourceRoot, ".security-migrate");
|
|
44
|
+
if (!existsSync(manifestPath))
|
|
45
|
+
return [];
|
|
46
|
+
const content = readFileSync(manifestPath, "utf-8");
|
|
47
|
+
return content
|
|
48
|
+
.split("\n")
|
|
49
|
+
.map((line) => line.trim())
|
|
50
|
+
.filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
51
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "security-migrate",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Migrate gitignored security files (env, credentials, keys) to new git worktrees",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"security-migrate": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": ["dist"],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "bun run src/cli.ts"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"typescript": "^5.0.0",
|
|
16
|
+
"@types/node": "^20.0.0"
|
|
17
|
+
}
|
|
18
|
+
}
|