@garyr/pt-cli 0.25.2 → 0.27.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/README.md CHANGED
@@ -18,7 +18,7 @@ graph LR
18
18
 
19
19
  %% Flow logic
20
20
  Existing -- Learn --> Engine
21
- Config -- Read/Write --> Engine
21
+ Config <-- Read/Write --> Engine
22
22
  Engine -- Initialize --> RSA
23
23
  Engine -- Initialize --> RSB
24
24
 
@@ -55,12 +55,12 @@ graph LR
55
55
  ### Installation
56
56
 
57
57
  ```bash
58
- # Clone this repository
58
+ npm i @garyr/pt-cli
59
+
60
+ # ...or clone this repository, then:
59
61
  cd pt-cli
60
62
  npm install
61
63
  npm run build
62
-
63
- # Link for global use
64
64
  npm link
65
65
  ```
66
66
 
@@ -0,0 +1,26 @@
1
+ import fs from 'fs';
2
+ import { loadConfig, saveConfig } from '../config.js';
3
+ export function defaultPostConfigCommand(options = {}) {
4
+ const config = loadConfig();
5
+ if (options.set) {
6
+ if (options.json) {
7
+ try {
8
+ const data = options.json.startsWith('{') || options.json.startsWith('[')
9
+ ? JSON.parse(options.json)
10
+ : JSON.parse(fs.readFileSync(options.json, 'utf-8'));
11
+ config.default_post_config = Array.isArray(data) ? data : [];
12
+ saveConfig(config);
13
+ console.log('Default post-config updated via JSON.');
14
+ }
15
+ catch (e) {
16
+ const error = e;
17
+ console.error('Failed to parse JSON for default post-config:', error.message);
18
+ }
19
+ return;
20
+ }
21
+ console.error('You must provide --json <data> to set the default post-config array.');
22
+ }
23
+ else {
24
+ console.log('Current default post-config tasks:', config.default_post_config || []);
25
+ }
26
+ }
@@ -3,10 +3,19 @@ import path from 'path';
3
3
  import inquirer from 'inquirer';
4
4
  import { loadConfig, saveConfig, getTemplateNames, shouldExclude, shouldIgnore, shouldExcludeFile, getDefaultPostConfig } from '../config.js';
5
5
  import chalk from 'chalk';
6
+ import { downloadAndExtract } from '../remote.js';
6
7
  export async function learn(sourcePath, updateTemplate = null, options = {}) {
7
- const resolvedPath = path.resolve(sourcePath);
8
+ let resolvedPath;
9
+ // Phase 1: Remote Check
10
+ if (sourcePath.startsWith('http')) {
11
+ console.log(chalk.cyan(`Downloading remote template from: ${sourcePath}...`));
12
+ resolvedPath = await downloadAndExtract(sourcePath);
13
+ }
14
+ else {
15
+ resolvedPath = path.resolve(sourcePath);
16
+ }
8
17
  if (!fs.existsSync(resolvedPath)) {
9
- console.error(chalk.red(`Error: Path "${sourcePath}" does not exist.`));
18
+ console.error(chalk.red(`Error: Path "${resolvedPath}" does not exist.`));
10
19
  process.exit(1);
11
20
  }
12
21
  const isUpdate = !!updateTemplate;
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { ignoreCommand } from './commands/ignoreCommand.js';
8
8
  import { variablesCommand } from './commands/variablesCommand.js';
9
9
  import { addCommand } from './commands/addCommand.js';
10
10
  import { removeCommand } from './commands/removeCommand.js';
11
+ import { defaultPostConfigCommand } from './commands/defaultPostConfigCommand.js';
11
12
  import pkg from '../package.json' with { type: 'json' };
12
13
  const program = new Command();
13
14
  program
@@ -61,6 +62,12 @@ program
61
62
  .option('--json <data>', 'Set variables via JSON string or file')
62
63
  .option('--delete <key>', 'Delete a specific global variable')
63
64
  .action(variablesCommand);
65
+ program
66
+ .command('default-post-config')
67
+ .description('View or set default post-config tasks')
68
+ .option('--set', 'Set the default post-config tasks via JSON')
69
+ .option('--json <data>', 'JSON string or file containing tasks array')
70
+ .action(defaultPostConfigCommand);
64
71
  program
65
72
  .command('add <name> [json]')
66
73
  .description('Import/add a template from a JSON string or file')
package/dist/remote.js ADDED
@@ -0,0 +1,29 @@
1
+ // src/remote.ts (New File)
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { Readable } from 'stream';
6
+ import { finished } from 'stream/promises';
7
+ import { extract } from 'tar'; // You'll need: npm install tar
8
+ export async function downloadAndExtract(url) {
9
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pt-template-'));
10
+ let downloadUrl = url;
11
+ // Convert GitHub/Gitea URLs to Zip/Tarball endpoints
12
+ if (url.includes('github.com')) {
13
+ downloadUrl = url.replace(/\/$/, '') + '/archive/refs/heads/main.tar.gz';
14
+ }
15
+ else if (url.includes('gitea')) {
16
+ downloadUrl = url.replace(/\/$/, '') + '/archive/main.tar.gz';
17
+ }
18
+ const response = await fetch(downloadUrl);
19
+ if (!response.ok)
20
+ throw new Error(`Failed to fetch ${downloadUrl}: ${response.statusText}`);
21
+ const dest = path.join(tempDir, 'template.tar.gz');
22
+ const fileStream = fs.createWriteStream(dest);
23
+ await finished(Readable.fromWeb(response.body).pipe(fileStream));
24
+ // Extract tarball
25
+ await extract({ file: dest, cwd: tempDir });
26
+ // Find the actual content folder (archives usually wrap content in a folder)
27
+ const dirs = fs.readdirSync(tempDir).filter(f => fs.statSync(path.join(tempDir, f)).isDirectory());
28
+ return path.join(tempDir, dirs[0]);
29
+ }
@@ -146,9 +146,14 @@ Each default task supports the same fields as template post-config:
146
146
  - **`pt learn --yes`**: all applicable default tasks are added automatically to the new template's `post_config`.
147
147
  - **Interactive mode (`pt learn`)**: applicable default tasks are shown in a checkbox group. Default tasks default to checked; `checked: false` overrides this.
148
148
 
149
- ### Viewing
149
+ ### Management
150
150
 
151
- Default tasks cannot be added via `pt add` or `pt learn` — they must be edited directly in `~/.pt/config.yaml` or viewed with `pt config`.
151
+ You can view current default tasks using `pt config` or `pt default-post-config`.
152
+ To update default tasks programmatically or via CLI, use the `pt default-post-config` command:
153
+ - `pt default-post-config`: List current default post-config tasks.
154
+ - `pt default-post-config --set --json '...'`: Replace the default post-config tasks list via a JSON string or file.
155
+
156
+ Alternatively, you can edit `~/.pt/config.yaml` directly.
152
157
 
153
158
  ## Global Variables
154
159
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garyr/pt-cli",
3
- "version": "0.25.2",
3
+ "version": "0.27.0",
4
4
  "description": "Project Template CLI - Learn structures and initialize projects",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -27,16 +27,18 @@
27
27
  "url": "https://github.com/garyritchie/pt-cli"
28
28
  },
29
29
  "dependencies": {
30
+ "chalk": "^5.3.0",
30
31
  "commander": "^12.1.0",
31
32
  "inquirer": "^12.3.0",
32
- "yaml": "^2.6.1",
33
- "chalk": "^5.3.0"
33
+ "tar": "^7.5.15",
34
+ "yaml": "^2.6.1"
34
35
  },
35
36
  "devDependencies": {
36
- "typescript": "^5.6.0",
37
- "@types/node": "^22.0.0",
38
37
  "@types/inquirer": "^9.0.7",
38
+ "@types/node": "^22.0.0",
39
+ "@types/tar": "^6.1.13",
39
40
  "ts-node": "^10.9.2",
40
- "tsx": "^4.21.0"
41
+ "tsx": "^4.21.0",
42
+ "typescript": "^5.6.0"
41
43
  }
42
- }
44
+ }
@@ -42,7 +42,7 @@ Default post-config tasks are stored in `~/.pt/config.yaml` under `default_post_
42
42
 
43
43
  Tasks with `checked: false` stay unchecked by default in interactive mode. In `pt learn --yes` mode, **all applicable** default tasks are included.
44
44
 
45
- Use `pt config` to view currently configured default tasks. To add default tasks, edit `~/.pt/config.yaml` directly or use `pt add` for template management (config is YAML-only at this time).
45
+ Use `pt config` to view currently configured default tasks. To update default tasks, you can use `pt default-post-config --set --json '...'` to apply a new JSON array of tasks. Alternatively, you can edit `~/.pt/config.yaml` directly.
46
46
 
47
47
  ## Global Variables
48
48
 
@@ -0,0 +1,32 @@
1
+ import fs from 'fs';
2
+ import { loadConfig, saveConfig, PostConfigTask } from '../config.js';
3
+
4
+ export interface DefaultPostConfigOptions {
5
+ set?: boolean;
6
+ json?: string;
7
+ }
8
+
9
+ export function defaultPostConfigCommand(options: DefaultPostConfigOptions = {}) {
10
+ const config = loadConfig();
11
+
12
+ if (options.set) {
13
+ if (options.json) {
14
+ try {
15
+ const data = options.json.startsWith('{') || options.json.startsWith('[')
16
+ ? JSON.parse(options.json)
17
+ : JSON.parse(fs.readFileSync(options.json, 'utf-8'));
18
+ config.default_post_config = Array.isArray(data) ? data : [];
19
+ saveConfig(config);
20
+ console.log('Default post-config updated via JSON.');
21
+ } catch (e) {
22
+ const error = e as Error;
23
+ console.error('Failed to parse JSON for default post-config:', error.message);
24
+ }
25
+ return;
26
+ }
27
+
28
+ console.error('You must provide --json <data> to set the default post-config array.');
29
+ } else {
30
+ console.log('Current default post-config tasks:', config.default_post_config || []);
31
+ }
32
+ }
@@ -3,6 +3,7 @@ import path from 'path';
3
3
  import inquirer from 'inquirer';
4
4
  import { loadConfig, saveConfig, FolderNode, TemplateConfig, getTemplateNames, shouldExclude, shouldIgnore, shouldExcludeFile, PostCopyFile, TemplateVariable, CopyFileEntry, PostConfigTask, getDefaultPostConfig } from '../config.js';
5
5
  import chalk from 'chalk';
6
+ import { downloadAndExtract } from '../remote.js';
6
7
 
7
8
  export interface LearnOptions {
8
9
  ignore?: string;
@@ -12,11 +13,20 @@ export interface LearnOptions {
12
13
  json?: boolean;
13
14
  }
14
15
 
16
+
15
17
  export async function learn(sourcePath: string, updateTemplate: string | null = null, options: LearnOptions = {}): Promise<void> {
16
- const resolvedPath = path.resolve(sourcePath);
18
+ let resolvedPath: string;
19
+
20
+ // Phase 1: Remote Check
21
+ if (sourcePath.startsWith('http')) {
22
+ console.log(chalk.cyan(`Downloading remote template from: ${sourcePath}...`));
23
+ resolvedPath = await downloadAndExtract(sourcePath);
24
+ } else {
25
+ resolvedPath = path.resolve(sourcePath);
26
+ }
17
27
 
18
28
  if (!fs.existsSync(resolvedPath)) {
19
- console.error(chalk.red(`Error: Path "${sourcePath}" does not exist.`));
29
+ console.error(chalk.red(`Error: Path "${resolvedPath}" does not exist.`));
20
30
  process.exit(1);
21
31
  }
22
32
 
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ import { ignoreCommand } from './commands/ignoreCommand.js';
13
13
  import { variablesCommand } from './commands/variablesCommand.js';
14
14
  import { addCommand } from './commands/addCommand.js';
15
15
  import { removeCommand } from './commands/removeCommand.js';
16
+ import { defaultPostConfigCommand } from './commands/defaultPostConfigCommand.js';
16
17
 
17
18
  import pkg from '../package.json' with { type: 'json' };
18
19
 
@@ -76,6 +77,13 @@ program
76
77
  .option('--delete <key>', 'Delete a specific global variable')
77
78
  .action(variablesCommand);
78
79
 
80
+ program
81
+ .command('default-post-config')
82
+ .description('View or set default post-config tasks')
83
+ .option('--set', 'Set the default post-config tasks via JSON')
84
+ .option('--json <data>', 'JSON string or file containing tasks array')
85
+ .action(defaultPostConfigCommand);
86
+
79
87
  program
80
88
  .command('add <name> [json]')
81
89
  .description('Import/add a template from a JSON string or file')
package/src/remote.ts ADDED
@@ -0,0 +1,33 @@
1
+ // src/remote.ts (New File)
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { Readable } from 'stream';
6
+ import { finished } from 'stream/promises';
7
+ import { extract } from 'tar'; // You'll need: npm install tar
8
+
9
+ export async function downloadAndExtract(url: string): Promise<string> {
10
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pt-template-'));
11
+ let downloadUrl = url;
12
+
13
+ // Convert GitHub/Gitea URLs to Zip/Tarball endpoints
14
+ if (url.includes('github.com')) {
15
+ downloadUrl = url.replace(/\/$/, '') + '/archive/refs/heads/main.tar.gz';
16
+ } else if (url.includes('gitea')) {
17
+ downloadUrl = url.replace(/\/$/, '') + '/archive/main.tar.gz';
18
+ }
19
+
20
+ const response = await fetch(downloadUrl);
21
+ if (!response.ok) throw new Error(`Failed to fetch ${downloadUrl}: ${response.statusText}`);
22
+
23
+ const dest = path.join(tempDir, 'template.tar.gz');
24
+ const fileStream = fs.createWriteStream(dest);
25
+ await finished(Readable.fromWeb(response.body as any).pipe(fileStream));
26
+
27
+ // Extract tarball
28
+ await extract({ file: dest, cwd: tempDir });
29
+
30
+ // Find the actual content folder (archives usually wrap content in a folder)
31
+ const dirs = fs.readdirSync(tempDir).filter(f => fs.statSync(path.join(tempDir, f)).isDirectory());
32
+ return path.join(tempDir, dirs[0]);
33
+ }