pmpt-cli 1.2.1 → 1.3.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/dist/commands/export.js +78 -149
- package/dist/commands/import.js +98 -94
- package/dist/index.js +3 -3
- package/dist/lib/pmptFile.js +106 -0
- package/package.json +1 -1
package/dist/commands/export.js
CHANGED
|
@@ -1,125 +1,48 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { resolve, join, basename } from 'path';
|
|
3
|
-
import { existsSync, writeFileSync, statSync } from 'fs';
|
|
4
|
-
import {
|
|
5
|
-
import { isInitialized, getConfigDir, getHistoryDir, loadConfig } from '../lib/config.js';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, statSync } from 'fs';
|
|
4
|
+
import { isInitialized, getDocsDir, loadConfig } from '../lib/config.js';
|
|
6
5
|
import { getAllSnapshots } from '../lib/history.js';
|
|
7
6
|
import { getPlanProgress } from '../lib/plan.js';
|
|
7
|
+
import { createPmptFile, SCHEMA_VERSION } from '../lib/pmptFile.js';
|
|
8
|
+
import glob from 'fast-glob';
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
* Uses native zip command on macOS/Linux, PowerShell on Windows
|
|
10
|
+
* Read all files from a snapshot directory
|
|
11
11
|
*/
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
function readSnapshotFiles(snapshotDir) {
|
|
13
|
+
const files = {};
|
|
14
|
+
if (!existsSync(snapshotDir))
|
|
15
|
+
return files;
|
|
16
|
+
const mdFiles = glob.sync('**/*.md', { cwd: snapshotDir });
|
|
17
|
+
for (const file of mdFiles) {
|
|
18
|
+
const filePath = join(snapshotDir, file);
|
|
19
|
+
try {
|
|
20
|
+
files[file] = readFileSync(filePath, 'utf-8');
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
execSync(`cd "${sourceDir}" && zip -r "${outputPath}" .`, {
|
|
24
|
-
stdio: 'pipe',
|
|
25
|
-
});
|
|
22
|
+
catch {
|
|
23
|
+
// Skip files that can't be read
|
|
26
24
|
}
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return false;
|
|
31
25
|
}
|
|
26
|
+
return files;
|
|
32
27
|
}
|
|
33
28
|
/**
|
|
34
|
-
*
|
|
29
|
+
* Read current docs folder
|
|
35
30
|
*/
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
for (const snapshot of snapshots) {
|
|
49
|
-
for (const file of snapshot.files) {
|
|
50
|
-
allFiles.add(file);
|
|
31
|
+
function readDocsFolder(docsDir) {
|
|
32
|
+
const files = {};
|
|
33
|
+
if (!existsSync(docsDir))
|
|
34
|
+
return files;
|
|
35
|
+
const mdFiles = glob.sync('**/*.md', { cwd: docsDir });
|
|
36
|
+
for (const file of mdFiles) {
|
|
37
|
+
const filePath = join(docsDir, file);
|
|
38
|
+
try {
|
|
39
|
+
files[file] = readFileSync(filePath, 'utf-8');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Skip files that can't be read
|
|
51
43
|
}
|
|
52
44
|
}
|
|
53
|
-
|
|
54
|
-
const features = answers.coreFeatures
|
|
55
|
-
? answers.coreFeatures
|
|
56
|
-
.split(/[,;\n]/)
|
|
57
|
-
.map((f) => f.trim())
|
|
58
|
-
.filter((f) => f)
|
|
59
|
-
.map((f) => `- ${f}`)
|
|
60
|
-
.join('\n')
|
|
61
|
-
: 'Not specified';
|
|
62
|
-
let md = `# ${projectName}
|
|
63
|
-
|
|
64
|
-
> Exported from pmpt-cli
|
|
65
|
-
|
|
66
|
-
## Project Overview
|
|
67
|
-
|
|
68
|
-
**Product Idea:**
|
|
69
|
-
${answers.productIdea || 'Not specified'}
|
|
70
|
-
|
|
71
|
-
${answers.additionalContext ? `**Additional Context:**\n${answers.additionalContext}\n` : ''}
|
|
72
|
-
**Key Features:**
|
|
73
|
-
${features}
|
|
74
|
-
|
|
75
|
-
**Tech Stack:**
|
|
76
|
-
${answers.techStack || 'Not specified'}
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## Export Information
|
|
81
|
-
|
|
82
|
-
| Item | Value |
|
|
83
|
-
|------|-------|
|
|
84
|
-
| Total Iterations | ${snapshots.length} |
|
|
85
|
-
| First Version | ${startDate} |
|
|
86
|
-
| Last Version | ${lastDate} |
|
|
87
|
-
| Files Tracked | ${allFiles.size} |
|
|
88
|
-
`;
|
|
89
|
-
if (gitInfo) {
|
|
90
|
-
md += `| Git Branch | ${gitInfo.branch} |
|
|
91
|
-
| Last Commit | ${gitInfo.commit} |
|
|
92
|
-
`;
|
|
93
|
-
}
|
|
94
|
-
md += `
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
## Version Timeline
|
|
98
|
-
|
|
99
|
-
| Version | Date | Files | Git |
|
|
100
|
-
|---------|------|-------|-----|
|
|
101
|
-
`;
|
|
102
|
-
for (const snapshot of snapshots) {
|
|
103
|
-
const gitCol = snapshot.git
|
|
104
|
-
? `${snapshot.git.commit}${snapshot.git.dirty ? ' (dirty)' : ''}`
|
|
105
|
-
: '-';
|
|
106
|
-
md += `| v${snapshot.version} | ${snapshot.timestamp} | ${snapshot.files.length} | ${gitCol} |\n`;
|
|
107
|
-
}
|
|
108
|
-
md += `
|
|
109
|
-
---
|
|
110
|
-
|
|
111
|
-
## File List
|
|
112
|
-
|
|
113
|
-
`;
|
|
114
|
-
for (const file of Array.from(allFiles).sort()) {
|
|
115
|
-
md += `- ${file}\n`;
|
|
116
|
-
}
|
|
117
|
-
md += `
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
*Generated by [pmpt-cli](https://pmptwiki.com)*
|
|
121
|
-
`;
|
|
122
|
-
return md;
|
|
45
|
+
return files;
|
|
123
46
|
}
|
|
124
47
|
export async function cmdExport(path, options) {
|
|
125
48
|
const projectPath = path ? resolve(path) : process.cwd();
|
|
@@ -138,69 +61,75 @@ export async function cmdExport(path, options) {
|
|
|
138
61
|
return;
|
|
139
62
|
}
|
|
140
63
|
const projectName = planProgress?.answers?.projectName || basename(projectPath);
|
|
141
|
-
const timestamp = new Date().toISOString().
|
|
142
|
-
const exportName =
|
|
143
|
-
// Output path
|
|
64
|
+
const timestamp = new Date().toISOString().slice(0, 10);
|
|
65
|
+
const exportName = `${projectName}-${timestamp}`;
|
|
66
|
+
// Output path - .pmpt extension
|
|
144
67
|
const outputPath = options?.output
|
|
145
68
|
? resolve(options.output)
|
|
146
|
-
: resolve(projectPath, `${exportName}.
|
|
69
|
+
: resolve(projectPath, `${exportName}.pmpt`);
|
|
147
70
|
const s = p.spinner();
|
|
148
|
-
s.start('
|
|
149
|
-
//
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const docsDir = join(getConfigDir(projectPath), 'docs');
|
|
160
|
-
if (existsSync(docsDir)) {
|
|
161
|
-
execSync(`cp -r "${docsDir}" "${tempDir}/docs"`, { stdio: 'pipe' });
|
|
162
|
-
}
|
|
163
|
-
// Copy config (without sensitive data)
|
|
164
|
-
if (config) {
|
|
165
|
-
const exportConfig = {
|
|
166
|
-
projectPath: basename(projectPath),
|
|
167
|
-
docsPath: config.docsPath,
|
|
168
|
-
createdAt: config.createdAt,
|
|
169
|
-
trackGit: config.trackGit,
|
|
170
|
-
};
|
|
171
|
-
writeFileSync(join(tempDir, 'config.json'), JSON.stringify(exportConfig, null, 2), 'utf-8');
|
|
71
|
+
s.start('Creating .pmpt file...');
|
|
72
|
+
// Build history array with file contents
|
|
73
|
+
const history = [];
|
|
74
|
+
for (const snapshot of snapshots) {
|
|
75
|
+
const files = readSnapshotFiles(snapshot.snapshotDir);
|
|
76
|
+
history.push({
|
|
77
|
+
version: snapshot.version,
|
|
78
|
+
timestamp: snapshot.timestamp,
|
|
79
|
+
files,
|
|
80
|
+
git: snapshot.git,
|
|
81
|
+
});
|
|
172
82
|
}
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
83
|
+
// Read current docs
|
|
84
|
+
const docsDir = getDocsDir(projectPath);
|
|
85
|
+
const docs = readDocsFolder(docsDir);
|
|
86
|
+
// Build metadata
|
|
87
|
+
const meta = {
|
|
88
|
+
projectName,
|
|
89
|
+
description: planProgress?.answers?.productIdea,
|
|
90
|
+
createdAt: config?.createdAt || new Date().toISOString(),
|
|
91
|
+
exportedAt: new Date().toISOString(),
|
|
92
|
+
};
|
|
93
|
+
// Convert plan answers to typed format
|
|
94
|
+
const planAnswers = planProgress?.answers
|
|
95
|
+
? {
|
|
96
|
+
projectName: planProgress.answers.projectName,
|
|
97
|
+
productIdea: planProgress.answers.productIdea,
|
|
98
|
+
additionalContext: planProgress.answers.additionalContext,
|
|
99
|
+
coreFeatures: planProgress.answers.coreFeatures,
|
|
100
|
+
techStack: planProgress.answers.techStack,
|
|
101
|
+
}
|
|
102
|
+
: undefined;
|
|
103
|
+
// Create .pmpt file content
|
|
104
|
+
const pmptContent = createPmptFile(meta, planAnswers, docs, history);
|
|
105
|
+
// Write file
|
|
106
|
+
try {
|
|
107
|
+
writeFileSync(outputPath, pmptContent, 'utf-8');
|
|
176
108
|
}
|
|
177
|
-
|
|
178
|
-
// Create zip
|
|
179
|
-
const zipSuccess = createZip(tempDir, outputPath);
|
|
180
|
-
// Cleanup temp directory
|
|
181
|
-
execSync(`rm -rf "${tempDir}"`, { stdio: 'pipe' });
|
|
182
|
-
if (!zipSuccess) {
|
|
109
|
+
catch (err) {
|
|
183
110
|
s.stop('Export failed');
|
|
184
|
-
p.log.error('Failed to
|
|
185
|
-
p.log.info('Make sure `zip` command is available on your system.');
|
|
111
|
+
p.log.error('Failed to write .pmpt file.');
|
|
186
112
|
process.exit(1);
|
|
187
113
|
}
|
|
188
114
|
s.stop('Export complete!');
|
|
189
|
-
//
|
|
115
|
+
// File size
|
|
190
116
|
const fileSizeBytes = statSync(outputPath).size;
|
|
191
117
|
const fileSize = fileSizeBytes < 1024
|
|
192
118
|
? `${fileSizeBytes} B`
|
|
193
119
|
: fileSizeBytes < 1024 * 1024
|
|
194
120
|
? `${(fileSizeBytes / 1024).toFixed(1)} KB`
|
|
195
121
|
: `${(fileSizeBytes / 1024 / 1024).toFixed(1)} MB`;
|
|
122
|
+
// Summary
|
|
196
123
|
const summary = [
|
|
197
124
|
`Project: ${projectName}`,
|
|
198
125
|
`Versions: ${snapshots.length}`,
|
|
126
|
+
`Schema: v${SCHEMA_VERSION}`,
|
|
199
127
|
`Size: ${fileSize}`,
|
|
200
128
|
'',
|
|
201
129
|
`Output: ${outputPath}`,
|
|
202
130
|
];
|
|
203
131
|
p.note(summary.join('\n'), 'Export Summary');
|
|
204
|
-
p.log.info('Share this file to let others reproduce your AI development journey!');
|
|
132
|
+
p.log.info('Share this .pmpt file to let others reproduce your AI development journey!');
|
|
133
|
+
p.log.message(' pmpt import <file.pmpt> — Import on another machine');
|
|
205
134
|
p.outro('');
|
|
206
135
|
}
|
package/dist/commands/import.js
CHANGED
|
@@ -1,50 +1,98 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
|
-
import { resolve, join } from 'path';
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync,
|
|
4
|
-
import { execSync } from 'child_process';
|
|
2
|
+
import { resolve, join, dirname } from 'path';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
5
4
|
import { isInitialized, getConfigDir, getHistoryDir, getDocsDir, initializeProject } from '../lib/config.js';
|
|
5
|
+
import { validatePmptFile } from '../lib/pmptFile.js';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* Uses native unzip command on macOS/Linux, PowerShell on Windows
|
|
7
|
+
* Restore history from .pmpt file
|
|
9
8
|
*/
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
function restoreHistory(historyDir, history) {
|
|
10
|
+
mkdirSync(historyDir, { recursive: true });
|
|
11
|
+
for (const version of history) {
|
|
12
|
+
const timestamp = version.timestamp.replace(/[:.]/g, '-').slice(0, 19);
|
|
13
|
+
const snapshotName = `v${version.version}-${timestamp}`;
|
|
14
|
+
const snapshotDir = join(historyDir, snapshotName);
|
|
15
|
+
mkdirSync(snapshotDir, { recursive: true });
|
|
16
|
+
// Write files
|
|
17
|
+
for (const [filename, content] of Object.entries(version.files)) {
|
|
18
|
+
const filePath = join(snapshotDir, filename);
|
|
19
|
+
const fileDir = dirname(filePath);
|
|
20
|
+
if (fileDir !== snapshotDir) {
|
|
21
|
+
mkdirSync(fileDir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
25
24
|
}
|
|
26
|
-
|
|
25
|
+
// Write metadata
|
|
26
|
+
const metaPath = join(snapshotDir, '.meta.json');
|
|
27
|
+
writeFileSync(metaPath, JSON.stringify({
|
|
28
|
+
version: version.version,
|
|
29
|
+
timestamp: version.timestamp,
|
|
30
|
+
files: Object.keys(version.files),
|
|
31
|
+
git: version.git,
|
|
32
|
+
}, null, 2), 'utf-8');
|
|
27
33
|
}
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Restore docs from .pmpt file
|
|
37
|
+
*/
|
|
38
|
+
function restoreDocs(docsDir, docs) {
|
|
39
|
+
mkdirSync(docsDir, { recursive: true });
|
|
40
|
+
for (const [filename, content] of Object.entries(docs)) {
|
|
41
|
+
const filePath = join(docsDir, filename);
|
|
42
|
+
const fileDir = dirname(filePath);
|
|
43
|
+
if (fileDir !== docsDir) {
|
|
44
|
+
mkdirSync(fileDir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
30
47
|
}
|
|
31
48
|
}
|
|
32
|
-
export async function cmdImport(
|
|
33
|
-
if (!
|
|
34
|
-
p.log.error('Please provide a
|
|
35
|
-
p.log.info('Usage: pmpt import <file.
|
|
49
|
+
export async function cmdImport(pmptFile, options) {
|
|
50
|
+
if (!pmptFile) {
|
|
51
|
+
p.log.error('Please provide a .pmpt file path.');
|
|
52
|
+
p.log.info('Usage: pmpt import <file.pmpt>');
|
|
36
53
|
process.exit(1);
|
|
37
54
|
}
|
|
38
|
-
const
|
|
39
|
-
if (!existsSync(
|
|
40
|
-
p.log.error(`File not found: ${
|
|
55
|
+
const filePath = resolve(pmptFile);
|
|
56
|
+
if (!existsSync(filePath)) {
|
|
57
|
+
p.log.error(`File not found: ${filePath}`);
|
|
41
58
|
process.exit(1);
|
|
42
59
|
}
|
|
43
|
-
if (!
|
|
44
|
-
p.log.error('Please provide a .
|
|
60
|
+
if (!filePath.endsWith('.pmpt')) {
|
|
61
|
+
p.log.error('Please provide a .pmpt file.');
|
|
62
|
+
p.log.info('Use `pmpt export` to create a .pmpt file.');
|
|
45
63
|
process.exit(1);
|
|
46
64
|
}
|
|
47
65
|
p.intro('pmpt import');
|
|
66
|
+
const s = p.spinner();
|
|
67
|
+
s.start('Reading .pmpt file...');
|
|
68
|
+
// Read file
|
|
69
|
+
let fileContent;
|
|
70
|
+
try {
|
|
71
|
+
fileContent = readFileSync(filePath, 'utf-8');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
s.stop('Read failed');
|
|
75
|
+
p.log.error('Failed to read file.');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
// Validate
|
|
79
|
+
s.message('Validating...');
|
|
80
|
+
const validation = validatePmptFile(fileContent);
|
|
81
|
+
if (!validation.success || !validation.data) {
|
|
82
|
+
s.stop('Validation failed');
|
|
83
|
+
p.log.error(validation.error || 'Invalid .pmpt file.');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
const pmptData = validation.data;
|
|
87
|
+
s.stop('Validation passed');
|
|
88
|
+
// Show summary and confirm
|
|
89
|
+
const summaryLines = [
|
|
90
|
+
`Project: ${pmptData.meta.projectName}`,
|
|
91
|
+
`Versions: ${pmptData.history.length}`,
|
|
92
|
+
`Schema: v${pmptData.schemaVersion}`,
|
|
93
|
+
pmptData.meta.description ? `Description: ${pmptData.meta.description.slice(0, 50)}...` : '',
|
|
94
|
+
].filter(Boolean);
|
|
95
|
+
p.note(summaryLines.join('\n'), 'Import Preview');
|
|
48
96
|
const projectPath = process.cwd();
|
|
49
97
|
// Check if already initialized
|
|
50
98
|
if (isInitialized(projectPath) && !options?.force) {
|
|
@@ -62,84 +110,40 @@ export async function cmdImport(zipFile, options) {
|
|
|
62
110
|
return;
|
|
63
111
|
}
|
|
64
112
|
}
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
// Create temp directory for extraction
|
|
68
|
-
const tempDir = join(projectPath, '.pmpt-import-temp');
|
|
69
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
70
|
-
const extracted = extractZip(zipPath, tempDir);
|
|
71
|
-
if (!extracted) {
|
|
72
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
73
|
-
s.stop('Extraction failed');
|
|
74
|
-
p.log.error('Failed to extract zip file.');
|
|
75
|
-
p.log.info('Make sure `unzip` command is available on your system.');
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|
|
78
|
-
s.message('Importing project...');
|
|
79
|
-
// Read imported data
|
|
80
|
-
let importedPlan = null;
|
|
81
|
-
let importedConfig = null;
|
|
82
|
-
let projectName = 'imported-project';
|
|
83
|
-
// Read plan.json if exists
|
|
84
|
-
const planPath = join(tempDir, 'plan.json');
|
|
85
|
-
if (existsSync(planPath)) {
|
|
86
|
-
try {
|
|
87
|
-
importedPlan = JSON.parse(readFileSync(planPath, 'utf-8'));
|
|
88
|
-
if (importedPlan.answers?.projectName) {
|
|
89
|
-
projectName = importedPlan.answers.projectName;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
// Ignore parse errors
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
// Read config.json if exists
|
|
97
|
-
const configPath = join(tempDir, 'config.json');
|
|
98
|
-
if (existsSync(configPath)) {
|
|
99
|
-
try {
|
|
100
|
-
importedConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
// Ignore parse errors
|
|
104
|
-
}
|
|
105
|
-
}
|
|
113
|
+
const importSpinner = p.spinner();
|
|
114
|
+
importSpinner.start('Importing project...');
|
|
106
115
|
// Initialize project if not exists
|
|
107
116
|
if (!isInitialized(projectPath)) {
|
|
108
|
-
initializeProject(projectPath, {
|
|
109
|
-
trackGit: importedConfig?.trackGit ?? true,
|
|
110
|
-
});
|
|
117
|
+
initializeProject(projectPath, { trackGit: true });
|
|
111
118
|
}
|
|
112
119
|
const pmptDir = getConfigDir(projectPath);
|
|
113
120
|
const historyDir = getHistoryDir(projectPath);
|
|
114
121
|
const docsDir = getDocsDir(projectPath);
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
// Import docs
|
|
122
|
-
const importedDocsDir = join(tempDir, 'docs');
|
|
123
|
-
if (existsSync(importedDocsDir)) {
|
|
124
|
-
cpSync(importedDocsDir, docsDir, { recursive: true, force: true });
|
|
122
|
+
// Restore history
|
|
123
|
+
restoreHistory(historyDir, pmptData.history);
|
|
124
|
+
// Restore docs
|
|
125
|
+
if (pmptData.docs) {
|
|
126
|
+
restoreDocs(docsDir, pmptData.docs);
|
|
125
127
|
}
|
|
126
|
-
//
|
|
127
|
-
if (
|
|
128
|
+
// Restore plan progress
|
|
129
|
+
if (pmptData.plan) {
|
|
128
130
|
const planProgressPath = join(pmptDir, 'plan-progress.json');
|
|
129
|
-
writeFileSync(planProgressPath, JSON.stringify(
|
|
131
|
+
writeFileSync(planProgressPath, JSON.stringify({
|
|
132
|
+
completed: true,
|
|
133
|
+
startedAt: pmptData.meta.createdAt,
|
|
134
|
+
updatedAt: pmptData.meta.exportedAt,
|
|
135
|
+
answers: pmptData.plan,
|
|
136
|
+
}, null, 2), 'utf-8');
|
|
130
137
|
}
|
|
131
138
|
// Count imported versions
|
|
132
139
|
let versionCount = 0;
|
|
133
140
|
if (existsSync(historyDir)) {
|
|
134
|
-
const { readdirSync } = await import('fs');
|
|
135
141
|
versionCount = readdirSync(historyDir).filter(d => d.startsWith('v')).length;
|
|
136
142
|
}
|
|
137
|
-
|
|
138
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
139
|
-
s.stop('Import complete!');
|
|
143
|
+
importSpinner.stop('Import complete!');
|
|
140
144
|
// Summary
|
|
141
145
|
const summary = [
|
|
142
|
-
`Project: ${projectName}`,
|
|
146
|
+
`Project: ${pmptData.meta.projectName}`,
|
|
143
147
|
`Versions imported: ${versionCount}`,
|
|
144
148
|
`Location: ${pmptDir}`,
|
|
145
149
|
];
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ const program = new Command();
|
|
|
16
16
|
program
|
|
17
17
|
.name('pmpt')
|
|
18
18
|
.description('pmpt — Record and share your AI-driven product development journey')
|
|
19
|
-
.version('1.
|
|
19
|
+
.version('1.3.0')
|
|
20
20
|
.addHelpText('after', `
|
|
21
21
|
Examples:
|
|
22
22
|
$ pmpt init Initialize project
|
|
@@ -26,8 +26,8 @@ Examples:
|
|
|
26
26
|
$ pmpt history View version history
|
|
27
27
|
$ pmpt history --compact Hide minor changes
|
|
28
28
|
$ pmpt squash v2 v5 Merge versions v2-v5 into v2
|
|
29
|
-
$ pmpt export Export
|
|
30
|
-
$ pmpt import <file.
|
|
29
|
+
$ pmpt export Export as .pmpt file (single JSON)
|
|
30
|
+
$ pmpt import <file.pmpt> Import from .pmpt file
|
|
31
31
|
|
|
32
32
|
Folder structure:
|
|
33
33
|
.pmpt/
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .pmpt File Format Schema (v1.0)
|
|
3
|
+
*
|
|
4
|
+
* Single JSON file format for sharing pmpt projects.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
// Schema version
|
|
8
|
+
export const SCHEMA_VERSION = '1.0';
|
|
9
|
+
export const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
10
|
+
// Git info schema
|
|
11
|
+
const GitInfoSchema = z.object({
|
|
12
|
+
commit: z.string(),
|
|
13
|
+
commitFull: z.string().optional(),
|
|
14
|
+
branch: z.string().optional(),
|
|
15
|
+
dirty: z.boolean().optional(),
|
|
16
|
+
tag: z.string().optional(),
|
|
17
|
+
}).optional();
|
|
18
|
+
// Single version/snapshot schema
|
|
19
|
+
const VersionSchema = z.object({
|
|
20
|
+
version: z.number().min(1),
|
|
21
|
+
timestamp: z.string(),
|
|
22
|
+
files: z.record(z.string(), z.string()), // filename -> content
|
|
23
|
+
git: GitInfoSchema,
|
|
24
|
+
});
|
|
25
|
+
// Plan answers schema
|
|
26
|
+
const PlanSchema = z.object({
|
|
27
|
+
projectName: z.string(),
|
|
28
|
+
productIdea: z.string().optional(),
|
|
29
|
+
additionalContext: z.string().optional(),
|
|
30
|
+
coreFeatures: z.string().optional(),
|
|
31
|
+
techStack: z.string().optional(),
|
|
32
|
+
}).optional();
|
|
33
|
+
// Project metadata schema
|
|
34
|
+
const MetaSchema = z.object({
|
|
35
|
+
projectName: z.string(),
|
|
36
|
+
author: z.string().optional(),
|
|
37
|
+
description: z.string().optional(),
|
|
38
|
+
createdAt: z.string(),
|
|
39
|
+
exportedAt: z.string(),
|
|
40
|
+
});
|
|
41
|
+
// Full .pmpt file schema
|
|
42
|
+
export const PmptFileSchema = z.object({
|
|
43
|
+
schemaVersion: z.string(),
|
|
44
|
+
cliMinVersion: z.string().optional(),
|
|
45
|
+
meta: MetaSchema,
|
|
46
|
+
plan: PlanSchema,
|
|
47
|
+
docs: z.record(z.string(), z.string()).optional(), // current docs
|
|
48
|
+
history: z.array(VersionSchema),
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* Validate .pmpt file content
|
|
52
|
+
*/
|
|
53
|
+
export function validatePmptFile(content) {
|
|
54
|
+
// Check size
|
|
55
|
+
if (content.length > MAX_FILE_SIZE) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
error: `File too large. Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Parse JSON
|
|
62
|
+
let json;
|
|
63
|
+
try {
|
|
64
|
+
json = JSON.parse(content);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
error: 'Invalid JSON format',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Validate schema
|
|
73
|
+
const result = PmptFileSchema.safeParse(json);
|
|
74
|
+
if (!result.success) {
|
|
75
|
+
const firstError = result.error.errors[0];
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
error: `Validation error: ${firstError.path.join('.')} - ${firstError.message}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Check schema version
|
|
82
|
+
if (result.data.schemaVersion !== SCHEMA_VERSION) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: `Unsupported schema version: ${result.data.schemaVersion}. Current: ${SCHEMA_VERSION}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
data: result.data,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create .pmpt file content from project data
|
|
95
|
+
*/
|
|
96
|
+
export function createPmptFile(meta, plan, docs, history) {
|
|
97
|
+
const pmptFile = {
|
|
98
|
+
schemaVersion: SCHEMA_VERSION,
|
|
99
|
+
cliMinVersion: '1.3.0',
|
|
100
|
+
meta,
|
|
101
|
+
plan,
|
|
102
|
+
docs,
|
|
103
|
+
history,
|
|
104
|
+
};
|
|
105
|
+
return JSON.stringify(pmptFile, null, 2);
|
|
106
|
+
}
|