@duonghieu0712z/create-tauri-vue-template 0.0.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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -0
  3. package/assets/vscode-settings.json +16 -0
  4. package/dist/bin/index.js +83 -0
  5. package/dist/cli/color.js +25 -0
  6. package/dist/cli/files.js +18 -0
  7. package/dist/cli/project.js +48 -0
  8. package/dist/cli/prompts.js +44 -0
  9. package/dist/cli/release-workflow.js +13 -0
  10. package/dist/cli/scaffold.js +30 -0
  11. package/dist/cli/text.js +24 -0
  12. package/dist/cli/types.js +1 -0
  13. package/dist/scripts/prepare-template.js +67 -0
  14. package/package.json +39 -0
  15. package/template/.editorconfig +8 -0
  16. package/template/.gitattributes +5 -0
  17. package/template/.github/dependabot.yml +38 -0
  18. package/template/.github/workflows/code-quality.yml +70 -0
  19. package/template/.github/workflows/release.yml +112 -0
  20. package/template/.husky/pre-commit +7 -0
  21. package/template/.oxfmtrc.json +36 -0
  22. package/template/.oxlintrc.json +24 -0
  23. package/template/.vscode/extensions.json +11 -0
  24. package/template/CHANGELOG.md +12 -0
  25. package/template/LICENSE +21 -0
  26. package/template/README.md +126 -0
  27. package/template/components.json +24 -0
  28. package/template/eslint.config.ts +32 -0
  29. package/template/index.html +14 -0
  30. package/template/package.json +74 -0
  31. package/template/pnpm-lock.yaml +3953 -0
  32. package/template/pnpm-workspace.yaml +3 -0
  33. package/template/public/favicon.ico +0 -0
  34. package/template/rust-toolchain.toml +2 -0
  35. package/template/scripts/bump-version.js +42 -0
  36. package/template/scripts/rename-app.js +137 -0
  37. package/template/scripts/utils.js +59 -0
  38. package/template/src/App.vue +71 -0
  39. package/template/src/assets/fonts/Geist/Geist-Italic[wght].woff2 +0 -0
  40. package/template/src/assets/fonts/Geist/Geist[wght].woff2 +0 -0
  41. package/template/src/assets/images/tauri.svg +6 -0
  42. package/template/src/assets/images/vite.svg +1 -0
  43. package/template/src/assets/images/vue.svg +1 -0
  44. package/template/src/generated/auto-import.d.ts +139 -0
  45. package/template/src/generated/bindings.ts +9 -0
  46. package/template/src/lib/utils.ts +8 -0
  47. package/template/src/main.ts +9 -0
  48. package/template/src/styles/fonts.css +15 -0
  49. package/template/src/styles/globals.css +130 -0
  50. package/template/src/vite-env.d.ts +7 -0
  51. package/template/src-tauri/Cargo.lock +6328 -0
  52. package/template/src-tauri/Cargo.toml +41 -0
  53. package/template/src-tauri/build.rs +3 -0
  54. package/template/src-tauri/capabilities/default.json +7 -0
  55. package/template/src-tauri/icons/128x128.png +0 -0
  56. package/template/src-tauri/icons/128x128@2x.png +0 -0
  57. package/template/src-tauri/icons/32x32.png +0 -0
  58. package/template/src-tauri/icons/Square107x107Logo.png +0 -0
  59. package/template/src-tauri/icons/Square142x142Logo.png +0 -0
  60. package/template/src-tauri/icons/Square150x150Logo.png +0 -0
  61. package/template/src-tauri/icons/Square284x284Logo.png +0 -0
  62. package/template/src-tauri/icons/Square30x30Logo.png +0 -0
  63. package/template/src-tauri/icons/Square310x310Logo.png +0 -0
  64. package/template/src-tauri/icons/Square44x44Logo.png +0 -0
  65. package/template/src-tauri/icons/Square71x71Logo.png +0 -0
  66. package/template/src-tauri/icons/Square89x89Logo.png +0 -0
  67. package/template/src-tauri/icons/StoreLogo.png +0 -0
  68. package/template/src-tauri/icons/icon.icns +0 -0
  69. package/template/src-tauri/icons/icon.ico +0 -0
  70. package/template/src-tauri/icons/icon.png +0 -0
  71. package/template/src-tauri/src/command/mod.rs +13 -0
  72. package/template/src-tauri/src/lib.rs +54 -0
  73. package/template/src-tauri/src/main.rs +6 -0
  74. package/template/src-tauri/tauri.conf.json +37 -0
  75. package/template/tsconfig.app.json +11 -0
  76. package/template/tsconfig.json +11 -0
  77. package/template/tsconfig.node.json +11 -0
  78. package/template/vite.config.ts +42 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 duonghieu0712z
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # Create Tauri Vue Template
2
+
3
+ Create a Tauri 2 + Vue 3 desktop app from the `tauri-vue-template` starter.
4
+
5
+ ## Usage
6
+
7
+ Run the interactive scaffold:
8
+
9
+ ```bash
10
+ pnpm create @duonghieu0712z/tauri-vue-template@latest
11
+ ```
12
+
13
+ Pass values directly when needed:
14
+
15
+ ```bash
16
+ pnpm create @duonghieu0712z/tauri-vue-template@latest my-app --name "My App" --id "com.example.my-app" --author "Your Name"
17
+ ```
18
+
19
+ The CLI copies the bundled template into the target directory, updates the package name, Tauri product name, application identifier, Rust crate names, release workflow metadata, and VS Code workspace settings.
20
+
21
+ ## Development
22
+
23
+ Install dependencies:
24
+
25
+ ```bash
26
+ pnpm install
27
+ ```
28
+
29
+ The CLI source lives in `src/` and is compiled to `dist/`. The published package runs the compiled JavaScript entry at `dist/bin/index.js`.
30
+
31
+ Check formatting and linting:
32
+
33
+ ```bash
34
+ pnpm build
35
+ pnpm format
36
+ pnpm lint
37
+ ```
38
+
39
+ Apply automatic fixes:
40
+
41
+ ```bash
42
+ pnpm format:fix
43
+ pnpm lint:fix
44
+ ```
45
+
46
+ ## Local Test
47
+
48
+ Run the CLI from this repository:
49
+
50
+ ```bash
51
+ pnpm build
52
+ node dist/bin/index.js
53
+ ```
54
+
55
+ Run without prompts:
56
+
57
+ ```bash
58
+ pnpm build
59
+ node dist/bin/index.js --name "My App" --id "com.example.my-app" --author "Your Name"
60
+ ```
61
+
62
+ ## Publish
63
+
64
+ Build and inspect the npm package tarball contents through the `prepack` script. This compiles TypeScript to `dist/` and prepares the bundled template before packing:
65
+
66
+ ```bash
67
+ pnpm pack --dry-run
68
+ ```
69
+
70
+ Publish the package:
71
+
72
+ ```bash
73
+ pnpm publish --access public
74
+ ```
@@ -0,0 +1,16 @@
1
+ {
2
+ "explorer.fileNesting.enabled": true,
3
+ "explorer.fileNesting.patterns": {
4
+ "tsconfig.json": "tsconfig.*.json, env.d.ts, typed-router.d.ts",
5
+ "vite.config.*": "jsconfig*, vitest.config.*, cypress.config.*, playwright.config.*",
6
+ "package.json": "package-lock.json, pnpm*, .yarnrc*, yarn*, .eslint*, eslint*, .oxlint*, oxlint*, .oxfmt*, .prettier*, prettier*, .editorconfig"
7
+ },
8
+ "editor.codeActionsOnSave": {
9
+ "source.fixAll": "explicit"
10
+ },
11
+ "editor.formatOnSave": true,
12
+ "editor.defaultFormatter": "oxc.oxc-vscode",
13
+ "[rust]": {
14
+ "editor.defaultFormatter": "rust-lang.rust-analyzer"
15
+ }
16
+ }
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { basename, dirname, relative, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { cac } from 'cac';
6
+ import { color, printIntro } from '../cli/color.js';
7
+ import { promptForMissingOptions } from '../cli/prompts.js';
8
+ import { scaffoldProject } from '../cli/scaffold.js';
9
+ import { requiredValue, toKebabCase, toSnakeCase, toTitleCase } from '../cli/text.js';
10
+ const require = createRequire(import.meta.url);
11
+ const packageJson = require('../../package.json');
12
+ function parseCommand() {
13
+ const cli = cac('create-tauri-vue-template');
14
+ cli.usage('[project-dir] [options]')
15
+ .option('--name <name>', 'Application display name')
16
+ .option('--package <name>', 'Package name')
17
+ .option('--id <identifier>', 'Tauri application identifier')
18
+ .option('--author <name>', 'Project author')
19
+ .help()
20
+ .version(packageJson.version);
21
+ const parsed = cli.parse(process.argv, { run: false });
22
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
23
+ process.exit(0);
24
+ }
25
+ if (process.argv.includes('--version') || process.argv.includes('-v')) {
26
+ process.exit(0);
27
+ }
28
+ const options = parsed.options;
29
+ return {
30
+ projectDir: parsed.args[0],
31
+ options: {
32
+ name: options.name,
33
+ package: options.package,
34
+ id: options.id,
35
+ author: options.author,
36
+ },
37
+ };
38
+ }
39
+ async function main() {
40
+ const { projectDir, options } = parseCommand();
41
+ if (!projectDir && !options.name && (!process.stdin.isTTY || !process.stdout.isTTY)) {
42
+ throw new Error('Usage: pnpm create @duonghieu0712z/tauri-vue-template@latest [project-dir] --name "My App" --id "com.example.my-app"');
43
+ }
44
+ printIntro();
45
+ const defaultAppName = projectDir ? toTitleCase(basename(projectDir)) : 'Tauri Vue App';
46
+ const resolvedOptions = await promptForMissingOptions(options, {
47
+ appName: defaultAppName,
48
+ });
49
+ const appName = requiredValue(resolvedOptions.name?.trim() || defaultAppName, 'App name');
50
+ const packageName = requiredValue(resolvedOptions.package?.trim() || toKebabCase(appName), 'Package name');
51
+ const targetArg = projectDir || packageName;
52
+ const targetDir = resolve(process.cwd(), targetArg);
53
+ const crateName = toKebabCase(packageName);
54
+ const crateLibName = `${toSnakeCase(crateName)}_lib`;
55
+ const identifier = requiredValue(resolvedOptions.id?.trim() || `com.example.${packageName}`, 'Tauri identifier');
56
+ const author = resolvedOptions.author?.trim();
57
+ const currentDir = dirname(fileURLToPath(import.meta.url));
58
+ const packageDir = resolve(currentDir, '..', '..');
59
+ await scaffoldProject(targetDir, packageDir, {
60
+ appName,
61
+ packageName,
62
+ crateName,
63
+ crateLibName,
64
+ identifier,
65
+ author,
66
+ });
67
+ const relativeTargetDir = relative(process.cwd(), targetDir) || '.';
68
+ console.log('');
69
+ console.log(`${color.green('Created')} ${appName} in ${color.cyan(relativeTargetDir)}`);
70
+ console.log('');
71
+ console.log(color.yellow('Next steps:'));
72
+ console.log(` ${color.cyan(`cd ${relativeTargetDir}`)}`);
73
+ console.log(` ${color.cyan('pnpm install')}`);
74
+ console.log(` ${color.cyan('pnpm start')}`);
75
+ }
76
+ try {
77
+ await main();
78
+ }
79
+ catch (error) {
80
+ const message = error instanceof Error ? error.message : String(error);
81
+ console.error(message);
82
+ process.exit(1);
83
+ }
@@ -0,0 +1,25 @@
1
+ import colors from 'picocolors';
2
+ export const color = {
3
+ cyan: colors.cyan,
4
+ dim: colors.dim,
5
+ green: colors.green,
6
+ yellow: colors.yellow,
7
+ };
8
+ export function printIntro() {
9
+ if (!process.stdout.isTTY) {
10
+ return;
11
+ }
12
+ console.log('');
13
+ console.log(color.cyan('Tauri Vue Template'));
14
+ console.log(color.dim('Scaffold a Tauri 2 + Vue 3 desktop app.'));
15
+ console.log('');
16
+ }
17
+ export function printSection(title) {
18
+ if (!process.stdout.isTTY) {
19
+ return;
20
+ }
21
+ console.log(color.yellow(title));
22
+ }
23
+ export function formatPrompt(label, defaultValue) {
24
+ return `${label} ${color.dim(`(${defaultValue})`)}: `;
25
+ }
@@ -0,0 +1,18 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ export async function readJson(path) {
3
+ return JSON.parse(await readFile(path, 'utf8'));
4
+ }
5
+ export async function writeJson(path, value) {
6
+ await writeFile(path, `${JSON.stringify(value, null, 4)}\n`);
7
+ }
8
+ export function replaceOne(content, pattern, replacement, description) {
9
+ if (!pattern.test(content)) {
10
+ throw new Error(`Could not update ${description}`);
11
+ }
12
+ return content.replace(pattern, replacement);
13
+ }
14
+ export async function updateText(path, replacements) {
15
+ const content = await readFile(path, 'utf8');
16
+ const updatedContent = replacements.reduce((currentContent, [pattern, replacement]) => replaceOne(currentContent, pattern, replacement, path), content);
17
+ await writeFile(path, updatedContent);
18
+ }
@@ -0,0 +1,48 @@
1
+ import { resolve } from 'node:path';
2
+ import { readJson, updateText, writeJson } from './files.js';
3
+ import { updateReleaseWorkflow } from './release-workflow.js';
4
+ export async function renameProject(targetDir, identity) {
5
+ const packageJsonPath = resolve(targetDir, 'package.json');
6
+ const packageJson = await readJson(packageJsonPath);
7
+ packageJson.name = identity.packageName;
8
+ if (identity.author) {
9
+ packageJson.author = identity.author;
10
+ }
11
+ else {
12
+ delete packageJson.author;
13
+ }
14
+ await writeJson(packageJsonPath, packageJson);
15
+ const tauriConfigPath = resolve(targetDir, 'src-tauri', 'tauri.conf.json');
16
+ const tauriConfig = await readJson(tauriConfigPath);
17
+ tauriConfig.productName = identity.appName;
18
+ tauriConfig.identifier = identity.identifier;
19
+ tauriConfig.app.windows = tauriConfig.app.windows.map((window) => ({
20
+ ...window,
21
+ title: window.title ? identity.appName : window.title,
22
+ }));
23
+ if (identity.author) {
24
+ tauriConfig.bundle.publisher = identity.author;
25
+ }
26
+ else {
27
+ delete tauriConfig.bundle.publisher;
28
+ }
29
+ await writeJson(tauriConfigPath, tauriConfig);
30
+ await updateText(resolve(targetDir, 'index.html'), [
31
+ [/<title>.*<\/title>/, `<title>${identity.appName}</title>`],
32
+ ]);
33
+ await updateText(resolve(targetDir, 'src-tauri', 'Cargo.toml'), [
34
+ [/^name = "([^"]+)"/m, `name = "${identity.crateName}"`],
35
+ [/^description = "([^"]+)"/m, `description = "${identity.appName}"`],
36
+ [/^(\[lib\]\s+[\s\S]*?^name = )"([^"]+)"/m, `$1"${identity.crateLibName}"`],
37
+ ]);
38
+ await updateText(resolve(targetDir, 'src-tauri', 'Cargo.toml'), [
39
+ [
40
+ /^authors = \[[^\]]*\]/m,
41
+ identity.author ? `authors = ["${identity.author}"]` : 'authors = []',
42
+ ],
43
+ ]);
44
+ await updateText(resolve(targetDir, 'src-tauri', 'src', 'main.rs'), [
45
+ [/[a-zA-Z0-9_]+_lib::run\(\)/, `${identity.crateLibName}::run()`],
46
+ ]);
47
+ await updateReleaseWorkflow(resolve(targetDir, '.github', 'workflows', 'release.yml'), identity);
48
+ }
@@ -0,0 +1,44 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ import { formatPrompt, printSection, color } from './color.js';
3
+ import { toKebabCase } from './text.js';
4
+ export async function promptForMissingOptions(options, defaults) {
5
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
6
+ const appName = options.name?.trim() || defaults.appName;
7
+ const packageName = options.package?.trim() || toKebabCase(appName);
8
+ return {
9
+ name: appName,
10
+ package: packageName,
11
+ id: options.id?.trim() || `com.example.${packageName}`,
12
+ author: options.author?.trim() || '',
13
+ };
14
+ }
15
+ const prompt = createInterface({
16
+ input: process.stdin,
17
+ output: process.stdout,
18
+ });
19
+ try {
20
+ printSection('Project identity');
21
+ const appName = options.name?.trim() ||
22
+ (await prompt.question(formatPrompt('App name', defaults.appName))).trim() ||
23
+ defaults.appName;
24
+ const defaultPackageName = toKebabCase(appName);
25
+ const packageName = options.package?.trim() ||
26
+ (await prompt.question(formatPrompt('Package name', defaultPackageName))).trim() ||
27
+ defaultPackageName;
28
+ const defaultIdentifier = `com.example.${packageName}`;
29
+ const identifier = options.id?.trim() ||
30
+ (await prompt.question(formatPrompt('Tauri identifier', defaultIdentifier))).trim() ||
31
+ defaultIdentifier;
32
+ const author = options.author?.trim() ||
33
+ (await prompt.question(`Author ${color.dim('(optional)')}: `)).trim();
34
+ return {
35
+ name: appName,
36
+ package: packageName,
37
+ id: identifier,
38
+ author,
39
+ };
40
+ }
41
+ finally {
42
+ prompt.close();
43
+ }
44
+ }
@@ -0,0 +1,13 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { replaceOne } from './files.js';
3
+ export async function updateReleaseWorkflow(path, identity) {
4
+ let content = await readFile(path, 'utf8');
5
+ content = replaceOne(content, /^(\s*releaseName:\s*).*$/m, `$1${identity.appName} v__VERSION__`, path);
6
+ if (/^\s*releaseAssetNamePattern:\s*/m.test(content)) {
7
+ content = replaceOne(content, /^(\s*releaseAssetNamePattern:\s*).*$/m, `$1${identity.packageName}_[version]_[arch][setup][ext]`, path);
8
+ }
9
+ else {
10
+ content = replaceOne(content, /^(\s*prerelease:\s*false)$/m, `$1\n releaseAssetNamePattern: ${identity.packageName}_[version]_[arch][setup][ext]`, path);
11
+ }
12
+ await writeFile(path, content);
13
+ }
@@ -0,0 +1,30 @@
1
+ import { access, copyFile, cp, mkdir, readdir } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { renameProject } from './project.js';
4
+ export async function ensureEmptyTarget(path) {
5
+ await mkdir(path, { recursive: true });
6
+ const entries = await readdir(path);
7
+ if (entries.length > 0) {
8
+ throw new Error(`Target directory is not empty: ${path}`);
9
+ }
10
+ }
11
+ export async function ensureTemplate(templateDir) {
12
+ try {
13
+ await access(templateDir);
14
+ }
15
+ catch {
16
+ await import('../scripts/prepare-template.js');
17
+ }
18
+ }
19
+ export async function writeEditorSettings(targetDir, packageDir) {
20
+ await mkdir(resolve(targetDir, '.vscode'), { recursive: true });
21
+ await copyFile(resolve(packageDir, 'assets', 'vscode-settings.json'), resolve(targetDir, '.vscode', 'settings.json'));
22
+ }
23
+ export async function scaffoldProject(targetDir, packageDir, identity) {
24
+ const templateDir = resolve(packageDir, 'template');
25
+ await ensureTemplate(templateDir);
26
+ await ensureEmptyTarget(targetDir);
27
+ await cp(templateDir, targetDir, { recursive: true });
28
+ await writeEditorSettings(targetDir, packageDir);
29
+ await renameProject(targetDir, identity);
30
+ }
@@ -0,0 +1,24 @@
1
+ export function toKebabCase(value) {
2
+ return value
3
+ .trim()
4
+ .replace(/['"]/g, '')
5
+ .replace(/[^a-zA-Z0-9]+/g, '-')
6
+ .replace(/^-+|-+$/g, '')
7
+ .toLowerCase();
8
+ }
9
+ export function toSnakeCase(value) {
10
+ return toKebabCase(value).replaceAll('-', '_');
11
+ }
12
+ export function toTitleCase(value) {
13
+ return toKebabCase(value)
14
+ .split('-')
15
+ .filter(Boolean)
16
+ .map((word) => `${word[0].toUpperCase()}${word.slice(1)}`)
17
+ .join(' ');
18
+ }
19
+ export function requiredValue(value, name) {
20
+ if (!value) {
21
+ throw new Error(`${name} cannot be empty`);
22
+ }
23
+ return value;
24
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ import { createWriteStream } from 'node:fs';
3
+ import { cp, mkdir, readdir, rm } from 'node:fs/promises';
4
+ import { dirname, resolve } from 'node:path';
5
+ import { pipeline } from 'node:stream/promises';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { extract } from 'tar';
8
+ const currentDir = dirname(fileURLToPath(import.meta.url));
9
+ const packageDir = resolve(currentDir, '..', '..');
10
+ const templateDir = resolve(packageDir, 'template');
11
+ const archivePath = resolve(packageDir, '.tmp-template.tar.gz');
12
+ const templateRepo = process.env.TAURI_VUE_TEMPLATE_REPO || 'duonghieu0712z/tauri-vue-template';
13
+ const templateRef = process.env.TAURI_VUE_TEMPLATE_REF || 'main';
14
+ const templateSourceDir = process.env.TAURI_VUE_TEMPLATE_SOURCE
15
+ ? resolve(process.env.TAURI_VUE_TEMPLATE_SOURCE)
16
+ : null;
17
+ const excludedPaths = new Set([
18
+ '.git',
19
+ '.eslintcache',
20
+ 'dist',
21
+ 'node_modules',
22
+ 'packages',
23
+ 'src-tauri/target',
24
+ ]);
25
+ function normalizePath(path) {
26
+ return path.replaceAll('\\', '/');
27
+ }
28
+ function shouldInclude(relativePath) {
29
+ return !excludedPaths.has(normalizePath(relativePath));
30
+ }
31
+ async function copyTemplateSource(sourceDir) {
32
+ await mkdir(templateDir, { recursive: true });
33
+ await readdir(sourceDir).then((entries) => Promise.all(entries
34
+ .filter((entry) => shouldInclude(entry))
35
+ .map((entry) => cp(resolve(sourceDir, entry), resolve(templateDir, entry), {
36
+ recursive: true,
37
+ filter: (source) => shouldInclude(normalizePath(source).slice(normalizePath(sourceDir).length + 1)),
38
+ }))));
39
+ }
40
+ async function downloadTemplateArchive() {
41
+ const archiveUrl = `https://github.com/${templateRepo}/archive/${templateRef}.tar.gz`;
42
+ const response = await fetch(archiveUrl);
43
+ if (!response.ok || !response.body) {
44
+ throw new Error(`Could not download template archive: ${archiveUrl}`);
45
+ }
46
+ await pipeline(response.body, createWriteStream(archivePath));
47
+ }
48
+ async function extractTemplateArchive() {
49
+ await mkdir(templateDir, { recursive: true });
50
+ await extract({
51
+ file: archivePath,
52
+ cwd: templateDir,
53
+ strip: 1,
54
+ filter: (path) => shouldInclude(path.split('/').slice(1).join('/')),
55
+ });
56
+ }
57
+ await rm(templateDir, { recursive: true, force: true });
58
+ await rm(archivePath, { force: true });
59
+ if (templateSourceDir) {
60
+ await copyTemplateSource(templateSourceDir);
61
+ }
62
+ else {
63
+ await downloadTemplateArchive();
64
+ await extractTemplateArchive();
65
+ await rm(archivePath, { force: true });
66
+ }
67
+ console.log(`Prepared template at ${templateDir}`);
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@duonghieu0712z/create-tauri-vue-template",
3
+ "version": "0.0.3",
4
+ "description": "Create a Tauri 2 + Vue 3 desktop app from the tauri-vue-template starter.",
5
+ "bin": {
6
+ "create-tauri-vue-template": "dist/bin/index.js"
7
+ },
8
+ "files": [
9
+ "assets",
10
+ "dist",
11
+ "template"
12
+ ],
13
+ "type": "module",
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "dependencies": {
18
+ "cac": "^7.0.0",
19
+ "picocolors": "^1.1.1",
20
+ "tar": "^7.5.1"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^25.9.1",
24
+ "oxfmt": "^0.51.0",
25
+ "oxlint": "^1.66.0",
26
+ "typescript": "~6.0.3"
27
+ },
28
+ "engines": {
29
+ "node": "^20.19.0 || >=22.12.0"
30
+ },
31
+ "scripts": {
32
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
33
+ "build": "pnpm clean && tsc -p tsconfig.json",
34
+ "format": "oxfmt --check .",
35
+ "format:fix": "oxfmt .",
36
+ "lint": "oxlint .",
37
+ "lint:fix": "oxlint --fix ."
38
+ }
39
+ }
@@ -0,0 +1,8 @@
1
+ [*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl,html}]
2
+ charset = utf-8
3
+ end_of_line = lf
4
+ indent_size = 4
5
+ indent_style = space
6
+ insert_final_newline = true
7
+ max_line_length = 100
8
+ trim_trailing_whitespace = true
@@ -0,0 +1,5 @@
1
+ * text=auto eol=lf
2
+
3
+ pnpm-lock.yaml linguist-generated=true
4
+ src-tauri/Cargo.lock linguist-generated=true
5
+ src/generated/**/*.ts linguist-generated=true
@@ -0,0 +1,38 @@
1
+ version: 2
2
+
3
+ updates:
4
+ - package-ecosystem: npm
5
+ directory: /
6
+ schedule:
7
+ interval: weekly
8
+ day: monday
9
+ time: '02:00'
10
+ timezone: Etc/UTC
11
+ open-pull-requests-limit: 10
12
+ groups:
13
+ npm-minor-and-patch:
14
+ update-types:
15
+ - minor
16
+ - patch
17
+
18
+ - package-ecosystem: cargo
19
+ directory: /src-tauri
20
+ schedule:
21
+ interval: weekly
22
+ day: monday
23
+ time: '02:30'
24
+ timezone: Etc/UTC
25
+ open-pull-requests-limit: 10
26
+ groups:
27
+ cargo-minor-and-patch:
28
+ update-types:
29
+ - minor
30
+ - patch
31
+
32
+ - package-ecosystem: github-actions
33
+ directory: /
34
+ schedule:
35
+ interval: weekly
36
+ day: monday
37
+ time: '03:00'
38
+ timezone: Etc/UTC
@@ -0,0 +1,70 @@
1
+ name: Code Quality
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ env:
13
+ NODE_VERSION: '24'
14
+ PNPM_VERSION: '11'
15
+
16
+ jobs:
17
+ frontend:
18
+ runs-on: ubuntu-latest
19
+
20
+ steps:
21
+ - name: Checkout repository
22
+ uses: actions/checkout@v6
23
+
24
+ - name: Setup pnpm
25
+ uses: pnpm/action-setup@v6
26
+ with:
27
+ version: ${{ env.PNPM_VERSION }}
28
+
29
+ - name: Setup Node.js
30
+ uses: actions/setup-node@v6
31
+ with:
32
+ node-version: ${{ env.NODE_VERSION }}
33
+ cache: pnpm
34
+
35
+ - name: Install dependencies
36
+ run: pnpm install --frozen-lockfile
37
+
38
+ - name: Check formatting
39
+ run: pnpm format
40
+
41
+ - name: Check lint
42
+ run: pnpm lint
43
+
44
+ rust:
45
+ runs-on: ubuntu-latest
46
+
47
+ steps:
48
+ - name: Checkout repository
49
+ uses: actions/checkout@v6
50
+
51
+ - name: Install Linux dependencies
52
+ run: |
53
+ sudo apt-get update
54
+ sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
55
+
56
+ - name: Setup Rust
57
+ uses: actions-rust-lang/setup-rust-toolchain@v1
58
+ with:
59
+ components: rustfmt, clippy
60
+
61
+ - name: Rust cache
62
+ uses: swatinem/rust-cache@v2
63
+ with:
64
+ workspaces: './src-tauri -> target'
65
+
66
+ - name: Check formatting
67
+ run: cargo fmt --manifest-path src-tauri/Cargo.toml -- --check
68
+
69
+ - name: Check lint
70
+ run: cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets --all-features -- -D warnings