@nitrostack/cli 1.0.14 → 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.
Files changed (49) hide show
  1. package/assets/canonical.gitignore +57 -0
  2. package/dist/commands/init.d.ts.map +1 -1
  3. package/dist/commands/init.js +8 -7
  4. package/dist/commands/pack.d.ts +10 -0
  5. package/dist/commands/pack.d.ts.map +1 -0
  6. package/dist/commands/pack.js +82 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +12 -0
  10. package/dist/pack/canonical-gitignore.d.ts +14 -0
  11. package/dist/pack/canonical-gitignore.d.ts.map +1 -0
  12. package/dist/pack/canonical-gitignore.js +28 -0
  13. package/dist/pack/exclusions.d.ts +8 -0
  14. package/dist/pack/exclusions.d.ts.map +1 -0
  15. package/dist/pack/exclusions.js +84 -0
  16. package/dist/pack/gitignore.d.ts +21 -0
  17. package/dist/pack/gitignore.d.ts.map +1 -0
  18. package/dist/pack/gitignore.js +123 -0
  19. package/dist/pack/ignore-matcher.d.ts +19 -0
  20. package/dist/pack/ignore-matcher.d.ts.map +1 -0
  21. package/dist/pack/ignore-matcher.js +149 -0
  22. package/dist/pack/index.d.ts +11 -0
  23. package/dist/pack/index.d.ts.map +1 -0
  24. package/dist/pack/index.js +8 -0
  25. package/dist/pack/pack-project.d.ts +6 -0
  26. package/dist/pack/pack-project.d.ts.map +1 -0
  27. package/dist/pack/pack-project.js +59 -0
  28. package/dist/pack/standalone.d.ts +3 -0
  29. package/dist/pack/standalone.d.ts.map +1 -0
  30. package/dist/pack/standalone.js +95 -0
  31. package/dist/pack/tree.d.ts +5 -0
  32. package/dist/pack/tree.d.ts.map +1 -0
  33. package/dist/pack/tree.js +70 -0
  34. package/dist/pack/types.d.ts +35 -0
  35. package/dist/pack/types.d.ts.map +1 -0
  36. package/dist/pack/types.js +1 -0
  37. package/dist/pack/validate-project.d.ts +9 -0
  38. package/dist/pack/validate-project.d.ts.map +1 -0
  39. package/dist/pack/validate-project.js +44 -0
  40. package/dist/pack/zipper.d.ts +20 -0
  41. package/dist/pack/zipper.d.ts.map +1 -0
  42. package/dist/pack/zipper.js +121 -0
  43. package/package.json +6 -3
  44. package/templates/typescript-oauth/.env.example +1 -1
  45. package/templates/typescript-oauth/_gitignore +57 -0
  46. package/templates/typescript-pizzaz/.env.example +1 -1
  47. package/templates/typescript-pizzaz/_gitignore +57 -0
  48. package/templates/typescript-starter/.env.example +1 -1
  49. package/templates/typescript-starter/_gitignore +57 -0
@@ -0,0 +1,57 @@
1
+ # Dependencies
2
+ node_modules/
3
+ src/widgets/node_modules/
4
+
5
+ # Build outputs
6
+ dist/
7
+ src/widgets/.next/
8
+ src/widgets/out/
9
+
10
+ # Environment files
11
+ .env
12
+ .env.local
13
+ .env.*.local
14
+
15
+ # IDE
16
+ .idea/
17
+ .vscode/
18
+ *.swp
19
+ *.swo
20
+ *~
21
+
22
+ # OS files
23
+ .DS_Store
24
+ Thumbs.db
25
+
26
+ # Logs
27
+ *.log
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+
32
+ # Runtime data
33
+ pids/
34
+ *.pid
35
+ *.seed
36
+ *.pid.lock
37
+
38
+ # Coverage
39
+ coverage/
40
+ .nyc_output/
41
+
42
+ # Uploads
43
+ uploads/
44
+
45
+ # TypeScript cache
46
+ *.tsbuildinfo
47
+
48
+ # Optional npm cache
49
+ .npm/
50
+
51
+ # Optional eslint cache
52
+ .eslintcache
53
+
54
+ # OAuth tokens/secrets (never commit these!)
55
+ *.pem
56
+ *.key
57
+ tokens.json
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA4IA,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,WAAW,iBAmPtF"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA6IA,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,WAAW,iBA2PtF"}
@@ -8,6 +8,7 @@ import { execSync } from 'child_process';
8
8
  import { NITRO_BANNER_FULL, createSuccessBox, NitroSpinner, log, spacer, nextSteps, brand, showFooter } from '../ui/branding.js';
9
9
  import { trackEvent, shutdownAnalytics } from '../analytics/posthog.js';
10
10
  import { runSkillsFlow } from '../skills/index.js';
11
+ import { writeCanonicalGitignore } from '../pack/canonical-gitignore.js';
11
12
  import readline from 'readline';
12
13
  /**
13
14
  * Prompts the user with a horizontal YES/NO choice using left/right arrow keys.
@@ -201,6 +202,11 @@ export async function initCommand(projectName, options) {
201
202
  // Copy template files
202
203
  if (fs.existsSync(templateDir)) {
203
204
  fs.copySync(templateDir, targetDir);
205
+ // Template ships `_gitignore` (npm never packs files named `.gitignore`)
206
+ const placeholder = path.join(targetDir, '_gitignore');
207
+ if (fs.existsSync(placeholder)) {
208
+ fs.removeSync(placeholder);
209
+ }
204
210
  }
205
211
  else {
206
212
  await createProjectFromScratch(targetDir, finalProjectName, {
@@ -209,6 +215,8 @@ export async function initCommand(projectName, options) {
209
215
  author: finalAuthor,
210
216
  });
211
217
  }
218
+ // Always write canonical .gitignore — npm strips `.gitignore` from published templates
219
+ await writeCanonicalGitignore(targetDir);
212
220
  // Update package.json
213
221
  const packageJsonPath = path.join(targetDir, 'package.json');
214
222
  if (fs.existsSync(packageJsonPath)) {
@@ -395,13 +403,6 @@ server.start().catch((error) => {
395
403
  `;
396
404
  fs.writeFileSync(path.join(targetDir, 'src', 'index.ts'), indexTs);
397
405
  fs.writeFileSync(path.join(targetDir, '.env'), '# Environment variables\n');
398
- const gitignore = `node_modules/
399
- dist/
400
- .env
401
- .env.local
402
- *.log
403
- `;
404
- fs.writeFileSync(path.join(targetDir, '.gitignore'), gitignore);
405
406
  const readme = `# ${projectName}
406
407
 
407
408
  ${answers.description}
@@ -0,0 +1,10 @@
1
+ interface PackCommandOptions {
2
+ output?: string;
3
+ dryRun?: boolean;
4
+ syncGitignore?: boolean;
5
+ includeEnv?: boolean;
6
+ cwd?: string;
7
+ }
8
+ export declare function packCommand(options: PackCommandOptions): Promise<void>;
9
+ export {};
10
+ //# sourceMappingURL=pack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../src/commands/pack.ts"],"names":[],"mappings":"AAiBA,UAAU,kBAAkB;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;AA2CD,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2D5E"}
@@ -0,0 +1,82 @@
1
+ import chalk from 'chalk';
2
+ import path from 'path';
3
+ import { createHeader, createSuccessBox, createErrorBox, NitroSpinner, spacer, brand, NITRO_BANNER_FULL, showFooter, } from '../ui/branding.js';
4
+ import { trackEvent, shutdownAnalytics } from '../analytics/posthog.js';
5
+ import { packProject } from '../pack/pack-project.js';
6
+ import { PackValidationError } from '../pack/validate-project.js';
7
+ function formatBytes(bytes) {
8
+ if (bytes < 1024)
9
+ return `${bytes} B`;
10
+ if (bytes < 1024 * 1024)
11
+ return `${(bytes / 1024).toFixed(1)} KB`;
12
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
13
+ }
14
+ function buildSummaryItems(result) {
15
+ const items = [
16
+ `${brand.skyBold(String(result.filesIncluded))} file(s) included`,
17
+ ];
18
+ if (result.outputPath) {
19
+ items.push(`Output: ${brand.sky(result.outputPath)}`);
20
+ }
21
+ if (result.zipSizeBytes !== null) {
22
+ items.push(`Size: ${brand.skyBold(formatBytes(result.zipSizeBytes))}`);
23
+ }
24
+ if (result.gitignoreUpdated) {
25
+ items.push(`Added ${result.addedGitignoreRules.length} .gitignore rule(s)`);
26
+ }
27
+ return items;
28
+ }
29
+ function printExcludedCategories(result) {
30
+ console.log(chalk.bold('\nExcluded from zip:'));
31
+ for (const category of result.excludedCategories) {
32
+ console.log(` ${chalk.cyan(category.category)}: ${category.paths.join(', ')}`);
33
+ }
34
+ spacer();
35
+ }
36
+ function printDryRunTree(result) {
37
+ if (!result.dryRunTree)
38
+ return;
39
+ console.log(chalk.bold('File tree:'));
40
+ console.log(result.dryRunTree);
41
+ spacer();
42
+ }
43
+ export async function packCommand(options) {
44
+ console.log(NITRO_BANNER_FULL);
45
+ console.log(createHeader('Pack', 'Create optimized project archive'));
46
+ trackEvent('cli_command_invoked', {
47
+ command: 'pack',
48
+ options: Object.keys(options).filter((key) => options[key] !== undefined),
49
+ });
50
+ const packOptions = {
51
+ output: options.output,
52
+ dryRun: options.dryRun,
53
+ syncGitignore: options.syncGitignore ?? true,
54
+ includeEnv: options.includeEnv ?? false,
55
+ cwd: path.resolve(options.cwd ?? process.cwd()),
56
+ };
57
+ const spinner = new NitroSpinner(packOptions.dryRun ? 'Analyzing project files...' : 'Creating optimized zip...').start();
58
+ try {
59
+ const result = await packProject(packOptions);
60
+ spinner.succeed(packOptions.dryRun
61
+ ? 'Dry run complete'
62
+ : `Created ${path.basename(result.outputPath ?? 'archive')}`);
63
+ printExcludedCategories(result);
64
+ if (packOptions.dryRun) {
65
+ printDryRunTree(result);
66
+ }
67
+ console.log(createSuccessBox(packOptions.dryRun ? 'Dry Run Complete' : 'Project Packed', buildSummaryItems(result)));
68
+ showFooter();
69
+ await shutdownAnalytics();
70
+ }
71
+ catch (error) {
72
+ spinner.fail('Pack failed');
73
+ if (error instanceof PackValidationError) {
74
+ console.log(createErrorBox('Pack Failed', error.message));
75
+ await shutdownAnalytics();
76
+ process.exit(1);
77
+ }
78
+ console.log(createErrorBox('Pack Failed', error instanceof Error ? error.message : String(error)));
79
+ await shutdownAnalytics();
80
+ process.exit(1);
81
+ }
82
+ }
package/dist/index.d.ts CHANGED
@@ -9,4 +9,6 @@ export { generate } from './commands/generate.js';
9
9
  export { upgradeCommand } from './commands/upgrade.js';
10
10
  export { installCommand } from './commands/install.js';
11
11
  export { cursorCommand } from './commands/cursor.js';
12
+ export { packCommand } from './commands/pack.js';
13
+ export { packProject } from './pack/pack-project.js';
12
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAcpC,wBAAgB,aAAa,YA6E5B;AAGD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAepC,wBAAgB,aAAa,YAuF5B;AAGD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { generate } from './commands/generate.js';
9
9
  import { upgradeCommand } from './commands/upgrade.js';
10
10
  import { installCommand } from './commands/install.js';
11
11
  import { cursorCommand } from './commands/cursor.js';
12
+ import { packCommand } from './commands/pack.js';
12
13
  const require = createRequire(import.meta.url);
13
14
  const packageJson = require('../package.json');
14
15
  export function createProgram() {
@@ -77,6 +78,15 @@ export function createProgram() {
77
78
  .option('-p, --port <port>', 'Port for default HTTP URL (for legacy-sse or streamable-http)')
78
79
  .option('--force', 'Force overwrite of existing configuration')
79
80
  .action(cursorCommand);
81
+ program
82
+ .command('pack')
83
+ .description('Create an optimized zip of the project (excludes build artifacts)')
84
+ .option('-o, --output <path>', 'Zip filename (always written to the project root)')
85
+ .option('--dry-run', 'Show excluded/included summary without creating zip')
86
+ .option('--include-env', 'Include .env files in the zip')
87
+ .option('--no-sync-gitignore', 'Skip merging canonical rules into local .gitignore')
88
+ .option('--cwd <dir>', 'Project directory', process.cwd())
89
+ .action(packCommand);
80
90
  return program;
81
91
  }
82
92
  // Re-export commands for programmatic use
@@ -88,6 +98,8 @@ export { generate } from './commands/generate.js';
88
98
  export { upgradeCommand } from './commands/upgrade.js';
89
99
  export { installCommand } from './commands/install.js';
90
100
  export { cursorCommand } from './commands/cursor.js';
101
+ export { packCommand } from './commands/pack.js';
102
+ export { packProject } from './pack/pack-project.js';
91
103
  // Run the CLI when this module is the entry point
92
104
  import { fileURLToPath } from 'url';
93
105
  import { realpathSync } from 'fs';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Resolve the bundled canonical .gitignore shipped with the CLI package.
3
+ */
4
+ export declare function getCanonicalGitignorePath(): string;
5
+ /**
6
+ * Load canonical .gitignore rules from the bundled starter template copy.
7
+ */
8
+ export declare function loadCanonicalGitignore(): Promise<string>;
9
+ /**
10
+ * Write the bundled canonical .gitignore into a project root as `.gitignore`.
11
+ * Used by init because npm never ships files named `.gitignore` in the package tarball.
12
+ */
13
+ export declare function writeCanonicalGitignore(projectRoot: string): Promise<void>;
14
+ //# sourceMappingURL=canonical-gitignore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical-gitignore.d.ts","sourceRoot":"","sources":["../../src/pack/canonical-gitignore.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED;;GAEG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC,CAM9D;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGhF"}
@@ -0,0 +1,28 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import fs from 'fs-extra';
4
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
5
+ /**
6
+ * Resolve the bundled canonical .gitignore shipped with the CLI package.
7
+ */
8
+ export function getCanonicalGitignorePath() {
9
+ return path.join(moduleDir, '../../assets/canonical.gitignore');
10
+ }
11
+ /**
12
+ * Load canonical .gitignore rules from the bundled starter template copy.
13
+ */
14
+ export async function loadCanonicalGitignore() {
15
+ const canonicalPath = getCanonicalGitignorePath();
16
+ if (!(await fs.pathExists(canonicalPath))) {
17
+ throw new Error(`Canonical .gitignore not found at ${canonicalPath}`);
18
+ }
19
+ return fs.readFile(canonicalPath, 'utf-8');
20
+ }
21
+ /**
22
+ * Write the bundled canonical .gitignore into a project root as `.gitignore`.
23
+ * Used by init because npm never ships files named `.gitignore` in the package tarball.
24
+ */
25
+ export async function writeCanonicalGitignore(projectRoot) {
26
+ const content = await loadCanonicalGitignore();
27
+ await fs.writeFile(path.join(projectRoot, '.gitignore'), content, 'utf-8');
28
+ }
@@ -0,0 +1,8 @@
1
+ import type { ExclusionCategory } from './types.js';
2
+ /** Env/secret patterns excluded by default; skipped when --include-env is set. */
3
+ export declare const ENV_EXCLUDED_PATTERNS: string[];
4
+ /** Hard-coded exclusions always applied in addition to merged .gitignore rules. */
5
+ export declare const HARD_EXCLUDED_PATTERNS: string[];
6
+ export declare const EXCLUSION_CATEGORIES: ExclusionCategory[];
7
+ export declare function getExclusionReport(includeEnv?: boolean): ExclusionCategory[];
8
+ //# sourceMappingURL=exclusions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exclusions.d.ts","sourceRoot":"","sources":["../../src/pack/exclusions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,EAAE,MAAM,EAIzC,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,sBAAsB,EAAE,MAAM,EA0B1C,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,EAyCnD,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,UAAU,GAAE,OAAe,GAAG,iBAAiB,EAAE,CAOnF"}
@@ -0,0 +1,84 @@
1
+ /** Env/secret patterns excluded by default; skipped when --include-env is set. */
2
+ export const ENV_EXCLUDED_PATTERNS = [
3
+ '.env',
4
+ '.env.local',
5
+ '.env.*.local',
6
+ ];
7
+ /** Hard-coded exclusions always applied in addition to merged .gitignore rules. */
8
+ export const HARD_EXCLUDED_PATTERNS = [
9
+ '.git/',
10
+ '**/node_modules/',
11
+ '**/dist/',
12
+ '**/build/',
13
+ '**/out/',
14
+ '**/.next/',
15
+ '*.tsbuildinfo',
16
+ '.npm/',
17
+ '.eslintcache',
18
+ 'coverage/',
19
+ '.nyc_output/',
20
+ 'tmp/',
21
+ 'temp/',
22
+ 'pids/',
23
+ 'uploads/',
24
+ ...ENV_EXCLUDED_PATTERNS,
25
+ '.idea/',
26
+ '.vscode/',
27
+ '.DS_Store',
28
+ 'Thumbs.db',
29
+ '*.log',
30
+ 'npm-debug.log*',
31
+ 'yarn-debug.log*',
32
+ 'yarn-error.log*',
33
+ '*.zip',
34
+ ];
35
+ export const EXCLUSION_CATEGORIES = [
36
+ {
37
+ category: 'VCS',
38
+ paths: ['.git/'],
39
+ },
40
+ {
41
+ category: 'Dependencies',
42
+ paths: ['**/node_modules/'],
43
+ },
44
+ {
45
+ category: 'Build outputs',
46
+ paths: ['**/dist/', '**/build/', '**/out/', '**/.next/'],
47
+ },
48
+ {
49
+ category: 'Caches',
50
+ paths: ['*.tsbuildinfo', '.npm/', '.eslintcache'],
51
+ },
52
+ {
53
+ category: 'Test',
54
+ paths: ['coverage/', '.nyc_output/'],
55
+ },
56
+ {
57
+ category: 'Temp/runtime',
58
+ paths: ['tmp/', 'temp/', 'pids/', 'uploads/'],
59
+ },
60
+ {
61
+ category: 'Secrets',
62
+ paths: [...ENV_EXCLUDED_PATTERNS],
63
+ },
64
+ {
65
+ category: 'IDE/OS',
66
+ paths: ['.idea/', '.vscode/', '.DS_Store', 'Thumbs.db'],
67
+ },
68
+ {
69
+ category: 'Logs',
70
+ paths: ['*.log', 'npm-debug.log*', 'yarn-debug.log*', 'yarn-error.log*'],
71
+ },
72
+ {
73
+ category: 'Archives',
74
+ paths: ['*.zip'],
75
+ },
76
+ ];
77
+ export function getExclusionReport(includeEnv = false) {
78
+ return EXCLUSION_CATEGORIES
79
+ .filter((entry) => !(includeEnv && entry.category === 'Secrets'))
80
+ .map((entry) => ({
81
+ category: entry.category,
82
+ paths: [...entry.paths],
83
+ }));
84
+ }
@@ -0,0 +1,21 @@
1
+ import { type IgnoreMatcher } from './ignore-matcher.js';
2
+ import type { GitignoreMergeResult } from './types.js';
3
+ export interface BuildIgnoreMatcherOptions {
4
+ includeEnv?: boolean;
5
+ }
6
+ export type { IgnoreMatcher };
7
+ /**
8
+ * Non-destructively merge canonical rules into a local .gitignore.
9
+ * Existing local rules are preserved; only missing canonical rules are appended.
10
+ */
11
+ export declare function mergeGitignoreRules(localContent: string, canonicalContent: string): GitignoreMergeResult;
12
+ /**
13
+ * Sync local .gitignore with canonical rules when enabled.
14
+ */
15
+ export declare function syncProjectGitignore(projectRoot: string, syncGitignore: boolean): Promise<GitignoreMergeResult>;
16
+ /**
17
+ * Build a gitignore matcher from merged project rules and hard-coded exclusions.
18
+ */
19
+ export declare function buildIgnoreMatcher(mergedGitignoreContent: string, options?: BuildIgnoreMatcherOptions): IgnoreMatcher;
20
+ export declare function isPathIgnored(matcher: IgnoreMatcher, projectRoot: string, absolutePath: string, isDirectory?: boolean): boolean;
21
+ //# sourceMappingURL=gitignore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../../src/pack/gitignore.ts"],"names":[],"mappings":"AAIA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAIvD,MAAM,WAAW,yBAAyB;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,YAAY,EAAE,aAAa,EAAE,CAAC;AA2B9B;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,MAAM,GACvB,oBAAoB,CAuCtB;AAQD;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,OAAO,GACrB,OAAO,CAAC,oBAAoB,CAAC,CAoB/B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,sBAAsB,EAAE,MAAM,EAC9B,OAAO,GAAE,yBAA8B,GACtC,aAAa,CAyBf;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,aAAa,EACtB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,WAAW,GAAE,OAAe,GAC3B,OAAO,CAMT"}
@@ -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"}