@nitrostack/cli 1.0.14 → 1.0.16
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.map +1 -1
- package/dist/commands/init.js +8 -7
- 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/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -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/package.json +6 -3
- package/templates/typescript-oauth/.env.example +14 -1
- package/templates/typescript-oauth/README.md +20 -0
- package/templates/typescript-oauth/_gitignore +57 -0
- package/templates/typescript-pizzaz/.env.example +13 -1
- package/templates/typescript-pizzaz/README.md +19 -0
- package/templates/typescript-pizzaz/_gitignore +57 -0
- package/templates/typescript-starter/.env.example +13 -1
- package/templates/typescript-starter/README.md +19 -0
- package/templates/typescript-starter/_gitignore +57 -0
|
@@ -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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
export class PackValidationError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'PackValidationError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function collectNitrostackPackages(packageJson) {
|
|
10
|
+
const packages = new Set();
|
|
11
|
+
const sections = [packageJson.dependencies, packageJson.devDependencies];
|
|
12
|
+
for (const section of sections) {
|
|
13
|
+
if (!section)
|
|
14
|
+
continue;
|
|
15
|
+
for (const pkg of Object.keys(section)) {
|
|
16
|
+
if (pkg.startsWith('@nitrostack/')) {
|
|
17
|
+
packages.add(pkg);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return Array.from(packages).sort();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Validate that the target directory is a NitroStack project.
|
|
25
|
+
*/
|
|
26
|
+
export async function validateNitrostackProject(cwd = process.cwd()) {
|
|
27
|
+
const projectRoot = path.resolve(cwd);
|
|
28
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
29
|
+
if (!(await fs.pathExists(packageJsonPath))) {
|
|
30
|
+
throw new PackValidationError('package.json not found in the current directory');
|
|
31
|
+
}
|
|
32
|
+
const packageJson = await fs.readJSON(packageJsonPath);
|
|
33
|
+
const nitrostackPackages = collectNitrostackPackages(packageJson);
|
|
34
|
+
if (nitrostackPackages.length === 0) {
|
|
35
|
+
throw new PackValidationError('No @nitrostack/* dependencies found in package.json. Run this command from a NitroStack project.');
|
|
36
|
+
}
|
|
37
|
+
const projectName = packageJson.name || path.basename(projectRoot);
|
|
38
|
+
return {
|
|
39
|
+
projectRoot,
|
|
40
|
+
packageJsonPath,
|
|
41
|
+
projectName,
|
|
42
|
+
nitrostackPackages,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IgnoreMatcher } from './ignore-matcher.js';
|
|
2
|
+
export interface ZipCollectionResult {
|
|
3
|
+
filesIncluded: number;
|
|
4
|
+
includedPaths: string[];
|
|
5
|
+
excludedPaths: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Collect relative file paths that should be included, and pruned excluded roots.
|
|
9
|
+
* Excluded directories are recorded once and not descended into.
|
|
10
|
+
* Symlinks are resolved via stat/realpath so:
|
|
11
|
+
* - symlink-to-directory is walked (not archived as a file)
|
|
12
|
+
* - cycles and targets outside the project root are skipped
|
|
13
|
+
*/
|
|
14
|
+
export declare function collectFilesToPack(projectRoot: string, matcher: IgnoreMatcher): Promise<ZipCollectionResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Create an optimized zip archive from the project directory.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createOptimizedZip(projectRoot: string, outputPath: string, matcher: IgnoreMatcher): Promise<ZipCollectionResult>;
|
|
19
|
+
export declare function getZipSizeBytes(outputPath: string): Promise<number>;
|
|
20
|
+
//# sourceMappingURL=zipper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zipper.d.ts","sourceRoot":"","sources":["../../src/pack/zipper.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAQD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,mBAAmB,CAAC,CAsF9B;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,mBAAmB,CAAC,CAwB9B;AAED,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGzE"}
|