@ykdz/template 0.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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +353 -0
  6. package/dist/declarations.d.ts +166 -0
  7. package/dist/declarations.d.ts.map +1 -0
  8. package/dist/declarations.js +340 -0
  9. package/dist/hono-api.d.ts +2 -0
  10. package/dist/hono-api.d.ts.map +1 -0
  11. package/dist/hono-api.js +247 -0
  12. package/dist/package-addition.d.ts +7 -0
  13. package/dist/package-addition.d.ts.map +1 -0
  14. package/dist/package-addition.js +580 -0
  15. package/dist/renderer.d.ts +44 -0
  16. package/dist/renderer.d.ts.map +1 -0
  17. package/dist/renderer.js +379 -0
  18. package/dist/rust-bin.d.ts +2 -0
  19. package/dist/rust-bin.d.ts.map +1 -0
  20. package/dist/rust-bin.js +206 -0
  21. package/dist/ts-lib.d.ts +2 -0
  22. package/dist/ts-lib.d.ts.map +1 -0
  23. package/dist/ts-lib.js +220 -0
  24. package/dist/vue-app.d.ts +2 -0
  25. package/dist/vue-app.d.ts.map +1 -0
  26. package/dist/vue-app.js +339 -0
  27. package/dist/vue-hono-app.d.ts +4 -0
  28. package/dist/vue-hono-app.d.ts.map +1 -0
  29. package/dist/vue-hono-app.js +484 -0
  30. package/package.json +54 -0
  31. package/templates/hono-api/src/app.ts +5 -0
  32. package/templates/hono-api/src/server.ts +14 -0
  33. package/templates/hono-api/test/app.test.ts +10 -0
  34. package/templates/hono-api/vitest.config.ts +13 -0
  35. package/templates/rust-bin/src/main.rs +18 -0
  36. package/templates/ts-lib/src/index.ts +7 -0
  37. package/templates/vue-app/env.d.ts +1 -0
  38. package/templates/vue-app/index.html +12 -0
  39. package/templates/vue-app/playwright.config.ts +20 -0
  40. package/templates/vue-app/src/App.vue +33 -0
  41. package/templates/vue-app/src/main.ts +6 -0
  42. package/templates/vue-app/src/stores/counter.ts +12 -0
  43. package/templates/vue-app/src/style.css +1 -0
  44. package/templates/vue-app/test/app.test.ts +13 -0
  45. package/templates/vue-app/test/e2e/app.spec.ts +9 -0
  46. package/templates/vue-app/vite.config.ts +13 -0
  47. package/templates/vue-app/vitest.config.ts +14 -0
  48. package/templates/vue-hono-app/api/src/index.ts +3 -0
  49. package/templates/vue-hono-app/api/src/runtime.ts +5 -0
  50. package/templates/vue-hono-app/api/src/server.ts +14 -0
  51. package/templates/vue-hono-app/api/test/app.test.ts +10 -0
  52. package/templates/vue-hono-app/api/vitest.config.ts +7 -0
  53. package/templates/vue-hono-app/web/env.d.ts +9 -0
  54. package/templates/vue-hono-app/web/index.html +12 -0
  55. package/templates/vue-hono-app/web/playwright.config.ts +21 -0
  56. package/templates/vue-hono-app/web/src/App.vue +42 -0
  57. package/templates/vue-hono-app/web/src/api.ts +8 -0
  58. package/templates/vue-hono-app/web/src/main.ts +6 -0
  59. package/templates/vue-hono-app/web/src/stores/counter.ts +12 -0
  60. package/templates/vue-hono-app/web/src/style.css +1 -0
  61. package/templates/vue-hono-app/web/test/app.test.ts +13 -0
  62. package/templates/vue-hono-app/web/test/e2e/app.spec.ts +10 -0
  63. package/templates/vue-hono-app/web/vite.config.ts +29 -0
  64. package/templates/vue-hono-app/web/vitest.config.ts +16 -0
@@ -0,0 +1,379 @@
1
+ import { constants } from "node:fs";
2
+ import { chmod, copyFile, mkdir, mkdtemp, readFile, readdir, rename, rm, rmdir, stat, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ function expandTemplatePath(templatePath, variables) {
5
+ return templatePath.replaceAll(/\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/g, (_, name) => {
6
+ const value = variables[name];
7
+ if (!value) {
8
+ throw new Error(`Missing renderer variable: ${name}`);
9
+ }
10
+ if (!/^[A-Za-z0-9._-]+$/.test(value)) {
11
+ throw new Error(`Renderer variable ${name} is not safe for a path segment`);
12
+ }
13
+ return value;
14
+ });
15
+ }
16
+ function resolveContainedPath(root, relativePath) {
17
+ if (path.isAbsolute(relativePath)) {
18
+ throw new Error(`Renderer paths must be relative: ${relativePath}`);
19
+ }
20
+ const resolvedRoot = path.resolve(root);
21
+ const resolvedPath = path.resolve(resolvedRoot, relativePath);
22
+ const insideRoot = resolvedPath === resolvedRoot || resolvedPath.startsWith(`${resolvedRoot}${path.sep}`);
23
+ if (!insideRoot) {
24
+ throw new Error(`Renderer path escapes its root: ${relativePath}`);
25
+ }
26
+ return resolvedPath;
27
+ }
28
+ function expandOperationPath(relativePath, options) {
29
+ return expandTemplatePath(relativePath, options.variables ?? {});
30
+ }
31
+ async function renderCopyFile(operation, options) {
32
+ const variables = options.variables ?? {};
33
+ const from = resolveContainedPath(options.sourceRoot, expandTemplatePath(operation.from, variables));
34
+ const to = resolveContainedPath(options.targetRoot, expandTemplatePath(operation.to, variables));
35
+ const sourceMode = (await stat(from)).mode;
36
+ await mkdir(path.dirname(to), { recursive: true });
37
+ await copyGeneratedFile(from, to);
38
+ await chmod(to, sourceMode & 0o777);
39
+ }
40
+ function isRecord(value) {
41
+ return typeof value === "object" && value !== null && !Array.isArray(value);
42
+ }
43
+ function isScalarJsonValue(value) {
44
+ return value === null || ["boolean", "number", "string"].includes(typeof value);
45
+ }
46
+ function serializeJsonValue(value, indentation, pathSegments, multilineArrays, rootKeyOrder) {
47
+ if (Array.isArray(value)) {
48
+ const compact = `[${value.map((item) => JSON.stringify(item)).join(", ")}]`;
49
+ const shouldCompact = value.every(isScalarJsonValue) &&
50
+ !multilineArrays.has(pathSegments.join(".")) &&
51
+ indentation + compact.length <= 100;
52
+ if (shouldCompact) {
53
+ return compact;
54
+ }
55
+ const items = value.map((item, index) => `${" ".repeat(indentation + 2)}${serializeJsonValue(item, indentation + 2, [...pathSegments, String(index)], multilineArrays, rootKeyOrder)}`);
56
+ return `[\n${items.join(",\n")}\n${" ".repeat(indentation)}]`;
57
+ }
58
+ if (!isRecord(value)) {
59
+ return JSON.stringify(value);
60
+ }
61
+ const entries = Object.entries(value).sort(([left], [right]) => compareJsonKeys(left, right, pathSegments, rootKeyOrder));
62
+ if (entries.length === 0) {
63
+ return "{}";
64
+ }
65
+ return ("{\n" +
66
+ entries
67
+ .map(([key, entryValue]) => `${" ".repeat(indentation + 2)}${JSON.stringify(key)}: ${serializeJsonValue(entryValue, indentation + 2, [...pathSegments, key], multilineArrays, rootKeyOrder)}`)
68
+ .join(",\n") +
69
+ `\n${" ".repeat(indentation)}}`);
70
+ }
71
+ const packageJsonRootKeyOrder = [
72
+ "name",
73
+ "version",
74
+ "private",
75
+ "files",
76
+ "type",
77
+ "types",
78
+ "exports",
79
+ "scripts",
80
+ "dependencies",
81
+ "devDependencies",
82
+ "peerDependencies",
83
+ "optionalDependencies",
84
+ "engines",
85
+ "packageManager"
86
+ ];
87
+ function compareJsonKeys(left, right, pathSegments, rootKeyOrder) {
88
+ if (pathSegments.length === 0 && rootKeyOrder) {
89
+ const leftOrder = rootKeyOrder.get(left) ?? Number.POSITIVE_INFINITY;
90
+ const rightOrder = rootKeyOrder.get(right) ?? Number.POSITIVE_INFINITY;
91
+ if (leftOrder !== rightOrder) {
92
+ return leftOrder - rightOrder;
93
+ }
94
+ }
95
+ return left.localeCompare(right);
96
+ }
97
+ function rootKeyOrderForPath(toPath) {
98
+ if (path.basename(toPath) !== "package.json") {
99
+ return undefined;
100
+ }
101
+ return new Map(packageJsonRootKeyOrder.map((key, index) => [key, index]));
102
+ }
103
+ function serializeJson(value, multilineArrays = [], rootKeyOrder) {
104
+ return `${serializeJsonValue(value, 0, [], new Set(multilineArrays), rootKeyOrder)}\n`;
105
+ }
106
+ function mergeJsonValue(base, patch) {
107
+ if (!isRecord(base) || !isRecord(patch)) {
108
+ return patch;
109
+ }
110
+ const result = { ...base };
111
+ for (const [key, value] of Object.entries(patch)) {
112
+ result[key] = key in result ? mergeJsonValue(result[key], value) : value;
113
+ }
114
+ return result;
115
+ }
116
+ async function writeJsonFile(targetRoot, toPath, value, multilineArrays, overwrite = false) {
117
+ const to = resolveContainedPath(targetRoot, toPath);
118
+ await mkdir(path.dirname(to), { recursive: true });
119
+ await writeGeneratedFile(to, serializeJson(value, multilineArrays, rootKeyOrderForPath(toPath)), overwrite);
120
+ }
121
+ async function renderWriteJson(operation, options) {
122
+ await writeJsonFile(options.targetRoot, expandOperationPath(operation.to, options), operation.value, operation.multilineArrays);
123
+ }
124
+ async function renderMergeJson(operation, options) {
125
+ const toPath = expandOperationPath(operation.to, options);
126
+ const to = resolveContainedPath(options.targetRoot, toPath);
127
+ let existing = {};
128
+ try {
129
+ existing = JSON.parse(await readFile(to, "utf8"));
130
+ }
131
+ catch (error) {
132
+ if (!isNodeError(error) || error.code !== "ENOENT") {
133
+ throw error;
134
+ }
135
+ }
136
+ await writeJsonFile(options.targetRoot, toPath, mergeJsonValue(existing, operation.value), undefined, true);
137
+ }
138
+ const foundationTextFiles = new Set([
139
+ ".dockerignore",
140
+ ".env.example",
141
+ ".gitattributes",
142
+ ".gitignore",
143
+ ".npmignore",
144
+ ".npmrc",
145
+ "README",
146
+ "README.md",
147
+ "LICENSE",
148
+ "CHANGELOG.md"
149
+ ]);
150
+ function assertFoundationTextPath(relativePath) {
151
+ const normalizedPath = relativePath.split(path.sep).join("/");
152
+ const isRootLevel = !normalizedPath.includes("/");
153
+ if (isRootLevel && foundationTextFiles.has(normalizedPath)) {
154
+ return;
155
+ }
156
+ if (isRootLevel && relativePath.endsWith(".md")) {
157
+ return;
158
+ }
159
+ if (isRootLevel && (relativePath.endsWith(".yaml") || relativePath.endsWith(".yml"))) {
160
+ return;
161
+ }
162
+ if (isRootLevel &&
163
+ ["Cargo.toml", "Cargo.lock", "rustfmt.toml"].includes(normalizedPath)) {
164
+ return;
165
+ }
166
+ if (normalizedPath === "scripts/check") {
167
+ return;
168
+ }
169
+ if (/^\.github\/workflows\/[A-Za-z0-9._-]+\.ya?ml$/.test(normalizedPath)) {
170
+ return;
171
+ }
172
+ if (/^\.github\/dependabot\.ya?ml$/.test(normalizedPath)) {
173
+ return;
174
+ }
175
+ throw new Error(`Text output is limited to foundation files: ${relativePath}`);
176
+ }
177
+ async function renderWriteText(operation, options) {
178
+ const toPath = expandOperationPath(operation.to, options);
179
+ assertFoundationTextPath(toPath);
180
+ const to = resolveContainedPath(options.targetRoot, toPath);
181
+ await mkdir(path.dirname(to), { recursive: true });
182
+ await writeGeneratedFile(to, operation.text);
183
+ }
184
+ async function renderSetExecutable(operation, options) {
185
+ const filePath = resolveContainedPath(options.targetRoot, expandOperationPath(operation.path, options));
186
+ const currentMode = (await stat(filePath)).mode;
187
+ const executeBits = 0o111;
188
+ const mode = operation.executable
189
+ ? currentMode | executeBits
190
+ : currentMode & ~executeBits;
191
+ await chmod(filePath, mode & 0o777);
192
+ }
193
+ function parseAnchorComment(commentText) {
194
+ const singleLine = commentText.match(/^\/\/\s*@template-anchor\s+([A-Za-z][A-Za-z0-9_-]*)\s*$/);
195
+ if (singleLine) {
196
+ return singleLine[1];
197
+ }
198
+ const multiline = commentText.match(/^\/\*\s*@template-anchor\s+([A-Za-z][A-Za-z0-9_-]*)\s*\*\/$/);
199
+ return multiline?.[1];
200
+ }
201
+ async function findTypeScriptAnchorRanges(sourceText) {
202
+ const ts = await import("typescript");
203
+ const sourceFile = ts.createSourceFile("template-anchor.ts", sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
204
+ const anchors = [];
205
+ const seenRanges = new Set();
206
+ if ((sourceFile.parseDiagnostics ?? []).length > 0) {
207
+ throw new Error("Checked Transform Anchor requires valid TypeScript source");
208
+ }
209
+ function collectNodeAnchors(node) {
210
+ if (node.kind === ts.SyntaxKind.EndOfFileToken) {
211
+ return;
212
+ }
213
+ const comments = ts.getLeadingCommentRanges(sourceText, node.getFullStart()) ?? [];
214
+ for (const comment of comments) {
215
+ const name = parseAnchorComment(sourceText.slice(comment.pos, comment.end));
216
+ const rangeKey = `${comment.pos}:${comment.end}`;
217
+ if (name === undefined || seenRanges.has(rangeKey)) {
218
+ continue;
219
+ }
220
+ seenRanges.add(rangeKey);
221
+ anchors.push({
222
+ name,
223
+ start: comment.pos,
224
+ end: comment.end
225
+ });
226
+ }
227
+ ts.forEachChild(node, collectNodeAnchors);
228
+ }
229
+ ts.forEachChild(sourceFile, collectNodeAnchors);
230
+ return anchors;
231
+ }
232
+ function replaceRanges(sourceText, ranges, replacements) {
233
+ let nextText = sourceText;
234
+ for (const range of [...ranges].sort((a, b) => b.start - a.start)) {
235
+ const replacement = replacements[range.name];
236
+ if (replacement === undefined) {
237
+ continue;
238
+ }
239
+ nextText = `${nextText.slice(0, range.start)}${replacement}${nextText.slice(range.end)}`;
240
+ }
241
+ return nextText;
242
+ }
243
+ async function renderReplaceAnchors(operation, options) {
244
+ if (operation.language !== "typescript") {
245
+ throw new Error("Checked Transform Anchor only supports TypeScript");
246
+ }
247
+ const filePath = resolveContainedPath(options.targetRoot, expandOperationPath(operation.path, options));
248
+ const sourceText = await readFile(filePath, "utf8");
249
+ const ranges = await findTypeScriptAnchorRanges(sourceText);
250
+ for (const anchorName of Object.keys(operation.replacements)) {
251
+ const matches = ranges.filter((range) => range.name === anchorName);
252
+ if (matches.length === 0) {
253
+ throw new Error(`Missing Checked Transform Anchor: ${anchorName}`);
254
+ }
255
+ if (matches.length > 1) {
256
+ throw new Error(`Duplicate Checked Transform Anchor: ${anchorName}`);
257
+ }
258
+ }
259
+ await writeFile(filePath, replaceRanges(sourceText, ranges, operation.replacements), "utf8");
260
+ }
261
+ function isNodeError(error) {
262
+ return error instanceof Error && "code" in error;
263
+ }
264
+ async function targetDirectoryStatus(targetRoot) {
265
+ try {
266
+ const targetStat = await stat(targetRoot);
267
+ if (!targetStat.isDirectory()) {
268
+ throw new Error(`Target path is not a directory: ${targetRoot}`);
269
+ }
270
+ }
271
+ catch (error) {
272
+ if (isNodeError(error) && error.code === "ENOENT") {
273
+ return "missing";
274
+ }
275
+ throw error;
276
+ }
277
+ const entries = await readdir(targetRoot);
278
+ if (entries.length > 0) {
279
+ throw new Error(`Target directory is not empty: ${targetRoot}`);
280
+ }
281
+ return "empty";
282
+ }
283
+ async function copyGeneratedFile(from, to) {
284
+ try {
285
+ await copyFile(from, to, constants.COPYFILE_EXCL);
286
+ }
287
+ catch (error) {
288
+ if (isNodeError(error) && error.code === "EEXIST") {
289
+ throw new Error(`Refusing to overwrite existing file: ${to}`);
290
+ }
291
+ throw error;
292
+ }
293
+ }
294
+ async function writeGeneratedFile(to, text, overwrite = false) {
295
+ try {
296
+ await writeFile(to, text, { encoding: "utf8", flag: overwrite ? "w" : "wx" });
297
+ }
298
+ catch (error) {
299
+ if (isNodeError(error) && error.code === "EEXIST") {
300
+ throw new Error(`Refusing to overwrite existing file: ${to}`);
301
+ }
302
+ throw error;
303
+ }
304
+ }
305
+ export async function renderProject(options) {
306
+ for (const operation of options.operations) {
307
+ if (operation.kind === "copyFile") {
308
+ await renderCopyFile(operation, options);
309
+ continue;
310
+ }
311
+ if (operation.kind === "writeJson") {
312
+ await renderWriteJson(operation, options);
313
+ continue;
314
+ }
315
+ if (operation.kind === "mergeJson") {
316
+ await renderMergeJson(operation, options);
317
+ continue;
318
+ }
319
+ if (operation.kind === "writeText") {
320
+ await renderWriteText(operation, options);
321
+ continue;
322
+ }
323
+ if (operation.kind === "setExecutable") {
324
+ await renderSetExecutable(operation, options);
325
+ continue;
326
+ }
327
+ if (operation.kind === "replaceAnchors") {
328
+ await renderReplaceAnchors(operation, options);
329
+ continue;
330
+ }
331
+ throw new Error(`Unsupported renderer operation: ${operation.kind}`);
332
+ }
333
+ }
334
+ async function commitStagedProject(stagingRoot, targetRoot) {
335
+ const targetStatus = await targetDirectoryStatus(targetRoot);
336
+ if (targetStatus === "empty") {
337
+ try {
338
+ await rmdir(targetRoot);
339
+ }
340
+ catch (error) {
341
+ if (isNodeError(error) && error.code === "ENOENT") {
342
+ // The target disappeared between the emptiness check and commit.
343
+ }
344
+ else if (isNodeError(error) && error.code === "ENOTEMPTY") {
345
+ throw new Error(`Target directory is not empty: ${targetRoot}`);
346
+ }
347
+ else {
348
+ throw error;
349
+ }
350
+ }
351
+ }
352
+ try {
353
+ await rename(stagingRoot, targetRoot);
354
+ }
355
+ catch (error) {
356
+ if (isNodeError(error) &&
357
+ ["EEXIST", "ENOTEMPTY", "ENOTDIR", "EISDIR"].includes(error.code ?? "")) {
358
+ throw new Error(`Refusing to overwrite existing target: ${targetRoot}`);
359
+ }
360
+ throw error;
361
+ }
362
+ }
363
+ export async function renderNewProject(options) {
364
+ const targetRoot = path.resolve(options.targetRoot);
365
+ await targetDirectoryStatus(targetRoot);
366
+ await mkdir(path.dirname(targetRoot), { recursive: true });
367
+ const stagingRoot = await mkdtemp(path.join(path.dirname(targetRoot), `.${path.basename(targetRoot)}.template-stage-`));
368
+ let committed = false;
369
+ try {
370
+ await renderProject({ ...options, targetRoot: stagingRoot });
371
+ await commitStagedProject(stagingRoot, targetRoot);
372
+ committed = true;
373
+ }
374
+ finally {
375
+ if (!committed) {
376
+ await rm(stagingRoot, { recursive: true, force: true });
377
+ }
378
+ }
379
+ }
@@ -0,0 +1,2 @@
1
+ export declare function initRustBinProject(targetDir: string): Promise<void>;
2
+ //# sourceMappingURL=rust-bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rust-bin.d.ts","sourceRoot":"","sources":["../src/rust-bin.ts"],"names":[],"mappings":"AAgNA,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMzE"}
@@ -0,0 +1,206 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { renderNewProject } from "./renderer.js";
4
+ const features = [
5
+ "root-check",
6
+ "fix-command",
7
+ "devcontainer",
8
+ "github-actions",
9
+ "dependabot",
10
+ "rustfmt-clippy",
11
+ "cargo-test"
12
+ ];
13
+ const generatedBy = {
14
+ packageName: "@ykdz/template",
15
+ version: "0.0.0",
16
+ command: "template init --preset rust-bin"
17
+ };
18
+ function cargoPackageNameFromDir(targetDir) {
19
+ const directoryName = path.basename(path.resolve(targetDir));
20
+ const slug = directoryName
21
+ .normalize("NFKD")
22
+ .replace(/[\u0300-\u036f]/g, "")
23
+ .toLowerCase()
24
+ .replace(/[^a-z0-9]+/g, "-")
25
+ .replace(/^-+|-+$/g, "");
26
+ return slug || "rust-bin";
27
+ }
28
+ function cargoToml(projectName) {
29
+ return [
30
+ "[package]",
31
+ `name = "${projectName}"`,
32
+ 'version = "0.1.0"',
33
+ 'edition = "2024"',
34
+ "",
35
+ "[dependencies]",
36
+ "",
37
+ "[lints]",
38
+ "workspace = true",
39
+ "",
40
+ "[workspace]",
41
+ 'members = ["."]',
42
+ 'resolver = "3"',
43
+ "",
44
+ "[workspace.lints.rust]",
45
+ 'unsafe_code = "forbid"',
46
+ "",
47
+ "[workspace.lints.clippy]",
48
+ 'all = "deny"',
49
+ 'pedantic = "deny"',
50
+ 'nursery = "deny"',
51
+ "",
52
+ "[profile.release]",
53
+ 'strip = "symbols"',
54
+ 'lto = "thin"',
55
+ "codegen-units = 1",
56
+ ""
57
+ ].join("\n");
58
+ }
59
+ function cargoLock(projectName) {
60
+ return [
61
+ "# This file is automatically @generated by Cargo.",
62
+ "# It is not intended for manual editing.",
63
+ "version = 4",
64
+ "",
65
+ "[[package]]",
66
+ `name = "${projectName}"`,
67
+ 'version = "0.1.0"',
68
+ ""
69
+ ].join("\n");
70
+ }
71
+ function operationsForRustBin(projectName) {
72
+ return [
73
+ {
74
+ kind: "writeText",
75
+ to: "Cargo.toml",
76
+ text: cargoToml(projectName)
77
+ },
78
+ {
79
+ kind: "writeText",
80
+ to: "Cargo.lock",
81
+ text: cargoLock(projectName)
82
+ },
83
+ {
84
+ kind: "writeText",
85
+ to: "rustfmt.toml",
86
+ text: ['edition = "2024"', "max_width = 100", ""].join("\n")
87
+ },
88
+ {
89
+ kind: "writeText",
90
+ to: ".gitignore",
91
+ text: ["target", ".env", ""].join("\n")
92
+ },
93
+ {
94
+ kind: "copyFile",
95
+ from: "src/main.rs",
96
+ to: "src/main.rs"
97
+ },
98
+ {
99
+ kind: "writeText",
100
+ to: "scripts/check",
101
+ text: [
102
+ "#!/usr/bin/env sh",
103
+ "set -eu",
104
+ "",
105
+ "echo cargo fmt --all -- --check",
106
+ "cargo fmt --all -- --check",
107
+ "echo cargo clippy --workspace --all-targets -- -D warnings",
108
+ "cargo clippy --workspace --all-targets -- -D warnings",
109
+ "echo cargo test --workspace",
110
+ "cargo test --workspace",
111
+ ""
112
+ ].join("\n")
113
+ },
114
+ {
115
+ kind: "setExecutable",
116
+ path: "scripts/check",
117
+ executable: true
118
+ },
119
+ {
120
+ kind: "writeJson",
121
+ to: ".project-kit/blueprint.json",
122
+ value: {
123
+ schemaVersion: 1,
124
+ preset: "rust-bin",
125
+ projectKind: "single-package",
126
+ features
127
+ }
128
+ },
129
+ {
130
+ kind: "writeJson",
131
+ to: ".project-kit/generated-by.json",
132
+ value: generatedBy
133
+ },
134
+ {
135
+ kind: "writeJson",
136
+ to: ".devcontainer/devcontainer.json",
137
+ value: {
138
+ name: `${projectName} Rust development`,
139
+ image: "mcr.microsoft.com/devcontainers/rust:1",
140
+ mounts: [
141
+ "source=${localWorkspaceFolderBasename}-cargo-registry,target=/usr/local/cargo/registry,type=volume",
142
+ "source=${localWorkspaceFolderBasename}-cargo-git,target=/usr/local/cargo/git,type=volume",
143
+ "source=${localWorkspaceFolderBasename}-target,target=${containerWorkspaceFolder}/target,type=volume"
144
+ ],
145
+ postCreateCommand: "rustup component add rustfmt clippy && cargo fetch",
146
+ customizations: {
147
+ vscode: {
148
+ extensions: ["rust-lang.rust-analyzer", "tamasfe.even-better-toml"]
149
+ }
150
+ }
151
+ }
152
+ },
153
+ {
154
+ kind: "writeText",
155
+ to: ".github/workflows/check.yml",
156
+ text: [
157
+ "name: Check",
158
+ "",
159
+ "on:",
160
+ " pull_request:",
161
+ " push:",
162
+ " branches:",
163
+ " - main",
164
+ "",
165
+ "jobs:",
166
+ " check:",
167
+ " runs-on: ubuntu-latest",
168
+ " steps:",
169
+ " - uses: actions/checkout@v4",
170
+ " - uses: dtolnay/rust-toolchain@stable",
171
+ " with:",
172
+ " components: rustfmt, clippy",
173
+ " - uses: Swatinem/rust-cache@v2",
174
+ " - run: ./scripts/check",
175
+ ""
176
+ ].join("\n")
177
+ },
178
+ {
179
+ kind: "writeText",
180
+ to: ".github/dependabot.yml",
181
+ text: [
182
+ "version: 2",
183
+ "updates:",
184
+ " - package-ecosystem: cargo",
185
+ " directory: /",
186
+ " schedule:",
187
+ " interval: weekly",
188
+ " - package-ecosystem: github-actions",
189
+ " directory: /",
190
+ " schedule:",
191
+ " interval: weekly",
192
+ ""
193
+ ].join("\n")
194
+ }
195
+ ];
196
+ }
197
+ function templateSourceRoot() {
198
+ return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "templates", "rust-bin");
199
+ }
200
+ export async function initRustBinProject(targetDir) {
201
+ await renderNewProject({
202
+ sourceRoot: templateSourceRoot(),
203
+ targetRoot: targetDir,
204
+ operations: operationsForRustBin(cargoPackageNameFromDir(targetDir))
205
+ });
206
+ }
@@ -0,0 +1,2 @@
1
+ export declare function initTsLibProject(targetDir: string): Promise<void>;
2
+ //# sourceMappingURL=ts-lib.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ts-lib.d.ts","sourceRoot":"","sources":["../src/ts-lib.ts"],"names":[],"mappings":"AA2NA,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMvE"}