@nitrostack/cli 1.0.13 → 1.0.15
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/assets/canonical.gitignore +57 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +90 -16
- package/dist/commands/pack.d.ts +10 -0
- package/dist/commands/pack.d.ts.map +1 -0
- package/dist/commands/pack.js +82 -0
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +66 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/pack/canonical-gitignore.d.ts +14 -0
- package/dist/pack/canonical-gitignore.d.ts.map +1 -0
- package/dist/pack/canonical-gitignore.js +28 -0
- package/dist/pack/exclusions.d.ts +8 -0
- package/dist/pack/exclusions.d.ts.map +1 -0
- package/dist/pack/exclusions.js +84 -0
- package/dist/pack/gitignore.d.ts +21 -0
- package/dist/pack/gitignore.d.ts.map +1 -0
- package/dist/pack/gitignore.js +123 -0
- package/dist/pack/ignore-matcher.d.ts +19 -0
- package/dist/pack/ignore-matcher.d.ts.map +1 -0
- package/dist/pack/ignore-matcher.js +149 -0
- package/dist/pack/index.d.ts +11 -0
- package/dist/pack/index.d.ts.map +1 -0
- package/dist/pack/index.js +8 -0
- package/dist/pack/pack-project.d.ts +6 -0
- package/dist/pack/pack-project.d.ts.map +1 -0
- package/dist/pack/pack-project.js +59 -0
- package/dist/pack/standalone.d.ts +3 -0
- package/dist/pack/standalone.d.ts.map +1 -0
- package/dist/pack/standalone.js +95 -0
- package/dist/pack/tree.d.ts +5 -0
- package/dist/pack/tree.d.ts.map +1 -0
- package/dist/pack/tree.js +70 -0
- package/dist/pack/types.d.ts +35 -0
- package/dist/pack/types.d.ts.map +1 -0
- package/dist/pack/types.js +1 -0
- package/dist/pack/validate-project.d.ts +9 -0
- package/dist/pack/validate-project.d.ts.map +1 -0
- package/dist/pack/validate-project.js +44 -0
- package/dist/pack/zipper.d.ts +20 -0
- package/dist/pack/zipper.d.ts.map +1 -0
- package/dist/pack/zipper.js +121 -0
- package/dist/skills/clone.d.ts +28 -0
- package/dist/skills/clone.d.ts.map +1 -0
- package/dist/skills/clone.js +60 -0
- package/dist/skills/detect-agents.d.ts +8 -0
- package/dist/skills/detect-agents.d.ts.map +1 -0
- package/dist/skills/detect-agents.js +129 -0
- package/dist/skills/discover.d.ts +12 -0
- package/dist/skills/discover.d.ts.map +1 -0
- package/dist/skills/discover.js +45 -0
- package/dist/skills/index.d.ts +21 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +97 -0
- package/dist/skills/installer.d.ts +21 -0
- package/dist/skills/installer.d.ts.map +1 -0
- package/dist/skills/installer.js +48 -0
- package/dist/skills/types.d.ts +39 -0
- package/dist/skills/types.d.ts.map +1 -0
- package/dist/skills/types.js +1 -0
- package/dist/skills/ui.d.ts +55 -0
- package/dist/skills/ui.d.ts.map +1 -0
- package/dist/skills/ui.js +102 -0
- package/package.json +6 -3
- package/templates/typescript-oauth/.env.example +1 -1
- package/templates/typescript-oauth/_gitignore +57 -0
- package/templates/typescript-pizzaz/.env.example +1 -1
- package/templates/typescript-pizzaz/_gitignore +57 -0
- package/templates/typescript-starter/.env.example +1 -1
- package/templates/typescript-starter/_gitignore +57 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { loadCanonicalGitignore } from './canonical-gitignore.js';
|
|
4
|
+
import { ENV_EXCLUDED_PATTERNS, HARD_EXCLUDED_PATTERNS } from './exclusions.js';
|
|
5
|
+
import { createIgnoreMatcher } from './ignore-matcher.js';
|
|
6
|
+
const GIT_DIR_PATTERN = '.git/';
|
|
7
|
+
function normalizeRuleLine(line) {
|
|
8
|
+
const trimmed = line.trim();
|
|
9
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return trimmed;
|
|
13
|
+
}
|
|
14
|
+
function parseRules(content) {
|
|
15
|
+
return content
|
|
16
|
+
.split(/\r?\n/)
|
|
17
|
+
.map(normalizeRuleLine)
|
|
18
|
+
.filter((line) => line !== null);
|
|
19
|
+
}
|
|
20
|
+
function isEnvRule(rule) {
|
|
21
|
+
return ENV_EXCLUDED_PATTERNS.some((pattern) => {
|
|
22
|
+
if (pattern === rule)
|
|
23
|
+
return true;
|
|
24
|
+
if (pattern === '.env.*.local') {
|
|
25
|
+
return /^\.env\..+\.local$/.test(rule) || rule === '.env.*.local';
|
|
26
|
+
}
|
|
27
|
+
return rule === pattern || rule.startsWith(`${pattern}/`);
|
|
28
|
+
}) || rule === '.env' || rule.startsWith('.env.');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Non-destructively merge canonical rules into a local .gitignore.
|
|
32
|
+
* Existing local rules are preserved; only missing canonical rules are appended.
|
|
33
|
+
*/
|
|
34
|
+
export function mergeGitignoreRules(localContent, canonicalContent) {
|
|
35
|
+
const localRules = parseRules(localContent);
|
|
36
|
+
const canonicalRules = parseRules(canonicalContent);
|
|
37
|
+
const existing = new Set(localRules);
|
|
38
|
+
const addedRules = [];
|
|
39
|
+
for (const rule of canonicalRules) {
|
|
40
|
+
if (!existing.has(rule)) {
|
|
41
|
+
addedRules.push(rule);
|
|
42
|
+
existing.add(rule);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!existing.has(GIT_DIR_PATTERN)) {
|
|
46
|
+
addedRules.push(GIT_DIR_PATTERN);
|
|
47
|
+
existing.add(GIT_DIR_PATTERN);
|
|
48
|
+
}
|
|
49
|
+
const newlyAddedRules = addedRules.filter((rule) => !localRules.includes(rule));
|
|
50
|
+
const updated = newlyAddedRules.length > 0;
|
|
51
|
+
if (!updated) {
|
|
52
|
+
const normalized = localContent.endsWith('\n') || localContent.length === 0
|
|
53
|
+
? localContent
|
|
54
|
+
: `${localContent}\n`;
|
|
55
|
+
return {
|
|
56
|
+
mergedContent: normalized,
|
|
57
|
+
addedRules: [],
|
|
58
|
+
updated: false,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const mergedContent = rebuildGitignoreContent(localContent, newlyAddedRules);
|
|
62
|
+
return {
|
|
63
|
+
mergedContent,
|
|
64
|
+
addedRules: newlyAddedRules,
|
|
65
|
+
updated: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function rebuildGitignoreContent(localContent, appendedRules) {
|
|
69
|
+
const base = localContent.trimEnd();
|
|
70
|
+
const suffix = appendedRules.join('\n');
|
|
71
|
+
return base.length > 0 ? `${base}\n\n# Added by nitrostack pack\n${suffix}\n` : `${suffix}\n`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Sync local .gitignore with canonical rules when enabled.
|
|
75
|
+
*/
|
|
76
|
+
export async function syncProjectGitignore(projectRoot, syncGitignore) {
|
|
77
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
78
|
+
const canonicalContent = await loadCanonicalGitignore();
|
|
79
|
+
const localContent = (await fs.pathExists(gitignorePath))
|
|
80
|
+
? await fs.readFile(gitignorePath, 'utf-8')
|
|
81
|
+
: '';
|
|
82
|
+
const mergeResult = mergeGitignoreRules(localContent, canonicalContent);
|
|
83
|
+
if (syncGitignore && mergeResult.updated) {
|
|
84
|
+
await fs.writeFile(gitignorePath, mergeResult.mergedContent, 'utf-8');
|
|
85
|
+
return mergeResult;
|
|
86
|
+
}
|
|
87
|
+
// Matcher still uses merged rules, but only report an update when we actually wrote.
|
|
88
|
+
return {
|
|
89
|
+
mergedContent: mergeResult.mergedContent,
|
|
90
|
+
addedRules: [],
|
|
91
|
+
updated: false,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build a gitignore matcher from merged project rules and hard-coded exclusions.
|
|
96
|
+
*/
|
|
97
|
+
export function buildIgnoreMatcher(mergedGitignoreContent, options = {}) {
|
|
98
|
+
const includeEnv = options.includeEnv ?? false;
|
|
99
|
+
let mergedRules = parseRules(mergedGitignoreContent);
|
|
100
|
+
if (includeEnv) {
|
|
101
|
+
mergedRules = mergedRules.filter((rule) => !isEnvRule(rule));
|
|
102
|
+
}
|
|
103
|
+
const hardPatterns = includeEnv
|
|
104
|
+
? HARD_EXCLUDED_PATTERNS.filter((pattern) => !ENV_EXCLUDED_PATTERNS.includes(pattern))
|
|
105
|
+
: HARD_EXCLUDED_PATTERNS;
|
|
106
|
+
const allRules = [...mergedRules];
|
|
107
|
+
for (const pattern of hardPatterns) {
|
|
108
|
+
if (!allRules.includes(pattern)) {
|
|
109
|
+
allRules.push(pattern);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (!allRules.includes(GIT_DIR_PATTERN)) {
|
|
113
|
+
allRules.push(GIT_DIR_PATTERN);
|
|
114
|
+
}
|
|
115
|
+
return createIgnoreMatcher(allRules);
|
|
116
|
+
}
|
|
117
|
+
export function isPathIgnored(matcher, projectRoot, absolutePath, isDirectory = false) {
|
|
118
|
+
const relativePath = path.relative(projectRoot, absolutePath).split(path.sep).join('/');
|
|
119
|
+
if (!relativePath || relativePath === '.') {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return matcher.ignores(relativePath, isDirectory);
|
|
123
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight gitignore-style matcher implemented in-house (no external ignore package).
|
|
3
|
+
* Supports the pattern subset used by nitrostack pack:
|
|
4
|
+
* - Directory rules: dist/, .git/
|
|
5
|
+
* - Recursive directory rules: slash-star-star/dist/
|
|
6
|
+
* - Exact file names: .env, .DS_Store
|
|
7
|
+
* - Simple globs: *.log, *.tsbuildinfo, npm-debug.log*, .env.*.local
|
|
8
|
+
* - Path-anchored rules with / (match relative to project root)
|
|
9
|
+
* - Negation with leading ! (last matching rule wins)
|
|
10
|
+
*/
|
|
11
|
+
export interface IgnoreMatcher {
|
|
12
|
+
ignores(relativePath: string, isDirectory?: boolean): boolean;
|
|
13
|
+
add(patterns: string | string[]): IgnoreMatcher;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a matcher from a list of gitignore-style patterns.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createIgnoreMatcher(patterns?: string[]): IgnoreMatcher;
|
|
19
|
+
//# sourceMappingURL=ignore-matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ignore-matcher.d.ts","sourceRoot":"","sources":["../../src/pack/ignore-matcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAC9D,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,aAAa,CAAC;CACjD;AAkHD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,GAAE,MAAM,EAAO,GAAG,aAAa,CAmD1E"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight gitignore-style matcher implemented in-house (no external ignore package).
|
|
3
|
+
* Supports the pattern subset used by nitrostack pack:
|
|
4
|
+
* - Directory rules: dist/, .git/
|
|
5
|
+
* - Recursive directory rules: slash-star-star/dist/
|
|
6
|
+
* - Exact file names: .env, .DS_Store
|
|
7
|
+
* - Simple globs: *.log, *.tsbuildinfo, npm-debug.log*, .env.*.local
|
|
8
|
+
* - Path-anchored rules with / (match relative to project root)
|
|
9
|
+
* - Negation with leading ! (last matching rule wins)
|
|
10
|
+
*/
|
|
11
|
+
function escapeRegex(value) {
|
|
12
|
+
return value.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Convert a single gitignore-like pattern into a RegExp that matches
|
|
16
|
+
* the full relative path (forward-slash separated, no leading ./).
|
|
17
|
+
*/
|
|
18
|
+
function compilePattern(pattern) {
|
|
19
|
+
let raw = pattern.trim();
|
|
20
|
+
if (!raw || raw.startsWith('#')) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
let negated = false;
|
|
24
|
+
if (raw.startsWith('!')) {
|
|
25
|
+
negated = true;
|
|
26
|
+
raw = raw.slice(1);
|
|
27
|
+
}
|
|
28
|
+
if (raw.startsWith('\\!')) {
|
|
29
|
+
raw = raw.slice(1);
|
|
30
|
+
}
|
|
31
|
+
let directoryOnly = false;
|
|
32
|
+
if (raw.endsWith('/')) {
|
|
33
|
+
directoryOnly = true;
|
|
34
|
+
raw = raw.slice(0, -1);
|
|
35
|
+
}
|
|
36
|
+
if (!raw) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const anchored = raw.startsWith('/');
|
|
40
|
+
if (anchored) {
|
|
41
|
+
raw = raw.slice(1);
|
|
42
|
+
}
|
|
43
|
+
const hasSlash = raw.includes('/');
|
|
44
|
+
const isRecursivePrefix = raw.startsWith('**/');
|
|
45
|
+
const basenameOnly = !anchored && !hasSlash;
|
|
46
|
+
let body = '';
|
|
47
|
+
let i = 0;
|
|
48
|
+
while (i < raw.length) {
|
|
49
|
+
if (raw.startsWith('**/', i)) {
|
|
50
|
+
body += '(?:.*/)?';
|
|
51
|
+
i += 3;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (raw[i] === '*') {
|
|
55
|
+
body += '[^/]*';
|
|
56
|
+
i += 1;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (raw[i] === '?') {
|
|
60
|
+
body += '[^/]';
|
|
61
|
+
i += 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
body += escapeRegex(raw[i]);
|
|
65
|
+
i += 1;
|
|
66
|
+
}
|
|
67
|
+
let source;
|
|
68
|
+
if (isRecursivePrefix || raw.includes('**/')) {
|
|
69
|
+
source = `^${body}(?:/.*)?$`;
|
|
70
|
+
}
|
|
71
|
+
else if (basenameOnly) {
|
|
72
|
+
// Match the basename anywhere in the path (gitignore default)
|
|
73
|
+
source = `(^|/)${body}(?:/.*)?$`;
|
|
74
|
+
}
|
|
75
|
+
else if (anchored || hasSlash) {
|
|
76
|
+
source = `^${body}(?:/.*)?$`;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
source = `(^|/)${body}(?:/.*)?$`;
|
|
80
|
+
}
|
|
81
|
+
// For directory-only rules, also compile a regex that requires a child path
|
|
82
|
+
// so a plain file named "dist" is not treated like directory "dist/".
|
|
83
|
+
let underSource;
|
|
84
|
+
if (isRecursivePrefix || raw.includes('**/')) {
|
|
85
|
+
underSource = `^${body}/.+$`;
|
|
86
|
+
}
|
|
87
|
+
else if (basenameOnly) {
|
|
88
|
+
underSource = `(^|/)${body}/.+$`;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
underSource = `^${body}/.+$`;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
raw: pattern.trim(),
|
|
95
|
+
negated,
|
|
96
|
+
directoryOnly,
|
|
97
|
+
anchored,
|
|
98
|
+
regex: new RegExp(source),
|
|
99
|
+
underRegex: directoryOnly ? new RegExp(underSource) : null,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function normalizeRelativePath(relativePath) {
|
|
103
|
+
return relativePath.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/, '');
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Create a matcher from a list of gitignore-style patterns.
|
|
107
|
+
*/
|
|
108
|
+
export function createIgnoreMatcher(patterns = []) {
|
|
109
|
+
const rules = [];
|
|
110
|
+
const matcher = {
|
|
111
|
+
add(input) {
|
|
112
|
+
const list = Array.isArray(input) ? input : [input];
|
|
113
|
+
for (const pattern of list) {
|
|
114
|
+
const compiled = compilePattern(pattern);
|
|
115
|
+
if (compiled) {
|
|
116
|
+
rules.push(compiled);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return matcher;
|
|
120
|
+
},
|
|
121
|
+
ignores(relativePath, isDirectory = false) {
|
|
122
|
+
const normalized = normalizeRelativePath(relativePath);
|
|
123
|
+
if (!normalized || normalized === '.') {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
// Trailing slash is a directory hint (gitignore-style)
|
|
127
|
+
const asDirectory = isDirectory || relativePath.replace(/\\/g, '/').endsWith('/');
|
|
128
|
+
let ignored = false;
|
|
129
|
+
for (const rule of rules) {
|
|
130
|
+
if (!rule.regex.test(normalized)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (rule.directoryOnly && !asDirectory) {
|
|
134
|
+
// Plain file: only ignore if it lives under the directory, not if it
|
|
135
|
+
// merely shares the directory's name (e.g. file "dist" vs dir "dist/").
|
|
136
|
+
if (!rule.underRegex || !rule.underRegex.test(normalized)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
ignored = !rule.negated;
|
|
141
|
+
}
|
|
142
|
+
return ignored;
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
if (patterns.length > 0) {
|
|
146
|
+
matcher.add(patterns);
|
|
147
|
+
}
|
|
148
|
+
return matcher;
|
|
149
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { createIgnoreMatcher } from './ignore-matcher.js';
|
|
2
|
+
export type { IgnoreMatcher } from './ignore-matcher.js';
|
|
3
|
+
export { mergeGitignoreRules, buildIgnoreMatcher, syncProjectGitignore, isPathIgnored, } from './gitignore.js';
|
|
4
|
+
export { loadCanonicalGitignore, getCanonicalGitignorePath, writeCanonicalGitignore } from './canonical-gitignore.js';
|
|
5
|
+
export { getExclusionReport, HARD_EXCLUDED_PATTERNS, ENV_EXCLUDED_PATTERNS, EXCLUSION_CATEGORIES, } from './exclusions.js';
|
|
6
|
+
export { collectFilesToPack, createOptimizedZip } from './zipper.js';
|
|
7
|
+
export { formatPackTree } from './tree.js';
|
|
8
|
+
export { packProject } from './pack-project.js';
|
|
9
|
+
export { validateNitrostackProject, PackValidationError } from './validate-project.js';
|
|
10
|
+
export type { PackOptions, PackResult, ExclusionCategory, NitrostackProjectInfo, GitignoreMergeResult, } from './types.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pack/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,GACd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACtH,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACvF,YAAY,EACV,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createIgnoreMatcher } from './ignore-matcher.js';
|
|
2
|
+
export { mergeGitignoreRules, buildIgnoreMatcher, syncProjectGitignore, isPathIgnored, } from './gitignore.js';
|
|
3
|
+
export { loadCanonicalGitignore, getCanonicalGitignorePath, writeCanonicalGitignore } from './canonical-gitignore.js';
|
|
4
|
+
export { getExclusionReport, HARD_EXCLUDED_PATTERNS, ENV_EXCLUDED_PATTERNS, EXCLUSION_CATEGORIES, } from './exclusions.js';
|
|
5
|
+
export { collectFilesToPack, createOptimizedZip } from './zipper.js';
|
|
6
|
+
export { formatPackTree } from './tree.js';
|
|
7
|
+
export { packProject } from './pack-project.js';
|
|
8
|
+
export { validateNitrostackProject, PackValidationError } from './validate-project.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pack-project.d.ts","sourceRoot":"","sources":["../../src/pack/pack-project.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAqB1D;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CA2ChF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { getExclusionReport } from './exclusions.js';
|
|
3
|
+
import { buildIgnoreMatcher, syncProjectGitignore } from './gitignore.js';
|
|
4
|
+
import { formatPackTree } from './tree.js';
|
|
5
|
+
import { validateNitrostackProject } from './validate-project.js';
|
|
6
|
+
import { collectFilesToPack, createOptimizedZip, getZipSizeBytes } from './zipper.js';
|
|
7
|
+
function sanitizeZipName(name) {
|
|
8
|
+
return name.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'nitrostack-project';
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Always place the zip in the project root.
|
|
12
|
+
* If --output is provided, only the filename is used (directories / absolute paths are ignored).
|
|
13
|
+
*/
|
|
14
|
+
function resolveOutputPath(projectRoot, projectName, output) {
|
|
15
|
+
const rawName = output
|
|
16
|
+
? path.basename(output).replace(/\.zip$/i, '') || projectName
|
|
17
|
+
: projectName;
|
|
18
|
+
const fileName = sanitizeZipName(rawName);
|
|
19
|
+
return path.join(projectRoot, `${fileName}.zip`);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Pack a NitroStack project into an optimized zip archive.
|
|
23
|
+
*/
|
|
24
|
+
export async function packProject(options = {}) {
|
|
25
|
+
const cwd = options.cwd ?? process.cwd();
|
|
26
|
+
const syncGitignore = options.syncGitignore ?? true;
|
|
27
|
+
const dryRun = options.dryRun ?? false;
|
|
28
|
+
const includeEnv = options.includeEnv ?? false;
|
|
29
|
+
const project = await validateNitrostackProject(cwd);
|
|
30
|
+
const mergeResult = await syncProjectGitignore(project.projectRoot, syncGitignore);
|
|
31
|
+
const matcher = buildIgnoreMatcher(mergeResult.mergedContent, { includeEnv });
|
|
32
|
+
const outputPath = resolveOutputPath(project.projectRoot, project.projectName, options.output);
|
|
33
|
+
if (dryRun) {
|
|
34
|
+
const collection = await collectFilesToPack(project.projectRoot, matcher);
|
|
35
|
+
return {
|
|
36
|
+
outputPath: null,
|
|
37
|
+
projectName: project.projectName,
|
|
38
|
+
filesIncluded: collection.filesIncluded,
|
|
39
|
+
zipSizeBytes: null,
|
|
40
|
+
gitignoreUpdated: mergeResult.updated,
|
|
41
|
+
addedGitignoreRules: mergeResult.addedRules,
|
|
42
|
+
excludedCategories: getExclusionReport(includeEnv),
|
|
43
|
+
includedPaths: collection.includedPaths,
|
|
44
|
+
excludedPaths: collection.excludedPaths,
|
|
45
|
+
dryRunTree: formatPackTree(project.projectName, collection.includedPaths, collection.excludedPaths),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const collection = await createOptimizedZip(project.projectRoot, outputPath, matcher);
|
|
49
|
+
const zipSizeBytes = await getZipSizeBytes(outputPath);
|
|
50
|
+
return {
|
|
51
|
+
outputPath,
|
|
52
|
+
projectName: project.projectName,
|
|
53
|
+
filesIncluded: collection.filesIncluded,
|
|
54
|
+
zipSizeBytes,
|
|
55
|
+
gitignoreUpdated: mergeResult.updated,
|
|
56
|
+
addedGitignoreRules: mergeResult.addedRules,
|
|
57
|
+
excludedCategories: getExclusionReport(includeEnv),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../../src/pack/standalone.ts"],"names":[],"mappings":";AAuDA,wBAAsB,iBAAiB,CAAC,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCpF"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { realpathSync } from 'fs';
|
|
7
|
+
import { packProject } from './pack-project.js';
|
|
8
|
+
import { PackValidationError } from './validate-project.js';
|
|
9
|
+
function formatBytes(bytes) {
|
|
10
|
+
if (bytes < 1024)
|
|
11
|
+
return `${bytes} B`;
|
|
12
|
+
if (bytes < 1024 * 1024)
|
|
13
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
14
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
15
|
+
}
|
|
16
|
+
function printExclusionReport(result) {
|
|
17
|
+
console.log(chalk.bold('\nExcluded from zip:'));
|
|
18
|
+
for (const category of result.excludedCategories) {
|
|
19
|
+
console.log(` ${chalk.cyan(category.category)}: ${category.paths.join(', ')}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function printDryRunTree(result) {
|
|
23
|
+
if (!result.dryRunTree)
|
|
24
|
+
return;
|
|
25
|
+
console.log(chalk.bold('\nFile tree:'));
|
|
26
|
+
console.log(result.dryRunTree);
|
|
27
|
+
}
|
|
28
|
+
function printPackSummary(result, dryRun) {
|
|
29
|
+
if (result.gitignoreUpdated) {
|
|
30
|
+
console.log(chalk.green(`Updated .gitignore with ${result.addedGitignoreRules.length} rule(s).`));
|
|
31
|
+
}
|
|
32
|
+
printExclusionReport(result);
|
|
33
|
+
if (dryRun) {
|
|
34
|
+
printDryRunTree(result);
|
|
35
|
+
}
|
|
36
|
+
console.log(chalk.bold('\nPack summary:'));
|
|
37
|
+
console.log(` Project: ${result.projectName}`);
|
|
38
|
+
console.log(` Files included: ${result.filesIncluded}`);
|
|
39
|
+
if (result.outputPath) {
|
|
40
|
+
console.log(` Output: ${result.outputPath}`);
|
|
41
|
+
}
|
|
42
|
+
if (result.zipSizeBytes !== null) {
|
|
43
|
+
console.log(` Zip size: ${formatBytes(result.zipSizeBytes)}`);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.log(' Dry run: zip file was not created.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function runStandalonePack(argv = process.argv) {
|
|
50
|
+
const program = new Command();
|
|
51
|
+
program
|
|
52
|
+
.name('nitrostack-pack')
|
|
53
|
+
.description('Create an optimized zip of a NitroStack project')
|
|
54
|
+
.option('-o, --output <path>', 'Zip filename (always written to the project root)')
|
|
55
|
+
.option('--dry-run', 'Show excluded/included summary without creating zip')
|
|
56
|
+
.option('--include-env', 'Include .env files in the zip')
|
|
57
|
+
.option('--no-sync-gitignore', 'Skip merging canonical rules into local .gitignore')
|
|
58
|
+
.option('--cwd <dir>', 'Project directory', process.cwd())
|
|
59
|
+
.action(async (options) => {
|
|
60
|
+
const packOptions = {
|
|
61
|
+
output: options.output,
|
|
62
|
+
dryRun: options.dryRun,
|
|
63
|
+
includeEnv: options.includeEnv ?? false,
|
|
64
|
+
syncGitignore: options.syncGitignore,
|
|
65
|
+
cwd: path.resolve(options.cwd),
|
|
66
|
+
};
|
|
67
|
+
try {
|
|
68
|
+
const result = await packProject(packOptions);
|
|
69
|
+
printPackSummary(result, Boolean(packOptions.dryRun));
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof PackValidationError) {
|
|
73
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
74
|
+
process.exitCode = 1;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
await program.parseAsync(argv);
|
|
81
|
+
}
|
|
82
|
+
function isMainModule() {
|
|
83
|
+
try {
|
|
84
|
+
const argvPath = realpathSync(process.argv[1]);
|
|
85
|
+
const modulePath = realpathSync(fileURLToPath(import.meta.url));
|
|
86
|
+
return argvPath === modulePath;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return process.argv[1]?.includes('standalone.js')
|
|
90
|
+
|| process.argv[1]?.endsWith('nitrostack-pack');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (isMainModule()) {
|
|
94
|
+
void runStandalonePack();
|
|
95
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/pack/tree.ts"],"names":[],"mappings":"AAgEA;;GAEG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EAAE,EACvB,aAAa,EAAE,MAAM,EAAE,GACtB,MAAM,CAwBR"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
function ensureChild(parent, name, status) {
|
|
2
|
+
const existing = parent.children.get(name);
|
|
3
|
+
if (existing) {
|
|
4
|
+
// Prefer leaf status over container when the same name appears
|
|
5
|
+
if (existing.status === 'container' && status !== 'container') {
|
|
6
|
+
existing.status = status;
|
|
7
|
+
}
|
|
8
|
+
return existing;
|
|
9
|
+
}
|
|
10
|
+
const node = {
|
|
11
|
+
name,
|
|
12
|
+
status,
|
|
13
|
+
children: new Map(),
|
|
14
|
+
};
|
|
15
|
+
parent.children.set(name, node);
|
|
16
|
+
return node;
|
|
17
|
+
}
|
|
18
|
+
function insertPath(root, relativePath, status) {
|
|
19
|
+
const normalized = relativePath.replace(/\/+$/, '');
|
|
20
|
+
if (!normalized)
|
|
21
|
+
return;
|
|
22
|
+
const parts = normalized.split('/').filter(Boolean);
|
|
23
|
+
let current = root;
|
|
24
|
+
for (let i = 0; i < parts.length; i++) {
|
|
25
|
+
const isLeaf = i === parts.length - 1;
|
|
26
|
+
const partName = isLeaf && relativePath.endsWith('/') ? `${parts[i]}/` : parts[i];
|
|
27
|
+
const nodeStatus = isLeaf ? status : 'container';
|
|
28
|
+
current = ensureChild(current, partName, nodeStatus);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function statusSymbol(status) {
|
|
32
|
+
if (status === 'included')
|
|
33
|
+
return '✅';
|
|
34
|
+
if (status === 'excluded')
|
|
35
|
+
return '❌';
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
function renderNode(node, prefix, isLast, lines) {
|
|
39
|
+
const branch = isLast ? '└── ' : '├── ';
|
|
40
|
+
const symbol = statusSymbol(node.status);
|
|
41
|
+
const label = symbol ? `${node.name.padEnd(32)} ${symbol}` : node.name;
|
|
42
|
+
lines.push(`${prefix}${branch}${label}`);
|
|
43
|
+
const children = Array.from(node.children.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
44
|
+
const childPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
45
|
+
children.forEach((child, index) => {
|
|
46
|
+
renderNode(child, childPrefix, index === children.length - 1, lines);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build an ASCII tree with ✅ (included) and ❌ (excluded) markers.
|
|
51
|
+
*/
|
|
52
|
+
export function formatPackTree(projectName, includedPaths, excludedPaths) {
|
|
53
|
+
const root = {
|
|
54
|
+
name: `${projectName}/`,
|
|
55
|
+
status: 'container',
|
|
56
|
+
children: new Map(),
|
|
57
|
+
};
|
|
58
|
+
for (const included of includedPaths) {
|
|
59
|
+
insertPath(root, included, 'included');
|
|
60
|
+
}
|
|
61
|
+
for (const excluded of excludedPaths) {
|
|
62
|
+
insertPath(root, excluded, 'excluded');
|
|
63
|
+
}
|
|
64
|
+
const lines = [root.name];
|
|
65
|
+
const children = Array.from(root.children.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
66
|
+
children.forEach((child, index) => {
|
|
67
|
+
renderNode(child, '', index === children.length - 1, lines);
|
|
68
|
+
});
|
|
69
|
+
return lines.join('\n');
|
|
70
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface PackOptions {
|
|
2
|
+
output?: string;
|
|
3
|
+
dryRun?: boolean;
|
|
4
|
+
syncGitignore?: boolean;
|
|
5
|
+
includeEnv?: boolean;
|
|
6
|
+
cwd?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface PackResult {
|
|
9
|
+
outputPath: string | null;
|
|
10
|
+
projectName: string;
|
|
11
|
+
filesIncluded: number;
|
|
12
|
+
zipSizeBytes: number | null;
|
|
13
|
+
gitignoreUpdated: boolean;
|
|
14
|
+
addedGitignoreRules: string[];
|
|
15
|
+
excludedCategories: ExclusionCategory[];
|
|
16
|
+
includedPaths?: string[];
|
|
17
|
+
excludedPaths?: string[];
|
|
18
|
+
dryRunTree?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ExclusionCategory {
|
|
21
|
+
category: string;
|
|
22
|
+
paths: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface NitrostackProjectInfo {
|
|
25
|
+
projectRoot: string;
|
|
26
|
+
packageJsonPath: string;
|
|
27
|
+
projectName: string;
|
|
28
|
+
nitrostackPackages: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface GitignoreMergeResult {
|
|
31
|
+
mergedContent: string;
|
|
32
|
+
addedRules: string[];
|
|
33
|
+
updated: boolean;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/pack/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,kBAAkB,EAAE,iBAAiB,EAAE,CAAC;IACxC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NitrostackProjectInfo } from './types.js';
|
|
2
|
+
export declare class PackValidationError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Validate that the target directory is a NitroStack project.
|
|
7
|
+
*/
|
|
8
|
+
export declare function validateNitrostackProject(cwd?: string): Promise<NitrostackProjectInfo>;
|
|
9
|
+
//# sourceMappingURL=validate-project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-project.d.ts","sourceRoot":"","sources":["../../src/pack/validate-project.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAQxD,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAkBD;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,qBAAqB,CAAC,CAyBhC"}
|