tempora-cli 0.1.1 → 0.1.3
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 +47 -0
- package/dist/index.js +372 -0
- package/dist/registry.json +93 -0
- package/package.json +3 -2
- package/registry.json +93 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# tempora-cli
|
|
2
|
+
|
|
3
|
+
A fast, language-agnostic CLI that bootstraps your project from a curated vault of community templates — no setup, no guesswork.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g tempora-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
**Guided mode:**
|
|
14
|
+
```bash
|
|
15
|
+
tempora init
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Direct mode:**
|
|
19
|
+
```bash
|
|
20
|
+
tempora init next-tailwind my-app
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Scaffold into current folder:**
|
|
24
|
+
```bash
|
|
25
|
+
tempora init next-tailwind .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Browse template info:**
|
|
29
|
+
```bash
|
|
30
|
+
tempora info next-tailwind
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Requirements
|
|
34
|
+
|
|
35
|
+
- Node.js 18+
|
|
36
|
+
- git
|
|
37
|
+
- npm
|
|
38
|
+
|
|
39
|
+
## Links
|
|
40
|
+
|
|
41
|
+
- [Documentation](https://tempora.vercel.app)
|
|
42
|
+
- [GitHub](https://github.com/DidIrb/tempora)
|
|
43
|
+
- [npm](https://www.npmjs.com/package/tempora-cli)
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
MIT © Tempora
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { createRequire as createRequire2 } from "module";
|
|
6
|
+
|
|
7
|
+
// src/commands/init.ts
|
|
8
|
+
import ora from "ora";
|
|
9
|
+
|
|
10
|
+
// src/utils/logger.ts
|
|
11
|
+
import pc from "picocolors";
|
|
12
|
+
var logger = {
|
|
13
|
+
info: (msg) => console.log(pc.cyan(" info ") + msg),
|
|
14
|
+
success: (msg) => console.log(pc.green(" \u2714 ") + msg),
|
|
15
|
+
warn: (msg) => console.log(pc.yellow(" warn ") + msg),
|
|
16
|
+
error: (msg) => console.error(pc.red(" \u2716 ") + msg),
|
|
17
|
+
log: (msg) => console.log(" " + msg)
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// src/utils/versionCheck.ts
|
|
21
|
+
import { createRequire } from "module";
|
|
22
|
+
import pc2 from "picocolors";
|
|
23
|
+
var require2 = createRequire(import.meta.url);
|
|
24
|
+
var { version: current } = require2("../package.json");
|
|
25
|
+
async function checkVersion() {
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch("https://registry.npmjs.org/tempora-cli/latest", {
|
|
28
|
+
signal: AbortSignal.timeout(3e3)
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok) return;
|
|
31
|
+
const { version: latest } = await res.json();
|
|
32
|
+
if (!current || !latest || current === latest) return;
|
|
33
|
+
const [curMajor, curMinor] = current.split(".").map(Number);
|
|
34
|
+
const [latMajor, latMinor] = latest.split(".").map(Number);
|
|
35
|
+
const isMinorOrMajor = latMajor > curMajor || latMajor === curMajor && latMinor > curMinor;
|
|
36
|
+
if (!isMinorOrMajor) return;
|
|
37
|
+
console.log("");
|
|
38
|
+
console.log(" " + pc2.bgYellow(pc2.black(" UPDATE ")) + " " + pc2.dim(current) + " \u2192 " + pc2.green(pc2.bold(latest)));
|
|
39
|
+
console.log(" " + pc2.dim("Run: ") + pc2.cyan("npm install -g tempora-cli"));
|
|
40
|
+
console.log(" " + pc2.dim("https://www.npmjs.com/package/tempora-cli"));
|
|
41
|
+
console.log("");
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/utils/antiOverwrite.ts
|
|
47
|
+
import fs from "fs";
|
|
48
|
+
import path from "path";
|
|
49
|
+
import inquirer from "inquirer";
|
|
50
|
+
async function resolveTargetDir(directory) {
|
|
51
|
+
const target = path.resolve(process.cwd(), directory ?? ".");
|
|
52
|
+
if (!fs.existsSync(target)) {
|
|
53
|
+
return { targetDir: target, overwrite: false };
|
|
54
|
+
}
|
|
55
|
+
const entries = fs.readdirSync(target).filter((f) => f !== ".git" && f !== ".DS_Store");
|
|
56
|
+
if (entries.length === 0) {
|
|
57
|
+
return { targetDir: target, overwrite: false };
|
|
58
|
+
}
|
|
59
|
+
logger.warn(`Directory "${path.basename(target)}" is not empty.`);
|
|
60
|
+
const { proceed } = await inquirer.prompt([
|
|
61
|
+
{
|
|
62
|
+
type: "confirm",
|
|
63
|
+
name: "proceed",
|
|
64
|
+
message: "Proceeding may overwrite existing files. Continue?",
|
|
65
|
+
default: false
|
|
66
|
+
}
|
|
67
|
+
]);
|
|
68
|
+
if (!proceed) {
|
|
69
|
+
logger.error("Aborted. No files were changed.");
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return { targetDir: target, overwrite: true };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/utils/downloader.ts
|
|
76
|
+
import fs2 from "fs";
|
|
77
|
+
import path2 from "path";
|
|
78
|
+
import { execSync } from "child_process";
|
|
79
|
+
import { fileURLToPath } from "url";
|
|
80
|
+
|
|
81
|
+
// src/config.ts
|
|
82
|
+
var org = "DidIrb";
|
|
83
|
+
var repo = "tempora";
|
|
84
|
+
var branch = "main";
|
|
85
|
+
var config = {
|
|
86
|
+
github: {
|
|
87
|
+
org,
|
|
88
|
+
repo,
|
|
89
|
+
branch,
|
|
90
|
+
apiBase: `https://api.github.com/repos/${org}/${repo}/contents`,
|
|
91
|
+
rawBase: `https://raw.githubusercontent.com/${org}/${repo}/${branch}`,
|
|
92
|
+
registryUrl: `https://raw.githubusercontent.com/${org}/${repo}/${branch}/registry.json`
|
|
93
|
+
},
|
|
94
|
+
docs: {
|
|
95
|
+
url: "https://tempora.dev/templates"
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// src/utils/downloader.ts
|
|
100
|
+
var __dirname = path2.dirname(fileURLToPath(import.meta.url));
|
|
101
|
+
function findLocalTemplatesDir() {
|
|
102
|
+
let current2 = __dirname;
|
|
103
|
+
for (let i = 0; i < 6; i++) {
|
|
104
|
+
const candidate = path2.join(current2, "templates");
|
|
105
|
+
if (fs2.existsSync(candidate)) return candidate;
|
|
106
|
+
current2 = path2.dirname(current2);
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function copyDirLocal(srcDir, destDir, overwrite) {
|
|
111
|
+
fs2.mkdirSync(destDir, { recursive: true });
|
|
112
|
+
for (const entry of fs2.readdirSync(srcDir, { withFileTypes: true })) {
|
|
113
|
+
const srcPath = path2.join(srcDir, entry.name);
|
|
114
|
+
const destPath = path2.join(destDir, entry.name);
|
|
115
|
+
if (entry.isDirectory()) {
|
|
116
|
+
copyDirLocal(srcPath, destPath, overwrite);
|
|
117
|
+
} else {
|
|
118
|
+
if (!overwrite && fs2.existsSync(destPath)) continue;
|
|
119
|
+
fs2.copyFileSync(srcPath, destPath);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function cloneTemplateSparse(templatePath, targetDir, overwrite) {
|
|
124
|
+
const repoUrl = `https://github.com/${config.github.org}/${config.github.repo}.git`;
|
|
125
|
+
const tmpDir = path2.join(targetDir, ".tempora-tmp");
|
|
126
|
+
try {
|
|
127
|
+
execSync("git --version", { stdio: "pipe" });
|
|
128
|
+
} catch {
|
|
129
|
+
throw new Error(
|
|
130
|
+
"git is not installed or not available on your PATH.\nTempora requires git to download templates. Install it from https://git-scm.com and try again."
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
fs2.mkdirSync(tmpDir, { recursive: true });
|
|
135
|
+
try {
|
|
136
|
+
execSync(`git clone --filter=blob:none --sparse --depth=1 ${repoUrl} .`, {
|
|
137
|
+
cwd: tmpDir,
|
|
138
|
+
stdio: "pipe"
|
|
139
|
+
});
|
|
140
|
+
} catch {
|
|
141
|
+
throw new Error(
|
|
142
|
+
"Could not connect to GitHub to download the template.\nCheck your internet connection and try again."
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
execSync(`git sparse-checkout set ${templatePath}`, {
|
|
147
|
+
cwd: tmpDir,
|
|
148
|
+
stdio: "pipe"
|
|
149
|
+
});
|
|
150
|
+
} catch {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Failed to checkout template path "${templatePath}" from the repository.
|
|
153
|
+
The template may have been moved or removed. Run "tempora init" to browse available templates.`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const clonedTemplatePath = path2.join(tmpDir, templatePath);
|
|
157
|
+
if (!fs2.existsSync(clonedTemplatePath)) {
|
|
158
|
+
throw new Error(`Template path "${templatePath}" not found in repository.`);
|
|
159
|
+
}
|
|
160
|
+
copyDirLocal(clonedTemplatePath, targetDir, overwrite);
|
|
161
|
+
} finally {
|
|
162
|
+
fs2.rmSync(tmpDir, { recursive: true, force: true });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function downloadTemplate(template, targetDir, overwrite, spinner) {
|
|
166
|
+
const localTemplatesDir = findLocalTemplatesDir();
|
|
167
|
+
if (localTemplatesDir) {
|
|
168
|
+
if (spinner) spinner.text = "Copying template from local...";
|
|
169
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
170
|
+
const relativeParts = template.path.replace(/^templates\//, "").split("/");
|
|
171
|
+
const localSrc = path2.join(localTemplatesDir, ...relativeParts);
|
|
172
|
+
if (fs2.existsSync(localSrc)) {
|
|
173
|
+
copyDirLocal(localSrc, targetDir, overwrite);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (spinner) spinner.text = `Downloading ${template.name} from GitHub...`;
|
|
178
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
179
|
+
cloneTemplateSparse(template.path, targetDir, overwrite);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/utils/postInstall.ts
|
|
183
|
+
import path3 from "path";
|
|
184
|
+
var DEFAULT_NEXT_STEPS = ["pnpm install", "pnpm dev"];
|
|
185
|
+
function printPostInstall(template, targetDir) {
|
|
186
|
+
const isCwd = path3.resolve(targetDir) === path3.resolve(process.cwd());
|
|
187
|
+
const dirName = isCwd ? null : path3.basename(targetDir);
|
|
188
|
+
const steps = template.nextSteps ?? DEFAULT_NEXT_STEPS;
|
|
189
|
+
logger.log(`
|
|
190
|
+
${template.description}
|
|
191
|
+
`);
|
|
192
|
+
logger.log(" Next steps:\n");
|
|
193
|
+
if (dirName) {
|
|
194
|
+
logger.log(` cd ${dirName}`);
|
|
195
|
+
}
|
|
196
|
+
for (const step of steps) {
|
|
197
|
+
logger.log(` ${step}`);
|
|
198
|
+
}
|
|
199
|
+
logger.log("");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/utils/registry.ts
|
|
203
|
+
import fs3 from "fs";
|
|
204
|
+
import path4 from "path";
|
|
205
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
206
|
+
var __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
|
|
207
|
+
function loadRegistry() {
|
|
208
|
+
const registryPath = path4.resolve(__dirname2, "./registry.json");
|
|
209
|
+
if (!fs3.existsSync(registryPath)) {
|
|
210
|
+
throw new Error("Registry not found. Please rebuild the CLI with npm run build.");
|
|
211
|
+
}
|
|
212
|
+
const raw = fs3.readFileSync(registryPath, "utf-8");
|
|
213
|
+
return JSON.parse(raw);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/utils/guided.ts
|
|
217
|
+
import inquirer2 from "inquirer";
|
|
218
|
+
var MAX_RESULTS = 4;
|
|
219
|
+
async function runGuidedSelection(registry) {
|
|
220
|
+
const languages = Object.keys(registry.byLanguage);
|
|
221
|
+
if (languages.length === 0) {
|
|
222
|
+
logger.error("No templates found in registry.");
|
|
223
|
+
logger.log(`Browse templates at ${config.docs.url}`);
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
const { language } = await inquirer2.prompt([{
|
|
227
|
+
type: "list",
|
|
228
|
+
name: "language",
|
|
229
|
+
message: "What language?",
|
|
230
|
+
choices: languages
|
|
231
|
+
}]);
|
|
232
|
+
const languageIds = registry.byLanguage[language] ?? [];
|
|
233
|
+
const availableCategories = Object.entries(registry.byCategory).filter(([, ids]) => ids.some((id) => languageIds.includes(id))).map(([cat]) => cat);
|
|
234
|
+
if (availableCategories.length === 0) {
|
|
235
|
+
logger.error(`No templates found for language: ${language}`);
|
|
236
|
+
logger.log(`Browse templates at ${config.docs.url}`);
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
const { category } = await inquirer2.prompt([{
|
|
240
|
+
type: "list",
|
|
241
|
+
name: "category",
|
|
242
|
+
message: "What category?",
|
|
243
|
+
choices: availableCategories
|
|
244
|
+
}]);
|
|
245
|
+
const categoryIds = registry.byCategory[category] ?? [];
|
|
246
|
+
const afterCategory = languageIds.filter((id) => categoryIds.includes(id));
|
|
247
|
+
const availableLibraries = Object.entries(registry.byLibrary).filter(([, ids]) => ids.some((id) => afterCategory.includes(id))).map(([lib]) => lib);
|
|
248
|
+
if (availableLibraries.length === 0) {
|
|
249
|
+
logger.error(`No templates found for ${language} / ${category}`);
|
|
250
|
+
logger.log(`Browse templates at ${config.docs.url}`);
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
const { library } = await inquirer2.prompt([{
|
|
254
|
+
type: "list",
|
|
255
|
+
name: "library",
|
|
256
|
+
message: "What library or framework?",
|
|
257
|
+
choices: availableLibraries
|
|
258
|
+
}]);
|
|
259
|
+
const libraryIds = registry.byLibrary[library] ?? [];
|
|
260
|
+
const matchedIds = afterCategory.filter((id) => libraryIds.includes(id));
|
|
261
|
+
if (matchedIds.length === 0) {
|
|
262
|
+
logger.error(`No templates found for ${language} / ${category} / ${library}`);
|
|
263
|
+
logger.log(`Browse templates at ${config.docs.url}`);
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
const hasMore = matchedIds.length > MAX_RESULTS;
|
|
267
|
+
const visibleIds = matchedIds.slice(0, MAX_RESULTS);
|
|
268
|
+
const choices = visibleIds.map((id) => ({
|
|
269
|
+
name: `${registry.templates[id].name} \u2014 ${registry.templates[id].description}`,
|
|
270
|
+
value: id
|
|
271
|
+
}));
|
|
272
|
+
if (hasMore) {
|
|
273
|
+
logger.log(`
|
|
274
|
+
Showing ${MAX_RESULTS} of ${matchedIds.length} templates.`);
|
|
275
|
+
logger.log(` See all at ${config.docs.url}
|
|
276
|
+
`);
|
|
277
|
+
}
|
|
278
|
+
const { templateId } = await inquirer2.prompt([{
|
|
279
|
+
type: "list",
|
|
280
|
+
name: "templateId",
|
|
281
|
+
message: "Pick a template:",
|
|
282
|
+
choices
|
|
283
|
+
}]);
|
|
284
|
+
return registry.templates[templateId] ?? null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/commands/init.ts
|
|
288
|
+
function registerInitCommand(program2) {
|
|
289
|
+
program2.command("init [template] [directory]").description("Scaffold a new project from a Tempora template").action(async (template, directory) => {
|
|
290
|
+
const spinner = ora();
|
|
291
|
+
try {
|
|
292
|
+
const registry = loadRegistry();
|
|
293
|
+
let resolvedTemplate = template;
|
|
294
|
+
let resolvedDirectory = directory;
|
|
295
|
+
if (!resolvedTemplate) {
|
|
296
|
+
const entry2 = await runGuidedSelection(registry);
|
|
297
|
+
if (!entry2) return;
|
|
298
|
+
resolvedTemplate = entry2.id;
|
|
299
|
+
}
|
|
300
|
+
const entry = registry.templates[resolvedTemplate];
|
|
301
|
+
if (!entry) {
|
|
302
|
+
logger.error(`Template "${resolvedTemplate}" not found.`);
|
|
303
|
+
logger.log(`Browse all templates at ${config.docs.url}`);
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
const result = await resolveTargetDir(resolvedDirectory);
|
|
307
|
+
if (!result) return;
|
|
308
|
+
const { targetDir, overwrite } = result;
|
|
309
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
310
|
+
spinner.start();
|
|
311
|
+
await downloadTemplate(entry, targetDir, overwrite, spinner);
|
|
312
|
+
spinner.succeed(`${entry.name} scaffolded successfully!`);
|
|
313
|
+
printPostInstall(entry, targetDir);
|
|
314
|
+
await checkVersion();
|
|
315
|
+
} catch (err) {
|
|
316
|
+
spinner.stop();
|
|
317
|
+
logger.error(err instanceof Error ? err.message : "Something went wrong.");
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/commands/info.ts
|
|
324
|
+
function registerInfoCommand(program2) {
|
|
325
|
+
program2.command("info <template>").description("Show details about a specific template").action(async (template) => {
|
|
326
|
+
try {
|
|
327
|
+
const registry = loadRegistry();
|
|
328
|
+
const entry = registry.templates[template];
|
|
329
|
+
if (!entry) {
|
|
330
|
+
logger.error(`Template "${template}" not found.`);
|
|
331
|
+
logger.log("Run tempora init to browse available templates.");
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
logger.log("");
|
|
335
|
+
logger.log(` ${entry.name}`);
|
|
336
|
+
logger.log(` ${entry.description}`);
|
|
337
|
+
logger.log("");
|
|
338
|
+
logger.log(` Language ${entry.language}`);
|
|
339
|
+
logger.log(` Category ${entry.category}`);
|
|
340
|
+
logger.log(` Library ${entry.library}`);
|
|
341
|
+
logger.log(` Version ${entry.version}`);
|
|
342
|
+
logger.log(` Tags ${entry.tags.join(", ")}`);
|
|
343
|
+
logger.log("");
|
|
344
|
+
if (entry.nextSteps && entry.nextSteps.length > 0) {
|
|
345
|
+
logger.log(" Next steps after scaffolding:");
|
|
346
|
+
for (const step of entry.nextSteps) {
|
|
347
|
+
logger.log(` ${step}`);
|
|
348
|
+
}
|
|
349
|
+
logger.log("");
|
|
350
|
+
}
|
|
351
|
+
await checkVersion();
|
|
352
|
+
} catch (err) {
|
|
353
|
+
logger.error(err instanceof Error ? err.message : "Something went wrong.");
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/index.ts
|
|
360
|
+
var require3 = createRequire2(import.meta.url);
|
|
361
|
+
var { version } = require3("../package.json");
|
|
362
|
+
var program = new Command();
|
|
363
|
+
program.name("tempora").description("Scaffold projects from curated templates").version(version, "-v, --version", "Show the current version").addHelpText("after", `
|
|
364
|
+
Examples:
|
|
365
|
+
$ tempora init next-tailwind my-app
|
|
366
|
+
$ tempora init next-tailwind .
|
|
367
|
+
$ tempora init
|
|
368
|
+
$ tempora info next-tailwind
|
|
369
|
+
`);
|
|
370
|
+
registerInitCommand(program);
|
|
371
|
+
registerInfoCommand(program);
|
|
372
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"updatedAt": "2026-06-28T01:10:10.949Z",
|
|
4
|
+
"templates": {
|
|
5
|
+
"angular-starter": {
|
|
6
|
+
"id": "angular-starter",
|
|
7
|
+
"name": "Angular Starter",
|
|
8
|
+
"language": "typescript",
|
|
9
|
+
"category": "frontend",
|
|
10
|
+
"library": "angular",
|
|
11
|
+
"description": "Angular 17 standalone components with TypeScript and Angular CLI preconfigured.",
|
|
12
|
+
"tags": [
|
|
13
|
+
"angular",
|
|
14
|
+
"typescript",
|
|
15
|
+
"frontend",
|
|
16
|
+
"spa"
|
|
17
|
+
],
|
|
18
|
+
"version": "1.0.0",
|
|
19
|
+
"nextSteps": [
|
|
20
|
+
"npm install",
|
|
21
|
+
"ng serve"
|
|
22
|
+
],
|
|
23
|
+
"path": "templates/typescript/frontend/angular/angular-starter"
|
|
24
|
+
},
|
|
25
|
+
"next-tailwind": {
|
|
26
|
+
"id": "next-tailwind",
|
|
27
|
+
"name": "Next.js + Tailwind",
|
|
28
|
+
"language": "typescript",
|
|
29
|
+
"category": "frontend",
|
|
30
|
+
"library": "nextjs",
|
|
31
|
+
"description": "Next.js 14 app router with Tailwind CSS and Prettier preconfigured.",
|
|
32
|
+
"tags": [
|
|
33
|
+
"nextjs",
|
|
34
|
+
"tailwind",
|
|
35
|
+
"typescript",
|
|
36
|
+
"react"
|
|
37
|
+
],
|
|
38
|
+
"version": "1.0.0",
|
|
39
|
+
"nextSteps": [
|
|
40
|
+
"pnpm install",
|
|
41
|
+
"pnpm dev"
|
|
42
|
+
],
|
|
43
|
+
"path": "templates/typescript/frontend/nextjs/next-tailwind"
|
|
44
|
+
},
|
|
45
|
+
"nextjs-tailwind": {
|
|
46
|
+
"id": "nextjs-tailwind",
|
|
47
|
+
"name": "Next.js + Tailwind",
|
|
48
|
+
"language": "typescript",
|
|
49
|
+
"category": "fullstack",
|
|
50
|
+
"library": "tailwind",
|
|
51
|
+
"description": "Next.js 14 app router with Tailwind CSS and Prettier preconfigured.",
|
|
52
|
+
"tags": [
|
|
53
|
+
"nextjs",
|
|
54
|
+
"tailwind",
|
|
55
|
+
"typescript",
|
|
56
|
+
"react"
|
|
57
|
+
],
|
|
58
|
+
"version": "1.0.0",
|
|
59
|
+
"nextSteps": [
|
|
60
|
+
"pnpm install",
|
|
61
|
+
"pnpm dev"
|
|
62
|
+
],
|
|
63
|
+
"path": "templates/typescript/nextjs-tailwind"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"byLanguage": {
|
|
67
|
+
"typescript": [
|
|
68
|
+
"angular-starter",
|
|
69
|
+
"next-tailwind",
|
|
70
|
+
"nextjs-tailwind"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
"byCategory": {
|
|
74
|
+
"frontend": [
|
|
75
|
+
"angular-starter",
|
|
76
|
+
"next-tailwind"
|
|
77
|
+
],
|
|
78
|
+
"fullstack": [
|
|
79
|
+
"nextjs-tailwind"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
"byLibrary": {
|
|
83
|
+
"angular": [
|
|
84
|
+
"angular-starter"
|
|
85
|
+
],
|
|
86
|
+
"nextjs": [
|
|
87
|
+
"next-tailwind"
|
|
88
|
+
],
|
|
89
|
+
"tailwind": [
|
|
90
|
+
"nextjs-tailwind"
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tempora-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Tempora CLI — scaffold projects from curated templates",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"bin",
|
|
16
16
|
"dist",
|
|
17
|
-
"registry.json"
|
|
17
|
+
"registry.json",
|
|
18
|
+
"README.md"
|
|
18
19
|
],
|
|
19
20
|
"dependencies": {
|
|
20
21
|
"chalk": "^5.3.0",
|
package/registry.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"updatedAt": "2026-06-28T01:10:10.949Z",
|
|
4
|
+
"templates": {
|
|
5
|
+
"angular-starter": {
|
|
6
|
+
"id": "angular-starter",
|
|
7
|
+
"name": "Angular Starter",
|
|
8
|
+
"language": "typescript",
|
|
9
|
+
"category": "frontend",
|
|
10
|
+
"library": "angular",
|
|
11
|
+
"description": "Angular 17 standalone components with TypeScript and Angular CLI preconfigured.",
|
|
12
|
+
"tags": [
|
|
13
|
+
"angular",
|
|
14
|
+
"typescript",
|
|
15
|
+
"frontend",
|
|
16
|
+
"spa"
|
|
17
|
+
],
|
|
18
|
+
"version": "1.0.0",
|
|
19
|
+
"nextSteps": [
|
|
20
|
+
"npm install",
|
|
21
|
+
"ng serve"
|
|
22
|
+
],
|
|
23
|
+
"path": "templates/typescript/frontend/angular/angular-starter"
|
|
24
|
+
},
|
|
25
|
+
"next-tailwind": {
|
|
26
|
+
"id": "next-tailwind",
|
|
27
|
+
"name": "Next.js + Tailwind",
|
|
28
|
+
"language": "typescript",
|
|
29
|
+
"category": "frontend",
|
|
30
|
+
"library": "nextjs",
|
|
31
|
+
"description": "Next.js 14 app router with Tailwind CSS and Prettier preconfigured.",
|
|
32
|
+
"tags": [
|
|
33
|
+
"nextjs",
|
|
34
|
+
"tailwind",
|
|
35
|
+
"typescript",
|
|
36
|
+
"react"
|
|
37
|
+
],
|
|
38
|
+
"version": "1.0.0",
|
|
39
|
+
"nextSteps": [
|
|
40
|
+
"pnpm install",
|
|
41
|
+
"pnpm dev"
|
|
42
|
+
],
|
|
43
|
+
"path": "templates/typescript/frontend/nextjs/next-tailwind"
|
|
44
|
+
},
|
|
45
|
+
"nextjs-tailwind": {
|
|
46
|
+
"id": "nextjs-tailwind",
|
|
47
|
+
"name": "Next.js + Tailwind",
|
|
48
|
+
"language": "typescript",
|
|
49
|
+
"category": "fullstack",
|
|
50
|
+
"library": "tailwind",
|
|
51
|
+
"description": "Next.js 14 app router with Tailwind CSS and Prettier preconfigured.",
|
|
52
|
+
"tags": [
|
|
53
|
+
"nextjs",
|
|
54
|
+
"tailwind",
|
|
55
|
+
"typescript",
|
|
56
|
+
"react"
|
|
57
|
+
],
|
|
58
|
+
"version": "1.0.0",
|
|
59
|
+
"nextSteps": [
|
|
60
|
+
"pnpm install",
|
|
61
|
+
"pnpm dev"
|
|
62
|
+
],
|
|
63
|
+
"path": "templates/typescript/nextjs-tailwind"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"byLanguage": {
|
|
67
|
+
"typescript": [
|
|
68
|
+
"angular-starter",
|
|
69
|
+
"next-tailwind",
|
|
70
|
+
"nextjs-tailwind"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
"byCategory": {
|
|
74
|
+
"frontend": [
|
|
75
|
+
"angular-starter",
|
|
76
|
+
"next-tailwind"
|
|
77
|
+
],
|
|
78
|
+
"fullstack": [
|
|
79
|
+
"nextjs-tailwind"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
"byLibrary": {
|
|
83
|
+
"angular": [
|
|
84
|
+
"angular-starter"
|
|
85
|
+
],
|
|
86
|
+
"nextjs": [
|
|
87
|
+
"next-tailwind"
|
|
88
|
+
],
|
|
89
|
+
"tailwind": [
|
|
90
|
+
"nextjs-tailwind"
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
}
|