lumia-plugin 0.2.4 → 0.2.6
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 +1 -1
- package/examples/base_plugin/README.md +16 -0
- package/examples/base_plugin/main.js +90 -0
- package/examples/base_plugin/manifest.json +125 -0
- package/examples/base_plugin/package.json +10 -0
- package/package.json +6 -2
- package/scripts/build-plugin.js +37 -1
- package/scripts/clean-template.js +15 -0
- package/scripts/create-plugin.js +30 -13
- package/scripts/sync-template.js +47 -0
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Run any command with `--help` to see detailed options.
|
|
|
14
14
|
|
|
15
15
|
## Template
|
|
16
16
|
|
|
17
|
-
The CLI ships with a showcase template that demonstrates
|
|
17
|
+
The CLI ships with a showcase template that demonstrates settings, actions, variables, and alert handling. It mirrors the starter plugin available in this repository so the CLI can scaffold it anywhere.
|
|
18
18
|
|
|
19
19
|
## License
|
|
20
20
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Showcase Plugin Template
|
|
2
|
+
|
|
3
|
+
This template demonstrates a minimal, production-friendly Lumia Stream plugin workflow:
|
|
4
|
+
|
|
5
|
+
- Defines a small set of settings with a short setup tutorial
|
|
6
|
+
- Exposes a single action that triggers an alert
|
|
7
|
+
- Updates a few variables that alerts and other Lumia features can use
|
|
8
|
+
- Keeps logging to errors only
|
|
9
|
+
|
|
10
|
+
Use the CLI to copy and customize the template:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
npx lumia-plugin create my_plugin
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
After scaffolding you can tailor the manifest, code, and README to match your idea.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const { Plugin } = require("@lumiastream/plugin");
|
|
2
|
+
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
message: "Hello from Showcase Plugin!",
|
|
5
|
+
username: "Viewer",
|
|
6
|
+
color: "#00c2ff",
|
|
7
|
+
duration: 5,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const VARIABLE_NAMES = {
|
|
11
|
+
message: "message",
|
|
12
|
+
username: "username",
|
|
13
|
+
color: "color",
|
|
14
|
+
duration: "duration",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
class ShowcasePluginTemplate extends Plugin {
|
|
18
|
+
async onload() {
|
|
19
|
+
await this._syncDefaults();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async onsettingsupdate(settings, previous = {}) {
|
|
23
|
+
if (
|
|
24
|
+
settings?.defaultMessage !== previous?.defaultMessage ||
|
|
25
|
+
settings?.defaultColor !== previous?.defaultColor ||
|
|
26
|
+
settings?.defaultDuration !== previous?.defaultDuration
|
|
27
|
+
) {
|
|
28
|
+
await this._syncDefaults(settings);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async actions(config = {}) {
|
|
33
|
+
const actions = Array.isArray(config.actions) ? config.actions : [];
|
|
34
|
+
for (const action of actions) {
|
|
35
|
+
if (action?.type === "trigger_alert") {
|
|
36
|
+
await this._triggerSampleAlert(action.value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async _syncDefaults(settings = this.settings) {
|
|
42
|
+
const message = settings?.defaultMessage ?? DEFAULTS.message;
|
|
43
|
+
const color = settings?.defaultColor ?? DEFAULTS.color;
|
|
44
|
+
const duration = Number(settings?.defaultDuration ?? DEFAULTS.duration);
|
|
45
|
+
|
|
46
|
+
await this.lumia.setVariable(VARIABLE_NAMES.message, message);
|
|
47
|
+
await this.lumia.setVariable(VARIABLE_NAMES.color, color);
|
|
48
|
+
await this.lumia.setVariable(VARIABLE_NAMES.duration, duration);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async _triggerSampleAlert(data = {}) {
|
|
52
|
+
const username = data?.username ?? DEFAULTS.username;
|
|
53
|
+
const message =
|
|
54
|
+
data?.message ?? this.settings?.defaultMessage ?? DEFAULTS.message;
|
|
55
|
+
const color = data?.color ?? this.settings?.defaultColor ?? DEFAULTS.color;
|
|
56
|
+
const duration = Number(
|
|
57
|
+
data?.duration ?? this.settings?.defaultDuration ?? DEFAULTS.duration
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
await this.lumia.setVariable(VARIABLE_NAMES.username, username);
|
|
61
|
+
await this.lumia.setVariable(VARIABLE_NAMES.message, message);
|
|
62
|
+
await this.lumia.setVariable(VARIABLE_NAMES.color, color);
|
|
63
|
+
await this.lumia.setVariable(VARIABLE_NAMES.duration, duration);
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
await this.lumia.triggerAlert({
|
|
67
|
+
alert: "sample_alert",
|
|
68
|
+
dynamic: {
|
|
69
|
+
value: color,
|
|
70
|
+
username,
|
|
71
|
+
message,
|
|
72
|
+
color,
|
|
73
|
+
duration,
|
|
74
|
+
},
|
|
75
|
+
extraSettings: {
|
|
76
|
+
username,
|
|
77
|
+
message,
|
|
78
|
+
color,
|
|
79
|
+
duration,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
await this.lumia.addLog(
|
|
84
|
+
`Sample alert failed: ${error?.message ?? String(error)}`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = ShowcasePluginTemplate;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "showcase_plugin",
|
|
3
|
+
"name": "Showcase Plugin",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"author": "Lumia Stream",
|
|
6
|
+
"email": "",
|
|
7
|
+
"website": "",
|
|
8
|
+
"repository": "",
|
|
9
|
+
"description": "Starter template that demonstrates settings, actions, variables, and alerts with a minimal code path.",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"lumiaVersion": "^9.0.0",
|
|
12
|
+
"category": "apps",
|
|
13
|
+
"keywords": "sample, demo, lumia, showcase, template",
|
|
14
|
+
"icon": "",
|
|
15
|
+
"changelog": "",
|
|
16
|
+
"config": {
|
|
17
|
+
"settings": [
|
|
18
|
+
{
|
|
19
|
+
"key": "defaultMessage",
|
|
20
|
+
"label": "Default Message",
|
|
21
|
+
"type": "text",
|
|
22
|
+
"defaultValue": "Hello from Showcase Plugin!",
|
|
23
|
+
"helperText": "Used when the action does not supply a message."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"key": "defaultColor",
|
|
27
|
+
"label": "Default Color",
|
|
28
|
+
"type": "color",
|
|
29
|
+
"defaultValue": "#00c2ff",
|
|
30
|
+
"helperText": "Used when the action does not supply a color."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"key": "defaultDuration",
|
|
34
|
+
"label": "Default Duration (seconds)",
|
|
35
|
+
"type": "number",
|
|
36
|
+
"defaultValue": 5,
|
|
37
|
+
"min": 1,
|
|
38
|
+
"max": 60,
|
|
39
|
+
"helperText": "Used when the action does not supply a duration."
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"settings_tutorial": "---\n### Setup\n1) Enter a default message and color.\n2) Adjust the default duration if you want a longer or shorter alert.\n3) Click Save to store the defaults.\n---\n### What this plugin does\n- Stores the message, username, color, and duration in variables.\n- Uses those values when triggering the sample alert.\n---",
|
|
43
|
+
"actions_tutorial": "---\n### Trigger Sample Alert\nUse this action to fire the sample alert. You can override the message, username, color, and duration per action. The alert uses both dynamic and extraSettings so variations and templates have the same data.\n---",
|
|
44
|
+
"actions": [
|
|
45
|
+
{
|
|
46
|
+
"type": "trigger_alert",
|
|
47
|
+
"label": "Trigger Sample Alert",
|
|
48
|
+
"description": "Trigger the sample alert with optional overrides.",
|
|
49
|
+
"fields": [
|
|
50
|
+
{
|
|
51
|
+
"key": "username",
|
|
52
|
+
"label": "Username",
|
|
53
|
+
"type": "text",
|
|
54
|
+
"defaultValue": "Viewer"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"key": "message",
|
|
58
|
+
"label": "Message",
|
|
59
|
+
"type": "text",
|
|
60
|
+
"defaultValue": "Hello from Showcase Plugin!"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"key": "color",
|
|
64
|
+
"label": "Color",
|
|
65
|
+
"type": "color",
|
|
66
|
+
"defaultValue": "#00c2ff"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"key": "duration",
|
|
70
|
+
"label": "Duration (seconds)",
|
|
71
|
+
"type": "number",
|
|
72
|
+
"defaultValue": 5,
|
|
73
|
+
"min": 1,
|
|
74
|
+
"max": 60
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"variables": [
|
|
80
|
+
{
|
|
81
|
+
"name": "message",
|
|
82
|
+
"description": "Stores the most recent message handled by the plugin.",
|
|
83
|
+
"value": ""
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "username",
|
|
87
|
+
"description": "Stores the most recent username handled by the plugin.",
|
|
88
|
+
"value": ""
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "color",
|
|
92
|
+
"description": "Tracks the color used by the latest sample alert.",
|
|
93
|
+
"value": ""
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "duration",
|
|
97
|
+
"description": "Tracks the duration used by the latest sample alert.",
|
|
98
|
+
"value": 0
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
"alerts": [
|
|
102
|
+
{
|
|
103
|
+
"title": "Sample Alert",
|
|
104
|
+
"key": "sample_alert",
|
|
105
|
+
"acceptedVariables": [
|
|
106
|
+
"message",
|
|
107
|
+
"username",
|
|
108
|
+
"color",
|
|
109
|
+
"duration"
|
|
110
|
+
],
|
|
111
|
+
"defaultMessage": "{{username}}: {{message}}",
|
|
112
|
+
"variationConditions": [
|
|
113
|
+
{
|
|
114
|
+
"type": "EQUAL_SELECTION",
|
|
115
|
+
"description": "Matches dynamic.value against the selected color.",
|
|
116
|
+
"selections": [
|
|
117
|
+
{ "label": "Blue", "value": "#00c2ff" },
|
|
118
|
+
{ "label": "Red", "value": "#ff5f5f" }
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lumia-showcase-plugin-template",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Internal template illustrating settings, actions, variables, and alerts for Lumia Stream plugins.",
|
|
6
|
+
"main": "main.js",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@lumiastream/plugin": "^0.2.6"
|
|
9
|
+
}
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumia-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Command-line tools for creating, building, and validating Lumia Stream plugins.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"lumia-plugin": "scripts/cli.js"
|
|
7
7
|
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"prepack": "node scripts/sync-template.js",
|
|
10
|
+
"postpack": "node scripts/clean-template.js"
|
|
11
|
+
},
|
|
8
12
|
"files": [
|
|
9
13
|
"scripts",
|
|
10
14
|
"examples/base_plugin",
|
|
@@ -20,7 +24,7 @@
|
|
|
20
24
|
"author": "Lumia Stream",
|
|
21
25
|
"license": "MIT",
|
|
22
26
|
"dependencies": {
|
|
23
|
-
"@lumiastream/plugin": "^0.2.
|
|
27
|
+
"@lumiastream/plugin": "^0.2.6",
|
|
24
28
|
"jszip": "3.10.1"
|
|
25
29
|
}
|
|
26
30
|
}
|
package/scripts/build-plugin.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const fs = require("fs");
|
|
4
|
+
const { spawnSync } = require("child_process");
|
|
4
5
|
const {
|
|
5
6
|
readManifest,
|
|
6
7
|
validateManifest,
|
|
@@ -64,6 +65,21 @@ async function main() {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
try {
|
|
68
|
+
const packageJsonPath = path.join(pluginDir, "package.json");
|
|
69
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
70
|
+
console.log("• Running npm install...");
|
|
71
|
+
const result = spawnSync("npm", ["install"], {
|
|
72
|
+
cwd: pluginDir,
|
|
73
|
+
stdio: "inherit",
|
|
74
|
+
});
|
|
75
|
+
if (result.status !== 0) {
|
|
76
|
+
console.error("✖ npm install failed. Aborting build.");
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
console.log("• No package.json found; skipping npm install.");
|
|
81
|
+
}
|
|
82
|
+
|
|
67
83
|
const { manifest } = await readManifest(pluginDir);
|
|
68
84
|
const errors = validateManifest(manifest);
|
|
69
85
|
if (errors.length) {
|
|
@@ -72,10 +88,30 @@ async function main() {
|
|
|
72
88
|
process.exit(1);
|
|
73
89
|
}
|
|
74
90
|
|
|
91
|
+
const entryFile =
|
|
92
|
+
typeof manifest.main === "string" && manifest.main.trim()
|
|
93
|
+
? manifest.main.trim()
|
|
94
|
+
: "main.js";
|
|
95
|
+
const entryPath = path.resolve(pluginDir, entryFile);
|
|
96
|
+
if (!fs.existsSync(entryPath)) {
|
|
97
|
+
console.error(`✖ Entry file not found: ${entryFile}`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log(`• Running syntax check on ${entryFile}...`);
|
|
102
|
+
const checkResult = spawnSync("node", ["--check", entryPath], {
|
|
103
|
+
cwd: pluginDir,
|
|
104
|
+
stdio: "inherit",
|
|
105
|
+
});
|
|
106
|
+
if (checkResult.status !== 0) {
|
|
107
|
+
console.error("✖ Syntax check failed. Aborting build.");
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
75
111
|
const files = await collectFiles(pluginDir);
|
|
76
112
|
if (!files.length) {
|
|
77
113
|
console.error(
|
|
78
|
-
"✖ No files found to package (did you point to the plugin root?)"
|
|
114
|
+
"✖ No files found to package (did you point to the plugin root?)",
|
|
79
115
|
);
|
|
80
116
|
process.exit(1);
|
|
81
117
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
const DEST_DIR = path.resolve(__dirname, "..", "examples", "base_plugin");
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
if (!fs.existsSync(DEST_DIR)) return;
|
|
9
|
+
await fs.promises.rm(DEST_DIR, { recursive: true, force: true });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
main().catch((error) => {
|
|
13
|
+
console.error("✖ Failed to clean template:", error?.message ?? error);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
package/scripts/create-plugin.js
CHANGED
|
@@ -2,7 +2,19 @@
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const TEMPLATE_DIRS = [
|
|
6
|
+
path.resolve(__dirname, "..", "..", "examples", "base_plugin"),
|
|
7
|
+
path.resolve(__dirname, "..", "examples", "base_plugin"),
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
function resolveTemplateDir() {
|
|
11
|
+
for (const candidate of TEMPLATE_DIRS) {
|
|
12
|
+
if (fs.existsSync(candidate)) {
|
|
13
|
+
return candidate;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
6
18
|
|
|
7
19
|
function toSafeId(value) {
|
|
8
20
|
const cleaned = value
|
|
@@ -95,18 +107,18 @@ async function updateManifest(manifestPath, pluginId, displayName) {
|
|
|
95
107
|
if (manifest.config && typeof manifest.config === "object") {
|
|
96
108
|
const { settings, actions, variables } = manifest.config;
|
|
97
109
|
|
|
98
|
-
const
|
|
99
|
-
(setting) => setting?.key === "
|
|
110
|
+
const defaultMessageSetting = ensureArray(settings).find(
|
|
111
|
+
(setting) => setting?.key === "defaultMessage"
|
|
100
112
|
);
|
|
101
|
-
if (
|
|
102
|
-
|
|
113
|
+
if (defaultMessageSetting) {
|
|
114
|
+
defaultMessageSetting.defaultValue = `Hello from ${displayName}!`;
|
|
103
115
|
}
|
|
104
116
|
|
|
105
|
-
const
|
|
106
|
-
(action) => action?.type === "
|
|
117
|
+
const triggerAlertAction = ensureArray(actions).find(
|
|
118
|
+
(action) => action?.type === "trigger_alert"
|
|
107
119
|
);
|
|
108
|
-
if (
|
|
109
|
-
const messageField =
|
|
120
|
+
if (triggerAlertAction && Array.isArray(triggerAlertAction.fields)) {
|
|
121
|
+
const messageField = triggerAlertAction.fields.find(
|
|
110
122
|
(field) => field?.key === "message"
|
|
111
123
|
);
|
|
112
124
|
if (messageField) {
|
|
@@ -119,7 +131,7 @@ async function updateManifest(manifestPath, pluginId, displayName) {
|
|
|
119
131
|
if (variable.origin) {
|
|
120
132
|
variable.origin = pluginId;
|
|
121
133
|
}
|
|
122
|
-
if (variable.name === "
|
|
134
|
+
if (variable.name === "message" && "example" in variable) {
|
|
123
135
|
variable.example = `Hello from ${displayName}!`;
|
|
124
136
|
}
|
|
125
137
|
}
|
|
@@ -180,8 +192,13 @@ async function main() {
|
|
|
180
192
|
return;
|
|
181
193
|
}
|
|
182
194
|
|
|
183
|
-
|
|
184
|
-
|
|
195
|
+
const templateDir = resolveTemplateDir();
|
|
196
|
+
if (!templateDir) {
|
|
197
|
+
console.error("✖ Template directory not found.");
|
|
198
|
+
console.error(" Looked in:");
|
|
199
|
+
for (const candidate of TEMPLATE_DIRS) {
|
|
200
|
+
console.error(` - ${candidate}`);
|
|
201
|
+
}
|
|
185
202
|
console.error(" Tip: Make sure lumia-plugin is properly installed.");
|
|
186
203
|
process.exit(1);
|
|
187
204
|
}
|
|
@@ -198,7 +215,7 @@ async function main() {
|
|
|
198
215
|
await ensureEmptyDir(targetDir);
|
|
199
216
|
|
|
200
217
|
console.log(" 📋 Copying template files...");
|
|
201
|
-
await copyTemplate(
|
|
218
|
+
await copyTemplate(templateDir, targetDir);
|
|
202
219
|
|
|
203
220
|
console.log(" ✏️ Updating manifest.json...");
|
|
204
221
|
await updateManifest(
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
const SOURCE_DIR = path.resolve(__dirname, "..", "..", "examples", "base_plugin");
|
|
6
|
+
const DEST_DIR = path.resolve(__dirname, "..", "examples", "base_plugin");
|
|
7
|
+
|
|
8
|
+
async function copyDir(src, dest) {
|
|
9
|
+
const stats = await fs.promises.stat(src);
|
|
10
|
+
if (stats.isDirectory()) {
|
|
11
|
+
await fs.promises.mkdir(dest, { recursive: true });
|
|
12
|
+
const entries = await fs.promises.readdir(src);
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
await copyDir(path.join(src, entry), path.join(dest, entry));
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (stats.isFile()) {
|
|
19
|
+
await fs.promises.copyFile(src, dest);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function main() {
|
|
24
|
+
const sourceExists = fs.existsSync(SOURCE_DIR);
|
|
25
|
+
const destExists = fs.existsSync(DEST_DIR);
|
|
26
|
+
|
|
27
|
+
if (!sourceExists && !destExists) {
|
|
28
|
+
console.error("✖ Template source not found:", SOURCE_DIR);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!sourceExists) {
|
|
33
|
+
console.warn("⚠ Template source missing, leaving existing copy in place.");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (destExists) {
|
|
38
|
+
await fs.promises.rm(DEST_DIR, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await copyDir(SOURCE_DIR, DEST_DIR);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
main().catch((error) => {
|
|
45
|
+
console.error("✖ Failed to sync template:", error?.message ?? error);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|