moracarta 2.1.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/LICENSE +21 -0
- package/README.md +84 -0
- package/index.html +31 -0
- package/package.json +35 -0
- package/public/assets/images/corner.webp +0 -0
- package/public/assets/images/custom-text-demo1.png +0 -0
- package/public/assets/images/custom-text-demo2.png +0 -0
- package/public/assets/images/demo-animation.gif +0 -0
- package/public/assets/images/demo-main-page.png +0 -0
- package/public/assets/images/googleDocsTabs.webp +0 -0
- package/public/assets/images/setup-demo.png +0 -0
- package/public/robots.txt +2 -0
- package/src/commands/add.js +170 -0
- package/src/commands/cliConfig.js +68 -0
- package/src/commands/deploy.js +41 -0
- package/src/commands/remove.js +198 -0
- package/src/commands/setup.js +257 -0
- package/src/config/fonts.js +7 -0
- package/src/data/letters.example.js +28 -0
- package/src/data/lettersLoader.js +7 -0
- package/src/i18n/de.js +8 -0
- package/src/i18n/en.js +8 -0
- package/src/i18n/es.js +8 -0
- package/src/i18n/fr.js +8 -0
- package/src/i18n/it.js +8 -0
- package/src/i18n/ja.js +8 -0
- package/src/i18n/ko.js +8 -0
- package/src/i18n/nl.js +0 -0
- package/src/i18n/pr.js +0 -0
- package/src/i18n/pt-BR.js +8 -0
- package/src/i18n/ru.js +8 -0
- package/src/i18n/tl.js +0 -0
- package/src/i18n/zh.js +8 -0
- package/src/scripts/data.variable.js +24 -0
- package/src/scripts/i18n.js +67 -0
- package/src/scripts/letters.script.js +257 -0
- package/src/scripts/main.script.js +101 -0
- package/src/services/googleDocs.js +76 -0
- package/src/styles/letters.css +405 -0
- package/src/styles/main.css +338 -0
- package/src/views/letters.view.html +83 -0
- package/vite.config.mjs +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wesley Hanauer Nunes
|
|
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,84 @@
|
|
|
1
|
+
# Moracarta
|
|
2
|
+
|
|
3
|
+
**Create your own personalized love-letter website and deploy it completely for free.**
|
|
4
|
+
|
|
5
|
+
š **[Live Demo](https://moracarta.pages.dev/)**
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
š Multiple letters
|
|
10
|
+
š Google Docs as your writing interface
|
|
11
|
+
šØ Customizable texts
|
|
12
|
+
š Free web deployment
|
|
13
|
+
š No database required
|
|
14
|
+
|
|
15
|
+
## **Get your own working URL in less than 5 minutes**
|
|
16
|
+
|
|
17
|
+
**And it's as simple as:**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
git clone https://github.com/WesleyHanauer/moracarta
|
|
21
|
+
cd moracarta
|
|
22
|
+
npm install
|
|
23
|
+
moracarta setup
|
|
24
|
+
moracarta build
|
|
25
|
+
moracarta deploy
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### App Demo
|
|
29
|
+
<p align="center">
|
|
30
|
+
<img src="public/assets/images/demo-main-page.png" height="400">
|
|
31
|
+
<img src="public/assets/images/demo-animation.gif" height="400">
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
### Setup Demo
|
|
35
|
+
|
|
36
|
+
<img src="public/assets/images/setup-demo.png" width="100%">
|
|
37
|
+
<br><br>
|
|
38
|
+
|
|
39
|
+
## That's it.
|
|
40
|
+
|
|
41
|
+
All that's left is to write your letters in Google Docs and, if you want, customize the two texts displayed on the main page.
|
|
42
|
+
|
|
43
|
+
## Requirements
|
|
44
|
+
|
|
45
|
+
- Node.js 22+
|
|
46
|
+
- A Google account
|
|
47
|
+
|
|
48
|
+
### Adding letters
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
moracarta add
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
* Paste the link to your public Google Doc.
|
|
55
|
+
* **IMPORTANT:** Use spaces for indentation instead of `TAB`.
|
|
56
|
+
* Use a different Google Docs **tab** for each letter to keep your Google Doc organized.
|
|
57
|
+
|
|
58
|
+
<p align="center">
|
|
59
|
+
<img src="public/assets/images/googleDocsTabs.webp" width="400">
|
|
60
|
+
</p>
|
|
61
|
+
<br>
|
|
62
|
+
|
|
63
|
+
### Removing letters
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
moracarta remove
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Select the letter you wish to remove.
|
|
70
|
+
|
|
71
|
+
### Customizing the website
|
|
72
|
+
|
|
73
|
+
During `moracarta setup`, you can choose from preset messages or enter your own custom text for the main page.
|
|
74
|
+
|
|
75
|
+
<p align="center">
|
|
76
|
+
<img src="public/assets/images/custom-text-demo2.png" width="700"><br>
|
|
77
|
+
<img src="public/assets/images/custom-text-demo1.png" width="700">
|
|
78
|
+
</p>
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
Moracarta is open-source software licensed under the **MIT License**.
|
|
83
|
+
|
|
84
|
+
See the [LICENSE](LICENSE) file for the full license text.
|
package/index.html
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
|
6
|
+
<meta name="robots" content="noindex, nofollow, noarchive">
|
|
7
|
+
<title data-variable="TITLE"></title>
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
|
+
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300..700;1,300..700&family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=Lora:ital,wght@0,400;1,400&family=Montserrat:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&family=Playfair+Display:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet">
|
|
11
|
+
<link rel="stylesheet" href="src/styles/main.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div class="header">
|
|
15
|
+
<div class="header-ornament">⧠⦠ā</div>
|
|
16
|
+
<h1 data-variable="TITLE"></h1>
|
|
17
|
+
<div class="header-line"></div>
|
|
18
|
+
<p class="header-sub love-note" data-variable="TEXT_TOP"></p>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="letter-list" id="letter-list"></div>
|
|
22
|
+
|
|
23
|
+
<div class="footer">
|
|
24
|
+
<div class="footer-line"></div>
|
|
25
|
+
<p data-variable="TEXT_BOTTOM"></p>
|
|
26
|
+
<p data-variable="YOUR_NAME"></p>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<script type="module" src="src/scripts/main.script.js"></script>
|
|
30
|
+
<script type="module" src="src/scripts/data.variable.js"></script>
|
|
31
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "moracarta",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"files": [ "index.html", "vite.config.mjs", "public", "src/commands", "src/config/fonts.js", "src/data/letters.example.js", "src/data/lettersLoader.js", "src/i18n", "src/scripts", "src/services", "src/styles", "src/views" ],
|
|
8
|
+
"bin": {
|
|
9
|
+
"moracarta": "src/commands/cliConfig.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"setup": "node src/commands/setup.js",
|
|
13
|
+
"dev": "vite --config vite.config.mjs",
|
|
14
|
+
"build": "vite build --config vite.config.mjs",
|
|
15
|
+
"preview": "vite preview --config vite.config.mjs",
|
|
16
|
+
"deploy": "node src/commands/deploy.js",
|
|
17
|
+
"add": "node src/commands/add.js"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/WesleyHanauer/moracarta.git"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [],
|
|
24
|
+
"author": "",
|
|
25
|
+
"license": "ISC",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/WesleyHanauer/moracarta/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/WesleyHanauer/moracarta#readme",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@inquirer/prompts": "^8.6.0",
|
|
32
|
+
"vite": "^8.2.2",
|
|
33
|
+
"wrangler": "^4.126.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { input } from "@inquirer/prompts";
|
|
4
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
import { getDocumentText } from "../services/googleDocs.js";
|
|
7
|
+
|
|
8
|
+
// After scaffolding, the real project lives in the user's cwd ā
|
|
9
|
+
// never in node_modules/moracarta ā so everything here is
|
|
10
|
+
// resolved against process.cwd(), not __dirname.
|
|
11
|
+
const DATA_DIRECTORY = resolve(
|
|
12
|
+
process.cwd(),
|
|
13
|
+
"src",
|
|
14
|
+
"data"
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const LETTERS_FILE = resolve(
|
|
18
|
+
DATA_DIRECTORY,
|
|
19
|
+
"letters.js"
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
function formatDate(date = new Date()) {
|
|
23
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
24
|
+
month: "short",
|
|
25
|
+
day: "numeric",
|
|
26
|
+
year: "numeric"
|
|
27
|
+
}).format(date);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function escapeTemplateLiteral(content) {
|
|
31
|
+
return content
|
|
32
|
+
.replaceAll("\\", "\\\\")
|
|
33
|
+
.replaceAll("`", "\\`")
|
|
34
|
+
.replaceAll("${", "\\${");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function readLettersFile() {
|
|
38
|
+
try {
|
|
39
|
+
return await readFile(LETTERS_FILE, "utf8");
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error.code === "ENOENT") {
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getNextId(lettersFile) {
|
|
50
|
+
const ids = [
|
|
51
|
+
...lettersFile.matchAll(/\bid:\s*(\d+)/g)
|
|
52
|
+
].map(match => Number(match[1]));
|
|
53
|
+
|
|
54
|
+
return ids.length > 0
|
|
55
|
+
? Math.max(...ids) + 1
|
|
56
|
+
: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function appendLetter(lettersFile, letter) {
|
|
60
|
+
const letterObject = ` {
|
|
61
|
+
id: ${letter.id},
|
|
62
|
+
date: '${letter.date}',
|
|
63
|
+
title: '${letter.title.replaceAll("'", "\\'")}',
|
|
64
|
+
content: \`${escapeTemplateLiteral(letter.content)}\`
|
|
65
|
+
}`;
|
|
66
|
+
|
|
67
|
+
if (!lettersFile.trim()) {
|
|
68
|
+
return `export const letters = [
|
|
69
|
+
${letterObject}
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const closingIndex = lettersFile.lastIndexOf("];");
|
|
76
|
+
|
|
77
|
+
if (closingIndex === -1) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
"Could not find the end of the letters array in src/data/letters.js."
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const beforeClosing = lettersFile
|
|
84
|
+
.slice(0, closingIndex)
|
|
85
|
+
.trimEnd();
|
|
86
|
+
|
|
87
|
+
const separator = beforeClosing.endsWith("[")
|
|
88
|
+
? "\n"
|
|
89
|
+
: ",\n";
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
beforeClosing +
|
|
93
|
+
separator +
|
|
94
|
+
letterObject +
|
|
95
|
+
"\n" +
|
|
96
|
+
lettersFile.slice(closingIndex)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function main() {
|
|
101
|
+
console.log("\nš Moracarta ā Add Letter\n");
|
|
102
|
+
|
|
103
|
+
const GOOGLE_DOCS_URL = "\x1b[34mhttps://docs.google.com/document/\x1b[0m";
|
|
104
|
+
|
|
105
|
+
const url = await input({
|
|
106
|
+
message: `Create a Google Doc for all your letters;
|
|
107
|
+
${GOOGLE_DOCS_URL} (Ctrl + click to open)
|
|
108
|
+
Each letter should have it's own tab;
|
|
109
|
+
Your Google Doc must be public;
|
|
110
|
+
(IMPORTANT) Use 5 spaces instead of TAB for identation;
|
|
111
|
+
You will have to add each letter individually;
|
|
112
|
+
Paste the Google Docs tab URL:`
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
console.log("\nFetching Google Docs tab...\n");
|
|
116
|
+
|
|
117
|
+
const content = await getDocumentText(url);
|
|
118
|
+
|
|
119
|
+
if (!content) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
"The Google Docs tab does not contain any text."
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const title = await input({
|
|
126
|
+
message: "Letter title:"
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (!title.trim()) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
"Letter title cannot be empty."
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await mkdir(DATA_DIRECTORY, {
|
|
136
|
+
recursive: true
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const lettersFile = await readLettersFile();
|
|
140
|
+
|
|
141
|
+
const nextId = getNextId(lettersFile);
|
|
142
|
+
|
|
143
|
+
const letter = {
|
|
144
|
+
id: nextId,
|
|
145
|
+
date: formatDate(),
|
|
146
|
+
title: title.trim(),
|
|
147
|
+
content
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const updatedFile = appendLetter(
|
|
151
|
+
lettersFile,
|
|
152
|
+
letter
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
await writeFile(
|
|
156
|
+
LETTERS_FILE,
|
|
157
|
+
updatedFile,
|
|
158
|
+
"utf8"
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
console.log("\nā Letter added successfully!");
|
|
162
|
+
console.log(` ID: ${letter.id}`);
|
|
163
|
+
console.log(` Title: ${letter.title}`);
|
|
164
|
+
console.log(` Date: ${letter.date}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
main().catch(error => {
|
|
168
|
+
console.error(`\nā ${error.message}`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const COMMANDS = new Set([
|
|
8
|
+
"setup",
|
|
9
|
+
"dev",
|
|
10
|
+
"build",
|
|
11
|
+
"preview",
|
|
12
|
+
"deploy",
|
|
13
|
+
"add",
|
|
14
|
+
"remove"
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const VITE_ARGS = {
|
|
18
|
+
dev: [],
|
|
19
|
+
build: ["build"],
|
|
20
|
+
preview: ["preview"]
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const command = process.argv[2];
|
|
24
|
+
const commandArguments = process.argv.slice(3);
|
|
25
|
+
|
|
26
|
+
if (!COMMANDS.has(command)) {
|
|
27
|
+
console.error(
|
|
28
|
+
"Usage: moracarta <setup|dev|build|preview|deploy|add|remove> [options]"
|
|
29
|
+
);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
34
|
+
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
35
|
+
|
|
36
|
+
const child =
|
|
37
|
+
command === "setup" ||
|
|
38
|
+
command === "deploy" ||
|
|
39
|
+
command === "add" ||
|
|
40
|
+
command === "remove"
|
|
41
|
+
? spawn(
|
|
42
|
+
process.execPath,
|
|
43
|
+
[
|
|
44
|
+
resolve(packageRoot, "src", "commands", `${command}.js`),
|
|
45
|
+
...commandArguments
|
|
46
|
+
],
|
|
47
|
+
{ stdio: "inherit" }
|
|
48
|
+
)
|
|
49
|
+
: spawn(
|
|
50
|
+
npxCommand,
|
|
51
|
+
[
|
|
52
|
+
"vite",
|
|
53
|
+
...VITE_ARGS[command],
|
|
54
|
+
"--config",
|
|
55
|
+
"vite.config.mjs",
|
|
56
|
+
...commandArguments
|
|
57
|
+
],
|
|
58
|
+
{ stdio: "inherit" }
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
child.on("exit", (code, signal) => {
|
|
62
|
+
if (signal) {
|
|
63
|
+
process.kill(process.pid, signal);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
process.exitCode = code ?? 1;
|
|
68
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
const configPath = resolve(process.cwd(), "src", "config", "globalVariables.js");
|
|
8
|
+
|
|
9
|
+
let globalVariables;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
({ default: globalVariables } = await import(pathToFileURL(configPath).href));
|
|
13
|
+
} catch (error) {
|
|
14
|
+
if (error.code === "ERR_MODULE_NOT_FOUND") {
|
|
15
|
+
console.error("\nā No configuration found. Run moracarta setup first.");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { PROJECT_NAME } = globalVariables;
|
|
23
|
+
|
|
24
|
+
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
25
|
+
|
|
26
|
+
console.log(`Building ${PROJECT_NAME}...`);
|
|
27
|
+
|
|
28
|
+
execSync(`${npxCommand} vite build --config vite.config.mjs`, {
|
|
29
|
+
stdio: "inherit"
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(`Deploying ${PROJECT_NAME} to Cloudflare Pages...`);
|
|
33
|
+
|
|
34
|
+
execSync(
|
|
35
|
+
`${npxCommand} wrangler pages deploy dist --project-name ${PROJECT_NAME}`,
|
|
36
|
+
{
|
|
37
|
+
stdio: "inherit"
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
console.log(`\nā¤ļø ${PROJECT_NAME} deployed successfully!`);
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { confirm, select } from "@inquirer/prompts";
|
|
4
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
|
|
7
|
+
const LETTERS_FILE = resolve(
|
|
8
|
+
process.cwd(),
|
|
9
|
+
"src",
|
|
10
|
+
"data",
|
|
11
|
+
"letters.js"
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
async function readLettersFile() {
|
|
15
|
+
try {
|
|
16
|
+
return await readFile(LETTERS_FILE, "utf8");
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error.code === "ENOENT") {
|
|
19
|
+
throw new Error(
|
|
20
|
+
"No letters have been added yet."
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extractLetters(lettersFile) {
|
|
29
|
+
const match = lettersFile.match(
|
|
30
|
+
/export const letters = \[([\s\S]*?)\];/
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (!match) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"Could not find the letters array in src/data/letters.js."
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const letters = [];
|
|
40
|
+
|
|
41
|
+
const objectRegex =
|
|
42
|
+
/\{[\s\S]*?content:\s*`[\s\S]*?`\s*\}/g;
|
|
43
|
+
|
|
44
|
+
const objects = match[1].match(objectRegex) ?? [];
|
|
45
|
+
|
|
46
|
+
for (const object of objects) {
|
|
47
|
+
const idMatch = object.match(/id:\s*(\d+)/);
|
|
48
|
+
const dateMatch = object.match(/date:\s*'([^']*)'/);
|
|
49
|
+
const titleMatch = object.match(
|
|
50
|
+
/title:\s*'((?:\\'|[^'])*)'/
|
|
51
|
+
);
|
|
52
|
+
const contentMatch = object.match(
|
|
53
|
+
/content:\s*`([\s\S]*?)`/
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (!idMatch || !titleMatch || !contentMatch) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
letters.push({
|
|
61
|
+
id: Number(idMatch[1]),
|
|
62
|
+
date: dateMatch?.[1] ?? "",
|
|
63
|
+
title: titleMatch[1].replaceAll("\\'", "'"),
|
|
64
|
+
content: contentMatch[1]
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return letters;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function escapeTemplateLiteral(content) {
|
|
72
|
+
return content
|
|
73
|
+
.replaceAll("\\", "\\\\")
|
|
74
|
+
.replaceAll("`", "\\`")
|
|
75
|
+
.replaceAll("${", "\\${");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function escapeSingleQuote(value) {
|
|
79
|
+
return value
|
|
80
|
+
.replaceAll("\\", "\\\\")
|
|
81
|
+
.replaceAll("'", "\\'");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function generateLettersFile(letters) {
|
|
85
|
+
const objects = letters.map(letter => ` {
|
|
86
|
+
id: ${letter.id},
|
|
87
|
+
date: '${escapeSingleQuote(letter.date)}',
|
|
88
|
+
title: '${escapeSingleQuote(letter.title)}',
|
|
89
|
+
content: \`${escapeTemplateLiteral(letter.content)}\`
|
|
90
|
+
}`);
|
|
91
|
+
|
|
92
|
+
return `export const letters = [
|
|
93
|
+
${objects.join(",\n")}
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function generateEmptyLettersFile() {
|
|
100
|
+
return `export const letters = [];
|
|
101
|
+
|
|
102
|
+
`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function main() {
|
|
106
|
+
console.log("\nš Moracarta ā Remove Letter\n");
|
|
107
|
+
|
|
108
|
+
const lettersFile = await readLettersFile();
|
|
109
|
+
const letters = extractLetters(lettersFile);
|
|
110
|
+
|
|
111
|
+
if (letters.length === 0) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
"No letters have been added yet."
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const choices = [
|
|
118
|
+
{
|
|
119
|
+
name: "Remove all letters",
|
|
120
|
+
value: "all"
|
|
121
|
+
},
|
|
122
|
+
...letters.map(letter => ({
|
|
123
|
+
name: `${letter.id} ā ${letter.title}`,
|
|
124
|
+
value: letter.id
|
|
125
|
+
}))
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
const selection = await select({
|
|
129
|
+
message: "Which letter do you want to remove?",
|
|
130
|
+
choices,
|
|
131
|
+
pageSize: 3
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (selection === "all") {
|
|
135
|
+
const confirmed = await confirm({
|
|
136
|
+
message: `Are you sure you want to remove all ${letters.length} letters?`,
|
|
137
|
+
default: false
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (!confirmed) {
|
|
141
|
+
console.log("\nā Operation cancelled.");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await writeFile(
|
|
146
|
+
LETTERS_FILE,
|
|
147
|
+
generateEmptyLettersFile(),
|
|
148
|
+
"utf8"
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
console.log(
|
|
152
|
+
`\nā All ${letters.length} letters were removed successfully!`
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const selectedLetter = letters.find(
|
|
159
|
+
letter => letter.id === selection
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (!selectedLetter) {
|
|
163
|
+
throw new Error("Selected letter could not be found.");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const remainingLetters = letters
|
|
167
|
+
.filter(letter => letter.id !== selection)
|
|
168
|
+
.map((letter, index) => ({
|
|
169
|
+
...letter,
|
|
170
|
+
id: index + 1
|
|
171
|
+
}));
|
|
172
|
+
|
|
173
|
+
const updatedFile = generateLettersFile(
|
|
174
|
+
remainingLetters
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
await writeFile(
|
|
178
|
+
LETTERS_FILE,
|
|
179
|
+
updatedFile,
|
|
180
|
+
"utf8"
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
console.log("\nā Letter removed successfully!");
|
|
184
|
+
console.log(` Removed: ${selectedLetter.title}`);
|
|
185
|
+
|
|
186
|
+
if (remainingLetters.length > 0) {
|
|
187
|
+
console.log(
|
|
188
|
+
` Remaining letters: ${remainingLetters.length}`
|
|
189
|
+
);
|
|
190
|
+
} else {
|
|
191
|
+
console.log(" No letters remaining.");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
main().catch(error => {
|
|
196
|
+
console.error(`\nā ${error.message}`);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
});
|