@oss-ma/tpl 1.0.36 → 1.0.38

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.
@@ -0,0 +1,169 @@
1
+ // cli/src/commands/update.ts
2
+ import { Command } from "commander";
3
+ import path from "node:path";
4
+ import crypto from "node:crypto";
5
+ import fs from "fs-extra";
6
+ import pc from "picocolors";
7
+ import { confirm } from "@inquirer/prompts";
8
+ import { readTemplateLock } from "../engine/readLock.js";
9
+ import { loadTemplate } from "../engine/loadTemplate.js";
10
+ import { writeTemplateLock } from "../engine/lock.js";
11
+ async function hashFile(filePath) {
12
+ const content = await fs.readFile(filePath);
13
+ return crypto.createHash("sha256").update(content).digest("hex");
14
+ }
15
+ async function collectTemplateFiles(filesDir) {
16
+ const files = new Set();
17
+ const entries = await fs.readdir(filesDir, { withFileTypes: true });
18
+ for (const entry of entries) {
19
+ const full = path.join(filesDir, entry.name);
20
+ if (entry.isDirectory()) {
21
+ const subFiles = await collectTemplateFiles(full);
22
+ for (const sub of subFiles) {
23
+ files.add(path.join(entry.name, sub).replace(/\\/g, "/"));
24
+ }
25
+ }
26
+ else if (entry.isFile()) {
27
+ files.add(entry.name);
28
+ }
29
+ }
30
+ return files;
31
+ }
32
+ export const updateCommand = new Command("update")
33
+ .option("--path <path>", "Project path", ".")
34
+ .option("--yes", "Auto-accept all updates without prompts")
35
+ .option("--dry-run", "Show what would be updated without making changes")
36
+ .description("Update project to latest template version")
37
+ .action(async (opts) => {
38
+ try {
39
+ const projectPath = path.resolve(opts.path);
40
+ // 1) Read current lock
41
+ const lock = await readTemplateLock(projectPath);
42
+ if (!lock) {
43
+ console.log(pc.red("❌ Error:"), "No template.lock found. Project not generated by tpl.");
44
+ process.exit(1);
45
+ }
46
+ console.log(pc.cyan("Current version:"), `${lock.template}@${lock.version}`);
47
+ // 2) Load current and latest template
48
+ const currentTemplate = await loadTemplate(lock.template);
49
+ const latestVersion = currentTemplate.spec.version;
50
+ if (lock.version === latestVersion) {
51
+ console.log(pc.green("✅"), "Already at latest version");
52
+ process.exit(0);
53
+ }
54
+ console.log(pc.cyan("Latest version:"), `${lock.template}@${latestVersion}`);
55
+ if (!opts.yes && !opts.dryRun) {
56
+ const proceed = await confirm({
57
+ message: `Update from ${lock.version} to ${latestVersion}?`,
58
+ default: true,
59
+ });
60
+ if (!proceed) {
61
+ console.log("Update cancelled");
62
+ process.exit(0);
63
+ }
64
+ }
65
+ // 3) Analyze changes
66
+ const latestFiles = await collectTemplateFiles(currentTemplate.filesDir);
67
+ const changes = [];
68
+ for (const relPath of latestFiles) {
69
+ const projectFilePath = path.join(projectPath, relPath);
70
+ const exists = await fs.pathExists(projectFilePath);
71
+ if (!exists) {
72
+ changes.push({ path: relPath, status: "added", userModified: false });
73
+ continue;
74
+ }
75
+ const currentHash = await hashFile(projectFilePath);
76
+ const originalHash = lock.filesIntegrity?.[relPath];
77
+ const userModified = originalHash && currentHash !== originalHash;
78
+ // Compare with new template version
79
+ const templateFilePath = path.join(currentTemplate.filesDir, relPath);
80
+ const templateHash = await hashFile(templateFilePath);
81
+ if (currentHash === templateHash) {
82
+ changes.push({ path: relPath, status: "unchanged", userModified: false });
83
+ }
84
+ else {
85
+ changes.push({
86
+ path: relPath,
87
+ status: originalHash ? "modified" : "added",
88
+ userModified: !!userModified,
89
+ });
90
+ }
91
+ }
92
+ // Check for deleted files
93
+ if (lock.filesIntegrity) {
94
+ for (const relPath of Object.keys(lock.filesIntegrity)) {
95
+ if (!latestFiles.has(relPath)) {
96
+ changes.push({ path: relPath, status: "deleted", userModified: false });
97
+ }
98
+ }
99
+ }
100
+ // 4) Report changes
101
+ const toUpdate = changes.filter((c) => c.status !== "unchanged");
102
+ if (toUpdate.length === 0) {
103
+ console.log(pc.green("✅"), "No changes to apply");
104
+ process.exit(0);
105
+ }
106
+ console.log("");
107
+ console.log(pc.bold("Changes to apply:"));
108
+ for (const change of toUpdate) {
109
+ const icon = change.status === "added"
110
+ ? pc.green("+")
111
+ : change.status === "deleted"
112
+ ? pc.red("-")
113
+ : pc.yellow("~");
114
+ const warning = change.userModified ? pc.red(" (user modified)") : "";
115
+ console.log(`${icon} ${change.path}${warning}`);
116
+ }
117
+ if (opts.dryRun) {
118
+ console.log("");
119
+ console.log(pc.cyan("Dry run mode — no changes made"));
120
+ process.exit(0);
121
+ }
122
+ // 5) Apply updates
123
+ console.log("");
124
+ let updated = 0;
125
+ let skipped = 0;
126
+ for (const change of toUpdate) {
127
+ const projectFilePath = path.join(projectPath, change.path);
128
+ const templateFilePath = path.join(currentTemplate.filesDir, change.path);
129
+ if (change.status === "deleted") {
130
+ console.log(pc.gray(`Skipping deletion: ${change.path}`));
131
+ skipped++;
132
+ continue;
133
+ }
134
+ if (change.userModified && !opts.yes) {
135
+ const overwrite = await confirm({
136
+ message: `Overwrite user-modified file: ${change.path}?`,
137
+ default: false,
138
+ });
139
+ if (!overwrite) {
140
+ console.log(pc.gray(`Skipped: ${change.path}`));
141
+ skipped++;
142
+ continue;
143
+ }
144
+ }
145
+ await fs.copy(templateFilePath, projectFilePath, { overwrite: true });
146
+ console.log(pc.green(`Updated: ${change.path}`));
147
+ updated++;
148
+ }
149
+ // 6) Update lock
150
+ await writeTemplateLock({
151
+ destDir: projectPath,
152
+ template: lock.template,
153
+ version: latestVersion,
154
+ options: lock.options ?? {},
155
+ packs: lock.packs,
156
+ generatedAt: new Date().toISOString(),
157
+ });
158
+ console.log("");
159
+ console.log(pc.green("✅ Update complete"));
160
+ console.log(`Updated: ${updated} files`);
161
+ if (skipped > 0)
162
+ console.log(`Skipped: ${skipped} files`);
163
+ process.exit(0);
164
+ }
165
+ catch (err) {
166
+ console.error(pc.red("❌ Error:"), err?.message ?? String(err));
167
+ process.exit(2);
168
+ }
169
+ });
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { Command } from "commander";
3
3
  import { initCommand } from "./commands/init.js";
4
4
  import { checkCommand } from "./commands/check.js";
5
+ import { updateCommand } from "./commands/update.js";
5
6
  const program = new Command();
6
7
  program
7
8
  .name("tpl")
@@ -9,4 +10,5 @@ program
9
10
  .version("0.1.0");
10
11
  program.addCommand(initCommand);
11
12
  program.addCommand(checkCommand);
13
+ program.addCommand(updateCommand);
12
14
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oss-ma/tpl",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
4
4
  "description": "Generate, enforce and maintain clean project architectures",
5
5
  "type": "module",
6
6
  "repository": {
@@ -26,12 +26,12 @@ jobs:
26
26
  persist-credentials: false
27
27
 
28
28
  - name: Initialize CodeQL
29
- uses: github/codeql-action/init@v4
29
+ uses: github/codeql-action/init@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f
30
30
  with:
31
31
  languages: javascript-typescript
32
32
  queries: security-extended
33
33
 
34
34
  - name: Perform CodeQL Analysis
35
- uses: github/codeql-action/analyze@v4
35
+ uses: github/codeql-action/analyze@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f
36
36
  with:
37
37
  category: "/language:javascript-typescript"
@@ -2,7 +2,11 @@
2
2
 
3
3
  Next.js 15 · App Router · TypeScript
4
4
 
5
- ## Getting Started
5
+ ## Overview
6
+
7
+ Modern Next.js application with TypeScript, App Router, and professional tooling.
8
+
9
+ ## Quickstart
6
10
 
7
11
  ```bash
8
12
  npm run dev
@@ -19,7 +23,7 @@ npm run dev
19
23
  - `npm run format` — Format code with Prettier
20
24
  - `npm run typecheck` — Type check with TypeScript
21
25
 
22
- ## Project Structure
26
+ ## Architecture
23
27
 
24
28
  ```
25
29
  src/
@@ -29,12 +33,22 @@ src/
29
33
  └── lib/ # Library code (providers, etc.)
30
34
  ```
31
35
 
36
+ See [ADR documentation](docs/adr/) for architectural decisions.
37
+
32
38
  ## Standards
33
39
 
34
40
  This project follows the [@oss-ma/tpl](https://www.npmjs.com/package/@oss-ma/tpl) standard.
35
41
 
36
42
  Run `npx @oss-ma/tpl check` to validate compliance.
37
43
 
44
+ ## Contributing
45
+
46
+ 1. Fork the repository
47
+ 2. Create a feature branch
48
+ 3. Make your changes
49
+ 4. Run `npm run lint && npm test`
50
+ 5. Submit a pull request
51
+
38
52
  ## Security
39
53
 
40
54
  See [SECURITY.md](SECURITY.md) for reporting vulnerabilities.