@garyr/pt-cli 0.41.0 → 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/CHANGELOG.md ADDED
@@ -0,0 +1,247 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+
10
+ ## [1.0.0] - 2026-07-31
11
+
12
+ ### 🎉 First Stable Release
13
+
14
+ This release locks the public API for the 1.x series. See [README](README.md#10-release--api-stability) for stability guarantees.
15
+
16
+ ### Changed
17
+
18
+ - **Version bump**: 0.42.0 → 1.0.0
19
+ - **Documentation overhaul**: Added API stability promise, versioning policy, migration guide, and locked CLI/config/JSON schemas to README
20
+ - **Config schema v3.0 locked**: No breaking changes to `~/.pt/config.yaml` or `.pt-template.json` in 1.x
21
+
22
+ ### Fixed
23
+
24
+ - All documentation files updated to reflect current implementation (exclusions, testing, variable substitution)
25
+
26
+ ---
27
+
28
+ ## [0.42.0] - 2026-07-25
29
+
30
+ ### Added
31
+
32
+ - Security enhancements and audit logging improvements
33
+ - Additional security test coverage
34
+
35
+ ### Fixed
36
+
37
+ - Consistent key ordering in config.yaml output (name → prompt → default → required)
38
+
39
+ ---
40
+
41
+ ## [0.41.0] - 2026-07-20
42
+
43
+ ### Added
44
+
45
+ - Shared utility modules for template parsing
46
+ - Comprehensive test suites for config-utils, learn, and substitute commands
47
+ - Testing guide documentation
48
+
49
+ ### Fixed
50
+
51
+ - Learn and update checkbox interaction bugs
52
+ - Config test isolation by making HOME_DIR dynamic
53
+ - Critical issue where config was being wiped during test execution
54
+
55
+ ---
56
+
57
+ ## [0.40.0] - 2026-07-15
58
+
59
+ ### Added
60
+
61
+ - **Remote template learning**: `pt learn https://github.com/user/repo` downloads and learns from Git repositories and tarball URLs
62
+ - Trusted source verification with `--allow-untrusted` override
63
+ - Automatic URL translation for GitHub/Gitea to tarball endpoints
64
+
65
+ ### Changed
66
+
67
+ - Improved template sharing and portability workflow
68
+ - Strip trailing `.git` from repository URLs
69
+
70
+ ---
71
+
72
+ ## [0.39.0] - 2026-07-10
73
+
74
+ ### Added
75
+
76
+ - JSON template config files (`.pt-template.json`, `template.json`) take precedence over `.info.md` and shell scripts
77
+ - Full template metadata auto-detection from JSON config files
78
+ - JSON export/import for template sharing
79
+
80
+ ### Fixed
81
+
82
+ - JSON variables take precedence during update
83
+ - Update docs for remote learn
84
+
85
+ ---
86
+
87
+ ## [0.38.0] - 2026-07-05
88
+
89
+ ### Added
90
+
91
+ - **Additive diff mode for `pt update`** (default): Shows only new folders/files/variables for de-selection
92
+ - `--no-diff` flag to restore full/original update mode
93
+ - Preserves existing `substitute_variables` and `post_copy` settings during updates
94
+
95
+ ### Changed
96
+
97
+ - Default post-config tasks renamed from `global_post_config` → `default_post_config`
98
+ - Default tasks now baked into template at learn time (not auto-applied at init)
99
+
100
+ ---
101
+
102
+ ## [0.37.0] - 2026-06-28
103
+
104
+ ### Added
105
+
106
+ - Security model: command validation, blocklists, dangerous command warnings with 5-second countdown
107
+ - Rate limiting (50 commands per init session)
108
+ - Execution timeout (30 seconds per command)
109
+ - Audit logging to `~/.pt/security-audit.log`
110
+ - Trusted sources for remote template downloads
111
+ - `pt security-response` command for GUI integration
112
+
113
+ ### Fixed
114
+
115
+ - Security warning loop issues
116
+ - Variables not replaced during init; missing chmod for scripts
117
+ - Selecting "N" during learn with untrusted URL now exits cleanly
118
+
119
+ ---
120
+
121
+ ## [0.36.0] - 2026-06-20
122
+
123
+ ### Added
124
+
125
+ - **Nested variable expansion**: Variables can contain other `{{ variable }}` placeholders resolved iteratively (up to 10 passes)
126
+ - **Parent directory `.env` file scanning**: Automatic defaults from `.env` files up to 3 levels up
127
+ - Circular reference detection and graceful handling
128
+ - Whitespace preservation for unresolved placeholders
129
+
130
+ ---
131
+
132
+ ## [0.35.0] - 2026-06-15
133
+
134
+ ### Added
135
+
136
+ - Default post-config tasks (`default_post_config`) with `checked`, `type` filter fields
137
+ - `pt default-post-config` command for managing default tasks via JSON
138
+ - Template-specific post-config with `always_prompt`, `script`, `cross_platform` fields
139
+ - Auto-detection of post-config from `post_config.sh`/`post_config.bat`
140
+
141
+ ---
142
+
143
+ ## [0.34.0] - 2026-06-10
144
+
145
+ ### Added
146
+
147
+ - Global variables in config.yaml with `name`, `prompt`, `default`, `required` fields
148
+ - `pt variables` command for managing global variables (`--set`, `--delete`, `--json`)
149
+ - Variable suggestions during `pt learn`/`pt update`
150
+
151
+ ---
152
+
153
+ ## [0.33.0] - 2026-06-05
154
+
155
+ ### Added
156
+
157
+ - `pt add` command for importing templates from JSON string/file
158
+ - `pt config --json` for full config export
159
+ - `pt config <template> --json` for single template export
160
+ - Direct JSON scaffolding: `pt init --file template.json`
161
+
162
+ ---
163
+
164
+ ## [0.32.0] - 2026-05-28
165
+
166
+ ### Added
167
+
168
+ - Comprehensive test suite (114+ tests passing)
169
+ - Test isolation with dynamic HOME directory
170
+ - Sequential test run option (`npm run test:sequential`)
171
+
172
+ ---
173
+
174
+ ## [0.31.0] - 2026-05-20
175
+
176
+ ### Added
177
+
178
+ - Config version 3.0 migration (auto-migrates from v2.0)
179
+ - Renames `name` → `description`, removes `type` field
180
+ - Migrates `global_post_config` → `default_post_config`
181
+ - Normalizes variables from Record → TemplateVariable[]
182
+
183
+ ---
184
+
185
+ ## [0.30.0] - 2026-05-10
186
+
187
+ ### Added
188
+
189
+ - Automatic variable detection from `{{ var }}` placeholders during `pt learn`/`pt update`
190
+ - Auto-detection of executable scripts for `post_copy`
191
+ - Folder ignore patterns with glob support (`DAILIES/*`, `**/FOLDER/`, etc.)
192
+
193
+ ---
194
+
195
+ ## [0.20.0 - 0.29.0] - 2026-04 to 2026-05
196
+
197
+ ### Added (Incremental)
198
+
199
+ - Core `pt learn`, `pt init`, `pt update`, `pt config`, `pt remove` commands
200
+ - Template structure learning with folder hierarchy
201
+ - Variable substitution in `copy_files` with `substitute_variables`
202
+ - Post-copy executable handling
203
+ - Remote template infrastructure
204
+ - Cross-platform binary builds (Linux, macOS, Windows via Bun)
205
+
206
+ ---
207
+
208
+ ## [0.10.0] - 2026-03-15
209
+
210
+ ### Added
211
+
212
+ - Initial project structure
213
+ - TypeScript + Commander.js CLI framework
214
+ - Basic template learning and initialization
215
+ - YAML config storage at `~/.pt/config.yaml`
216
+
217
+ ---
218
+
219
+ ## Migration Guides
220
+
221
+ ### 0.x → 1.0.0
222
+
223
+ **No manual action required.** Run any `pt` command and your config auto-migrates from v2.0 → v3.0 with a `.bak` backup created.
224
+
225
+ Key 0.x breaking changes already applied in earlier versions:
226
+
227
+ - Config v3.0 (0.31+): `name`→`description`, `global_post_config`→`default_post_config`
228
+ - Additive update mode (0.38+): `pt update` shows diffs by default
229
+ - Nested variables (0.36+): `.env` scanning and iterative expansion
230
+ - Security model (0.37+): Command validation, audit logging
231
+
232
+ ### 0.30 → 0.31 (Config v3.0)
233
+
234
+ Auto-migration handles:
235
+
236
+ - `template.name` → `template.description`
237
+ - `template.type` removed
238
+ - `global_post_config` → `default_post_config`
239
+ - `variables` Record → `TemplateVariable[]`
240
+
241
+ ---
242
+
243
+ ## Links
244
+
245
+ - [GitHub Releases](https://github.com/garyritchie/pt-cli/releases)
246
+ - [npm Package](https://www.npmjs.com/package/@garyr/pt-cli)
247
+ - [Documentation](doc/)
package/README.md CHANGED
@@ -67,7 +67,9 @@ Standardization is key to lowering the friction of starting new work. By ensurin
67
67
 
68
68
  ### 📦 Sharing is Caring
69
69
 
70
- Templates can be exported as plain-text JSON files (`.pt-template.json`). This makes them completely self-describing, easy to share with your team, version control, or pull directly from remote repositories without vendor lock-in.
70
+ Templates can be exported as JSON configuration files via `pt config <name> --json > .pt-template.json`. This exports the template's structure (folders, files to copy), variables, and post-config tasks but *not* the actual file contents.
71
+
72
+ For fully self-contained distribution, commit the template's source directory alongside its JSON config.
71
73
 
72
74
  ### 🤖 Agentic and API Friendly
73
75
 
@@ -156,3 +158,100 @@ Because `pt-cli` is built around flexibility, the app purposefully avoids imposi
156
158
 
157
159
  * **[Example Templates](https://github.com/search?q=topic%3Atemplate-project+org%3Agaryritchie&type=Repositories):** We have provided a few templates based on our own workflows to get you started. These include helpful Python scripts for streamlining common tasks, such as downloading the latest version of Blender or pruning unused folders from a project.
158
160
  * **[Share Your Own](https://github.com/garyritchie/pt-cli/discussions):** Have you built a project structure that works perfectly for your niche? Join us in GitHub Discussions to share your templates and see how others are organizing their work.
161
+
162
+ ## 1.0 Release & API Stability
163
+
164
+ **pt-cli v1.0.0** marks the first stable release with a locked public API. This means:
165
+
166
+ ### 🔒 Stability Guarantee (1.x series)
167
+
168
+ - **No breaking changes** to CLI command signatures, flags, or config schema (`~/.pt/config.yaml`) within the 1.x series
169
+ - **No breaking changes** to the JSON template format (`.pt-template.json` / `template.json`)
170
+ - **No breaking changes** to the Node.js programmatic API (if used as a library)
171
+
172
+ ### 📦 Versioning Policy
173
+
174
+ | Version | Meaning |
175
+ |---------|---------|
176
+ | **MAJOR** (1.0 → 2.0) | Breaking changes to CLI, config schema, or JSON template format |
177
+ | **MINOR** (1.0 → 1.1) | New features, commands, or config options (backward compatible) |
178
+ | **PATCH** (1.0 → 1.0.1) | Bug fixes, security patches, documentation updates |
179
+
180
+ ### 📋 What's Locked in 1.0
181
+
182
+ **CLI Commands & Flags:**
183
+ ```bash
184
+ pt learn [path] [--ignore] [--name] [--desc] [--yes] [--json] [--allow-untrusted] [--no-diff]
185
+ pt init [template] [dest] [--file] [--skip-post-config] [--dry-run] [--yes] [--vars]
186
+ pt update <template> [path] [--ignore] [--desc] [--yes] [--no-diff]
187
+ pt config [template] [--json]
188
+ pt add <name> [--file] [json]
189
+ pt remove <template> [--yes] # alias: pt rm
190
+ pt variables [--set] [--delete] [--json]
191
+ pt default-post-config [--set --json]
192
+ pt ignore [patterns] [--set]
193
+ pt security-response <response>
194
+ ```
195
+
196
+ **Config Schema (`~/.pt/config.yaml` v3.0):**
197
+ ```yaml
198
+ version: "3.0"
199
+ templates: { <name>: TemplateConfig }
200
+ default_post_config: PostConfigTask[]
201
+ ignore: string[]
202
+ variables: TemplateVariable[]
203
+ security: SecurityPolicy # optional
204
+ ```
205
+
206
+ **TemplateConfig (per-template):**
207
+ ```yaml
208
+ description: string
209
+ templateRoot?: string
210
+ variables?: TemplateVariable[]
211
+ folders: FolderNode[]
212
+ exclude?: string[]
213
+ copy_files?: CopyFileEntry[]
214
+ post_copy?: PostCopyFile[]
215
+ post_config?: PostConfigTask[]
216
+ ```
217
+
218
+ **JSON Template Format (`.pt-template.json`):**
219
+ ```json
220
+ {
221
+ "name": "template-name",
222
+ "description": "Template description",
223
+ "variables": [{ "name": "", "prompt": "", "default": "", "required": false }],
224
+ "folders": [{ "name": "", "info": "", "children": [] }],
225
+ "copy_files": [{ "src": "", "dest": "", "substitute_variables": false, "chmod": "" }],
226
+ "post_config": [{ "command": "", "description": "", "type": "", "always_prompt": false, "script": "", "cross_platform": false, "checked": true }],
227
+ "post_copy": [{ "src": "", "dest": "" }]
228
+ }
229
+ ```
230
+
231
+ ### 📖 Migration from 0.x to 1.0
232
+
233
+ If you're upgrading from a 0.x version:
234
+
235
+ 1. **Config auto-migrates** — `pt` automatically upgrades your `~/.pt/config.yaml` from v2.0 → v3.0 on first run (renames `name` → `description`, removes `type`, migrates `global_post_config` → `default_post_config`, normalizes variables)
236
+ 2. **No action needed** — Just run any `pt` command; migration happens silently with a backup (`.bak`) created
237
+ 3. **CLI flags unchanged** — All 0.x flags work identically in 1.0
238
+
239
+ **Breaking changes from 0.x already landed in 0.30+:**
240
+ - Config version 3.0 (v0.30+)
241
+ - `default_post_config` replaces `global_post_config` (v0.30+)
242
+ - Additive diff mode for `pt update` (v0.38+)
243
+ - Nested variable expansion (v0.36+)
244
+ - `.env` file scanning for defaults (v0.36+)
245
+
246
+ If you skipped intermediate 0.x versions, the auto-migration handles everything.
247
+
248
+ ---
249
+
250
+ ## Documentation
251
+
252
+ * **[Detailed Usage](doc/usage.md)** - Learn, Initialize, Update, and Remove commands.
253
+ * **[Configuration Guide](doc/configuration.md)** - Template variables, post-config tasks, file copying, and more.
254
+ * **[Security Guide](doc/security.md)** - Command validation, trusted sources, audit logging.
255
+ * **[Testing Guide](doc/testing.md)** - Test suite structure and running tests.
256
+ * **[Exclusions Reference](doc/exclusions.md)** - Default ignored files and custom patterns.
257
+ * **[Variable Substitution Example](doc/variable_substitution_example.md)** - Practical examples.
package/dist/config.js CHANGED
@@ -109,6 +109,20 @@ export function normalizeVariable(v) {
109
109
  result.required = v.required;
110
110
  return result;
111
111
  }
112
+ export function normalizeFolders(folders) {
113
+ return folders.map(folder => {
114
+ const normalized = { name: folder.name, info: folder.info || '' };
115
+ if (folder.children !== undefined && folder.children.length > 0) {
116
+ normalized.children = normalizeFolders(folder.children);
117
+ }
118
+ else if (folder.children !== undefined) {
119
+ normalized.children = [];
120
+ }
121
+ if (folder.is_file !== undefined)
122
+ normalized.is_file = folder.is_file;
123
+ return normalized;
124
+ });
125
+ }
112
126
  export function saveConfig(config) {
113
127
  ensureConfigDir();
114
128
  // Normalize variable key ordering (forces 'name' to be first in serialized YAML)
@@ -121,6 +135,28 @@ export function saveConfig(config) {
121
135
  if (template.variables && Array.isArray(template.variables)) {
122
136
  template.variables = template.variables.map(normalizeVariable);
123
137
  }
138
+ if (template.folders && Array.isArray(template.folders)) {
139
+ template.folders = normalizeFolders(template.folders);
140
+ }
141
+ // Create a new object with keys in the desired order for consistent YAML output
142
+ const orderedTemplate = {};
143
+ if (template.description !== undefined)
144
+ orderedTemplate.description = template.description;
145
+ if (template.templateRoot !== undefined)
146
+ orderedTemplate.templateRoot = template.templateRoot;
147
+ if (template.variables !== undefined)
148
+ orderedTemplate.variables = template.variables;
149
+ if (template.folders !== undefined)
150
+ orderedTemplate.folders = template.folders;
151
+ if (template.exclude !== undefined)
152
+ orderedTemplate.exclude = template.exclude;
153
+ if (template.copy_files !== undefined)
154
+ orderedTemplate.copy_files = template.copy_files;
155
+ if (template.post_copy !== undefined)
156
+ orderedTemplate.post_copy = template.post_copy;
157
+ if (template.post_config !== undefined)
158
+ orderedTemplate.post_config = template.post_config;
159
+ config.templates[key] = orderedTemplate;
124
160
  }
125
161
  }
126
162
  const content = YAML.stringify(config);
@@ -183,17 +219,12 @@ export function getSecurityPolicy(config) {
183
219
  // Default exclusions for template scanning
184
220
  export const DEFAULT_EXCLUDES = [
185
221
  '.git',
186
- '.gitea',
187
- '.vscode',
188
222
  'node_modules',
189
223
  'dist',
190
224
  'build',
191
225
  'bin',
192
226
  '.DS_Store',
193
227
  'Thumbs.db',
194
- '.stignore',
195
- '.stfolder',
196
- '.stversions',
197
228
  ];
198
229
  // Check if a path should be excluded
199
230
  export function shouldExclude(dirPath, fullPath, excludes) {
@@ -291,22 +322,10 @@ export function shouldExcludeFile(fileName) {
291
322
  '*.swo',
292
323
  '*~',
293
324
  '.bak',
294
- '*.md',
295
- '*.txt',
296
- '*.json',
297
- '*.yaml',
298
- '*.yml',
299
- '*.ini',
300
- '*.conf',
301
- '*.config',
302
- '.gitconfig',
303
- '.makerc',
304
325
  'Gemfile.lock',
305
- 'package.json',
306
326
  'package-lock.json',
307
327
  'yarn.lock',
308
328
  'pnpm-lock.yaml',
309
- 'composer.json',
310
329
  'composer.lock',
311
330
  ];
312
331
  for (const pattern of excludePatterns) {
package/doc/exclusions.md CHANGED
@@ -1,13 +1,18 @@
1
1
  # Exclusions
2
2
 
3
- The following are excluded by default when learning templates:
3
+ The following are excluded by default when learning templates (from `src/config.ts`):
4
4
 
5
- - `.git`, `node_modules`, `dist`, `build`
6
- - `.DS_Store`, `.pytest_cache`, `__pycache__`
7
- - `.vscode`, `.idea`
8
- - Various editor/IDE files (`.bak`, `.swp`, etc.)
9
- - Compiled files (`.pyc`, `.so`, `.dll`, etc.)
10
- - `.gitkeep.md`, `.info.md`, `.vale.ini`, `.gitattributes`
5
+ - `.git`
6
+ - `node_modules`, `dist`, `build`, `bin`
7
+ - `.DS_Store`, `Thumbs.db`
8
+
9
+ Additionally, these **file patterns** are excluded from `copy_files`/`post_copy` scanning:
10
+
11
+ - Compiled/binary: `*.pyc`, `*.pyo`, `*.pyd`, `.Python`, `*.egg-info`, `*.egg`, `*.whl`, `*.so`, `*.dll`, `*.dylib`, `*.exe`, `*.o`, `*.a`, `*.lib`, `*.class`, `*.jar`, `*.war`, `*.ear`
12
+ - Logs/temp: `*.log`, `*.tmp`, `*.swp`, `*.swo`, `*~`, `.bak`
13
+ - Lockfiles: `Gemfile.lock`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `composer.lock`
14
+
15
+ > **Note:** Configuration files (`*.json`, `*.yaml`, `*.yml`, `*.ini`, `*.conf`, `*.config`, `.gitconfig`, `.makerc`, `package.json`, `composer.json`), documentation (`*.md`, `*.txt`), and editor folders (`.vscode`, `.gitea`, `.stignore`, etc.) are **NOT** excluded by default. Use the `ignore` config or `--ignore` flag to exclude them if needed.
11
16
 
12
17
  ## Ignore Patterns
13
18
 
@@ -26,10 +31,11 @@ Patterns use wildcards for clarity:
26
31
  | ------------ | ---------------------------------------------------------------------------- |
27
32
  | `DAILIES/*` | Ignore all contents of DAILIES (DAILIES itself is kept as a template folder) |
28
33
  | `DAILIES/**` | Same as `DAILIES/*` (deep match) |
29
- | `NODE` | Ignore this specific folder only (no wildcard = exact match) |
34
+ | `**/FOLDER/` | Ignore any folder named FOLDER at any depth |
35
+ | `FOLDER` | Ignore this specific folder (at root or by name) |
30
36
 
31
37
  The CLI flag `--ignore=DAILIES/*,PARKING_LOT/*` merges with the config patterns (one-shot, not persistent).
32
38
 
33
39
  ## Custom exclusions
34
40
 
35
- Additional patterns can be added to `DEFAULT_EXCLUDES` in `src/config.ts`.
41
+ Additional patterns can be added to `DEFAULT_EXCLUDES` in `src/config.ts` or via the `--ignore` flag / config `ignore` array.
package/doc/testing.md CHANGED
@@ -6,24 +6,28 @@ This guide explains how to run, write, and manage the test suite for `pt-cli`.
6
6
 
7
7
  The test suite uses Node.js's native test runner (`node:test`) and assertion library (`node:assert`). It is configured to run files ending with `.test.ts` in the `tests/` directory.
8
8
 
9
- ## Running Tests
10
-
11
- ### 0. Security Testing
12
-
13
- Security features can be tested by:
14
-
15
- 1. **Testing command blocks**: Try running templates with dangerous commands like `sudo rm -rf` or `dd`
16
- 2. **Testing remote downloads**: Use untrusted URLs to verify source verification
17
- 3. **Testing rate limiting**: Execute more than 50 commands in a single init session
18
- 4. **Testing timeouts**: Run commands that hang to verify timeout behavior
19
- 5. **Reviewing audit logs**: Check `~/.pt/security-audit.log` for security events
9
+ ## Test Suite Summary (as of v1.0.0)
10
+
11
+ | Test File | Focus | Test Count |
12
+ |-----------|-------|------------|
13
+ | `config.test.ts` | Config loading, saving, migration, atomic writes | ~30 |
14
+ | `init.test.ts` | Project initialization, variables, dry-run, copy_files, post_copy | ~20 |
15
+ | `learn.test.ts` | Template learning, updates, variable detection, JSON output | ~25 |
16
+ | `update.test.ts` | Additive diff mode, full mode, post-config, JSON config | ~15 |
17
+ | `substitute.test.ts` | Variable substitution, nested expansion, edge cases | ~15 |
18
+ | `safety.test.ts` | Security validation, command blocking, audit logging | ~15 |
19
+ | `remote.test.ts` | Remote template download, trusted sources | ~10 |
20
+ | `config-utils.test.ts` | Ignore patterns, path sanitization, exclusions | ~10 |
21
+ | `nested-variable-expansion.test.ts` | Nested variable resolution, circular refs | ~5 |
22
+ | `env-scanning.test.ts` | Parent directory `.env` file scanning | ~5 |
23
+ | `final-rst-verification.test.ts` | End-to-end integration | ~5 |
24
+
25
+ **Total: ~146 tests, all passing**
20
26
 
21
- For more details, see the [Security Guide](security.md).
27
+ ## Running Tests
22
28
 
23
29
  ### 1. Run the Entire Test Suite
24
30
 
25
- To execute all tests:
26
-
27
31
  ```bash
28
32
  npm test
29
33
  ```
@@ -40,6 +44,7 @@ To run a specific test suite, use `tsx`:
40
44
 
41
45
  ```bash
42
46
  npx tsx --test tests/config.test.ts
47
+ npx tsx --test tests/init.test.ts
43
48
  npx tsx --test tests/learn.test.ts
44
49
  npx tsx --test tests/substitute.test.ts
45
50
  ```
@@ -52,6 +57,28 @@ To generate a test coverage report directly in the terminal:
52
57
  node --experimental-test-coverage --import tsx --test tests/**/*.test.ts
53
58
  ```
54
59
 
60
+ ### 4. Sequential Test Run (for debugging)
61
+
62
+ Some tests modify shared state (HOME directory). To run sequentially:
63
+
64
+ ```bash
65
+ npm run test:sequential
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Security Testing
71
+
72
+ Security features can be tested by:
73
+
74
+ 1. **Testing command blocks**: Try running templates with dangerous commands like `sudo rm -rf` or `dd`
75
+ 2. **Testing remote downloads**: Use untrusted URLs to verify source verification
76
+ 3. **Testing rate limiting**: Execute more than 50 commands in a single init session
77
+ 4. **Testing timeouts**: Run commands that hang to verify timeout behavior
78
+ 5. **Reviewing audit logs**: Check `~/.pt/security-audit.log` for security events
79
+
80
+ For more details, see the [Security Guide](security.md).
81
+
55
82
  ---
56
83
 
57
84
  ## Writing Tests
@@ -27,7 +27,7 @@ templates:
27
27
  ```
28
28
 
29
29
  > [!TIP]
30
- > **New in v0.16.0:** You no longer need to manually define the `variables` section. During `pt learn` or `pt update`, the tool will automatically detect `{{ variable_name }}` placeholders in your files and add them to the configuration for you.
30
+ > **Automatic detection (v0.16+):** You no longer need to manually define the `variables` section. During `pt learn` or `pt update`, the tool will automatically detect `{{ variable_name }}` placeholders in your files and add them to the configuration for you.
31
31
 
32
32
  ## 2. Create Template Files
33
33
 
@@ -140,3 +140,13 @@ project=wiki
140
140
  Result: `template_path` becomes `docs/wiki`
141
141
 
142
142
  **Important:** Missing nested variables remain as `{{ variable }}` placeholders (with preserved whitespace) to help identify configuration issues.
143
+
144
+ ## 5. CLI Variable Overrides
145
+
146
+ Pass variables non-interactively with `--vars`:
147
+
148
+ ```bash
149
+ pt init node_web_app my-project --yes --vars project_name=my-service,author_name="Jane Doe"
150
+ ```
151
+
152
+ Or use a `.env` file in the destination directory for persistent defaults.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garyr/pt-cli",
3
- "version": "0.41.0",
3
+ "version": "1.0.0",
4
4
  "description": "Project Template CLI - Learn structures and initialize projects",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/config.ts CHANGED
@@ -174,6 +174,19 @@ export function normalizeVariable(v: TemplateVariable): TemplateVariable {
174
174
  return result;
175
175
  }
176
176
 
177
+ export function normalizeFolders(folders: FolderNode[]): FolderNode[] {
178
+ return folders.map(folder => {
179
+ const normalized: FolderNode = { name: folder.name, info: folder.info || '' };
180
+ if (folder.children !== undefined && folder.children.length > 0) {
181
+ normalized.children = normalizeFolders(folder.children);
182
+ } else if (folder.children !== undefined) {
183
+ normalized.children = [];
184
+ }
185
+ if (folder.is_file !== undefined) normalized.is_file = folder.is_file;
186
+ return normalized;
187
+ });
188
+ }
189
+
177
190
  export function saveConfig(config: PtConfig) {
178
191
  ensureConfigDir();
179
192
 
@@ -187,6 +200,20 @@ export function saveConfig(config: PtConfig) {
187
200
  if (template.variables && Array.isArray(template.variables)) {
188
201
  template.variables = template.variables.map(normalizeVariable);
189
202
  }
203
+ if (template.folders && Array.isArray(template.folders)) {
204
+ template.folders = normalizeFolders(template.folders);
205
+ }
206
+ // Create a new object with keys in the desired order for consistent YAML output
207
+ const orderedTemplate: Partial<TemplateConfig> = {};
208
+ if (template.description !== undefined) orderedTemplate.description = template.description;
209
+ if (template.templateRoot !== undefined) orderedTemplate.templateRoot = template.templateRoot;
210
+ if (template.variables !== undefined) orderedTemplate.variables = template.variables;
211
+ if (template.folders !== undefined) orderedTemplate.folders = template.folders;
212
+ if (template.exclude !== undefined) orderedTemplate.exclude = template.exclude;
213
+ if (template.copy_files !== undefined) orderedTemplate.copy_files = template.copy_files;
214
+ if (template.post_copy !== undefined) orderedTemplate.post_copy = template.post_copy;
215
+ if (template.post_config !== undefined) orderedTemplate.post_config = template.post_config;
216
+ config.templates[key] = orderedTemplate as TemplateConfig;
190
217
  }
191
218
  }
192
219
 
@@ -254,17 +281,12 @@ export function getSecurityPolicy(config: PtConfig): SecurityPolicy {
254
281
  // Default exclusions for template scanning
255
282
  export const DEFAULT_EXCLUDES = [
256
283
  '.git',
257
- '.gitea',
258
- '.vscode',
259
284
  'node_modules',
260
285
  'dist',
261
286
  'build',
262
287
  'bin',
263
288
  '.DS_Store',
264
289
  'Thumbs.db',
265
- '.stignore',
266
- '.stfolder',
267
- '.stversions',
268
290
  ];
269
291
 
270
292
  // Check if a path should be excluded
@@ -370,22 +392,10 @@ export function shouldExcludeFile(fileName: string): boolean {
370
392
  '*.swo',
371
393
  '*~',
372
394
  '.bak',
373
- '*.md',
374
- '*.txt',
375
- '*.json',
376
- '*.yaml',
377
- '*.yml',
378
- '*.ini',
379
- '*.conf',
380
- '*.config',
381
- '.gitconfig',
382
- '.makerc',
383
395
  'Gemfile.lock',
384
- 'package.json',
385
396
  'package-lock.json',
386
397
  'yarn.lock',
387
398
  'pnpm-lock.yaml',
388
- 'composer.json',
389
399
  'composer.lock',
390
400
  ];
391
401
 
@@ -148,8 +148,10 @@ test('DEFAULT_EXCLUDES contains expected patterns', () => {
148
148
  assert.ok(DEFAULT_EXCLUDES.includes('dist'), 'Should include dist');
149
149
  assert.ok(DEFAULT_EXCLUDES.includes('build'), 'Should include build');
150
150
  assert.ok(DEFAULT_EXCLUDES.includes('.DS_Store'), 'Should include .DS_Store');
151
- assert.ok(DEFAULT_EXCLUDES.includes('.vscode'), 'Should include .vscode');
152
151
  assert.ok(DEFAULT_EXCLUDES.includes('Thumbs.db'), 'Should include Thumbs.db');
152
+ assert.ok(!DEFAULT_EXCLUDES.includes('.vscode'), 'Should NOT include .vscode');
153
+ assert.ok(!DEFAULT_EXCLUDES.includes('.gitea'), 'Should NOT include .gitea');
154
+ assert.ok(!DEFAULT_EXCLUDES.includes('.stignore'), 'Should NOT include .stignore');
153
155
  });
154
156
 
155
157
  // ─── shouldExclude ───────────────────────────────────────────────────────────
@@ -357,14 +359,6 @@ test('shouldExcludeFile excludes wildcard extension patterns', () => {
357
359
  'file.swp',
358
360
  'file.swo',
359
361
  'backup~',
360
- 'readme.md',
361
- 'notes.txt',
362
- 'data.json',
363
- 'config.yaml',
364
- 'settings.yml',
365
- 'setup.ini',
366
- 'app.conf',
367
- 'lint.config',
368
362
  ];
369
363
 
370
364
  for (const file of wildcardExcludes) {
@@ -379,14 +373,10 @@ test('shouldExcludeFile excludes exact match files', () => {
379
373
  const exactExcludes = [
380
374
  '.Python',
381
375
  '.bak',
382
- '.gitconfig',
383
- '.makerc',
384
376
  'Gemfile.lock',
385
- 'package.json',
386
377
  'package-lock.json',
387
378
  'yarn.lock',
388
379
  'pnpm-lock.yaml',
389
- 'composer.json',
390
380
  'composer.lock',
391
381
  ];
392
382
 
@@ -415,6 +405,18 @@ test('shouldExcludeFile does NOT exclude normal source files', () => {
415
405
  'Component.jsx',
416
406
  'handler.py',
417
407
  'server.rb',
408
+ 'readme.md',
409
+ 'notes.txt',
410
+ 'data.json',
411
+ 'config.yaml',
412
+ 'settings.yml',
413
+ 'setup.ini',
414
+ 'app.conf',
415
+ 'lint.config',
416
+ '.gitconfig',
417
+ '.makerc',
418
+ 'package.json',
419
+ 'composer.json',
418
420
  ];
419
421
 
420
422
  for (const file of normalFiles) {