sammi-next 1.4.2 → 1.5.1
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 +12 -4
- package/bin/sammi-next.mjs +3 -0
- package/dist/node/cli.mjs +324 -2
- package/dist/node/cli.mjs.map +1 -1
- package/dist/runtime/{index.d.mts → index.d.ts} +35 -4
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/{index.mjs → index.js} +5 -22
- package/dist/runtime/index.js.map +1 -0
- package/dist/shared/{config-types.d.mts → config-types.d.ts} +3 -2
- package/dist/shared/{config-types.d.mts.map → config-types.d.ts.map} +1 -1
- package/dist/shared/{config-types.mjs → config-types.js} +3 -2
- package/dist/shared/config-types.js.map +1 -0
- package/package.json +14 -10
- package/dist/build-CZcDZxfX.mjs +0 -170
- package/dist/build-CZcDZxfX.mjs.map +0 -1
- package/dist/global-BaL6-Y6j.d.mts +0 -349
- package/dist/global-BaL6-Y6j.d.mts.map +0 -1
- package/dist/node/build.d.mts +0 -20
- package/dist/node/build.d.mts.map +0 -1
- package/dist/node/build.mjs +0 -3
- package/dist/node/config.d.mts +0 -11
- package/dist/node/config.d.mts.map +0 -1
- package/dist/node/config.mjs +0 -164
- package/dist/node/config.mjs.map +0 -1
- package/dist/runtime/index.d.mts.map +0 -1
- package/dist/runtime/index.mjs.map +0 -1
- package/dist/shared/config-types.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -2,15 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
> Modern builder and runtime for SAMMI Extensions
|
|
4
4
|
|
|
5
|
-
Features
|
|
5
|
+
## Features
|
|
6
6
|
- Extension Preview
|
|
7
7
|
- Helper functions
|
|
8
8
|
- Rich Features
|
|
9
9
|
- Optimize Build
|
|
10
10
|
- Fully Typed APIs
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- A
|
|
12
|
+
## Description
|
|
13
|
+
SAMMI Next is a [SAMMI] Bridge extensions build tool inspired on [Vite] that significantly improves the extension development experience. It consists of two major parts:
|
|
14
|
+
- A runtime API with helper functions to develop a [SAMMI] extension with the SAMMI Next format.
|
|
15
|
+
- A cli that bundles your code with [tsdown], pre-configured to output highly optimized code for production.
|
|
15
16
|
|
|
17
|
+
## Import Globals
|
|
18
|
+
```TypeScript
|
|
19
|
+
/// <reference types="sammi-next" />
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
[SAMMI]: https://sammi.solutions/
|
|
23
|
+
[Vite]: https://vite.dev/
|
|
16
24
|
[tsdown]: https://tsdown.dev/
|
package/dist/node/cli.mjs
CHANGED
|
@@ -1,8 +1,330 @@
|
|
|
1
|
-
import { a as version, r as buildExtension, t as BuildMode } from "../build-CZcDZxfX.mjs";
|
|
2
|
-
import { resolveBuildConfig } from "./config.mjs";
|
|
3
1
|
import cac from "cac";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
4
|
import colors from "picocolors";
|
|
5
|
+
import chokidar from "chokidar";
|
|
6
|
+
import { build } from "tsdown";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import Ajv from "ajv";
|
|
5
9
|
|
|
10
|
+
//#region package.json
|
|
11
|
+
var version = "1.5.0";
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/node/constants.ts
|
|
15
|
+
const DEFAULT_CONFIG_EXTENSIONS = [
|
|
16
|
+
".mjs",
|
|
17
|
+
".js",
|
|
18
|
+
".mts",
|
|
19
|
+
".ts"
|
|
20
|
+
];
|
|
21
|
+
const GLOBAL_NAME = "SAMMIExtensions";
|
|
22
|
+
const BUILD_PREFIX = colors.blue("[sammi-next]");
|
|
23
|
+
const GREEN_CHECK = colors.green("✔");
|
|
24
|
+
const RED_X = colors.red("✗");
|
|
25
|
+
function findPackageDir() {
|
|
26
|
+
let initPath = fileURLToPath(import.meta.url);
|
|
27
|
+
while (!initPath.endsWith("sammi-next")) initPath = path.resolve(initPath, "..");
|
|
28
|
+
return initPath;
|
|
29
|
+
}
|
|
30
|
+
const SAMMI_NEXT_PACKAGE_DIR = findPackageDir();
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/node/utils.ts
|
|
34
|
+
function displayTime(time) {
|
|
35
|
+
if (time < 1e3) return `${time}ms`;
|
|
36
|
+
time = time / 1e3;
|
|
37
|
+
if (time < 60) return `${time.toFixed(2)}s`;
|
|
38
|
+
const mins = Math.floor(time / 60);
|
|
39
|
+
const seconds = Math.round(time % 60);
|
|
40
|
+
if (seconds === 60) return `${mins + 1}m`;
|
|
41
|
+
return `${mins}m${seconds < 1 ? "" : ` ${seconds}s`}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/node/build.ts
|
|
46
|
+
let BuildMode = /* @__PURE__ */ function(BuildMode) {
|
|
47
|
+
BuildMode[BuildMode["DEV"] = 0] = "DEV";
|
|
48
|
+
BuildMode[BuildMode["PRODUCTION"] = 1] = "PRODUCTION";
|
|
49
|
+
return BuildMode;
|
|
50
|
+
}({});
|
|
51
|
+
const BuildModes = Object.keys(BuildMode).filter((key) => isNaN(Number(key)));
|
|
52
|
+
function readOptionalFile(path$1) {
|
|
53
|
+
if (!fs.existsSync(path$1)) return;
|
|
54
|
+
return fs.readFileSync(path$1, "utf-8");
|
|
55
|
+
}
|
|
56
|
+
const CommandRE = /\w+\(\w+,\s*{\s*default:/gm;
|
|
57
|
+
function generateSEF(options) {
|
|
58
|
+
const { config, rootDir, mode } = options;
|
|
59
|
+
const content = [];
|
|
60
|
+
const required_files = [
|
|
61
|
+
{
|
|
62
|
+
header: "extension_name",
|
|
63
|
+
content: config.name
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
header: "extension_info",
|
|
67
|
+
content: config.info
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
header: "extension_version",
|
|
71
|
+
content: config.version
|
|
72
|
+
}
|
|
73
|
+
];
|
|
74
|
+
for (const field of required_files) content.push(`[${field.header}]`, field.content, "");
|
|
75
|
+
const external = readOptionalFile(config.external);
|
|
76
|
+
content.push("[insert_external]", external ? `<div id="${config.id}-external">${external}</div>` : "", "");
|
|
77
|
+
const js_script = fs.readFileSync(path.join(rootDir, config.out.dir, config.out.js), "utf-8");
|
|
78
|
+
content.push("[insert_command]", CommandRE.test(js_script) ? `${GLOBAL_NAME}.${config.id}.default()` : "", "");
|
|
79
|
+
content.push("[insert_hook]", "", "");
|
|
80
|
+
content.push("[insert_script]", js_script, "");
|
|
81
|
+
let over = readOptionalFile(config.over);
|
|
82
|
+
if (over && mode === BuildMode.PRODUCTION) over = JSON.stringify(JSON.parse(over));
|
|
83
|
+
content.push("[insert_over]", over && over != "{}" ? over : "", "");
|
|
84
|
+
return content.join("\n");
|
|
85
|
+
}
|
|
86
|
+
function generatePreview(options) {
|
|
87
|
+
const { config, rootDir } = options;
|
|
88
|
+
const external = readOptionalFile(config.external);
|
|
89
|
+
const js_script = fs.readFileSync(path.join(rootDir, config.out.dir, config.out.js), "utf-8");
|
|
90
|
+
return fs.readFileSync(path.join(SAMMI_NEXT_PACKAGE_DIR, ".sammi", "preview.blueprint.html"), "utf-8").replace(/{{EXTERNAL}}/g, external ? `<div id="${config.id}-external">${external}</div>` : "").replace(/{{SCRIPT}}/g, js_script);
|
|
91
|
+
}
|
|
92
|
+
async function buildOnce(options) {
|
|
93
|
+
const { config, rootDir, mode } = options;
|
|
94
|
+
const startTime = Date.now();
|
|
95
|
+
const bundle = await build({
|
|
96
|
+
entry: [config.entry],
|
|
97
|
+
outDir: path.join(rootDir, config.out.dir),
|
|
98
|
+
platform: "browser",
|
|
99
|
+
format: "iife",
|
|
100
|
+
target: ["es2022"],
|
|
101
|
+
sourcemap: false,
|
|
102
|
+
minify: mode === BuildMode.PRODUCTION,
|
|
103
|
+
banner: { js: `/* ${config.name} v${config.version} - Built with SAMMI Next v${version} */` },
|
|
104
|
+
noExternal: ["**"],
|
|
105
|
+
outputOptions: {
|
|
106
|
+
entryFileNames: config.out.js,
|
|
107
|
+
extend: true,
|
|
108
|
+
name: `${GLOBAL_NAME}.${config.id}`,
|
|
109
|
+
exports: "named"
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
const tsdownTime = Date.now();
|
|
113
|
+
console.info(GREEN_CHECK, BUILD_PREFIX, `built ${config.out.js} in ${displayTime(tsdownTime - startTime)}`);
|
|
114
|
+
fs.writeFileSync(path.join(rootDir, config.out.dir, config.out.sef), generateSEF(options), "utf-8");
|
|
115
|
+
const sefTime = Date.now();
|
|
116
|
+
console.info(GREEN_CHECK, BUILD_PREFIX, `built ${config.out.sef} in ${displayTime(sefTime - tsdownTime)}`);
|
|
117
|
+
fs.writeFileSync(path.join(rootDir, config.out.dir, "preview.html"), generatePreview(options), "utf-8");
|
|
118
|
+
const previewTime = Date.now();
|
|
119
|
+
console.info(GREEN_CHECK, BUILD_PREFIX, `built preview.html in ${displayTime(previewTime - sefTime)}`);
|
|
120
|
+
return {
|
|
121
|
+
bundle,
|
|
122
|
+
startTime
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function buildExtension(options) {
|
|
126
|
+
const { config, mode } = options;
|
|
127
|
+
console.info(colors.cyan(`SAMMI Next v${version} ${colors.green(`building "${config.name}" extension in ${BuildMode[mode].toLowerCase()} mode...`)}`));
|
|
128
|
+
let bundle;
|
|
129
|
+
let startTime;
|
|
130
|
+
try {
|
|
131
|
+
const res = await buildOnce(options);
|
|
132
|
+
bundle = res.bundle;
|
|
133
|
+
startTime = res.startTime;
|
|
134
|
+
if (options.mode !== BuildMode.DEV) return bundle;
|
|
135
|
+
console.info(BUILD_PREFIX, colors.cyan("watching for file changes..."));
|
|
136
|
+
const watchPaths = [
|
|
137
|
+
path.dirname(config.entry),
|
|
138
|
+
config.external,
|
|
139
|
+
config.over
|
|
140
|
+
].filter(Boolean);
|
|
141
|
+
const watcher = chokidar.watch(watchPaths, { ignoreInitial: true });
|
|
142
|
+
let timer = null;
|
|
143
|
+
watcher.on("all", (event, p) => {
|
|
144
|
+
console.info(colors.cyan(`${event}: ${p}`));
|
|
145
|
+
if (timer) clearTimeout(timer);
|
|
146
|
+
timer = setTimeout(() => {
|
|
147
|
+
buildOnce(options).then((res$1) => {
|
|
148
|
+
bundle = res$1.bundle;
|
|
149
|
+
startTime = res$1.startTime;
|
|
150
|
+
}).catch((e) => console.error(e));
|
|
151
|
+
}, 100);
|
|
152
|
+
});
|
|
153
|
+
process.on("SIGINT", () => {
|
|
154
|
+
console.info("\nStopping watch mode...");
|
|
155
|
+
watcher.close().then(() => process.exit(0)).catch((e) => {
|
|
156
|
+
console.error(e);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
return watcher;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (startTime) {
|
|
163
|
+
console.error(RED_X, BUILD_PREFIX, `Build failed in ${displayTime(Date.now() - startTime)}`);
|
|
164
|
+
startTime = void 0;
|
|
165
|
+
}
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/node/config.ts
|
|
172
|
+
const ajv = new Ajv();
|
|
173
|
+
ajv.addKeyword({
|
|
174
|
+
keyword: "fileExists",
|
|
175
|
+
validate: (_schema, data) => {
|
|
176
|
+
if (!data) return true;
|
|
177
|
+
return fs.existsSync(data);
|
|
178
|
+
},
|
|
179
|
+
errors: false
|
|
180
|
+
});
|
|
181
|
+
const configValidator = ajv.compile({
|
|
182
|
+
type: "object",
|
|
183
|
+
properties: {
|
|
184
|
+
id: {
|
|
185
|
+
type: "string",
|
|
186
|
+
minLength: 1,
|
|
187
|
+
pattern: "^[a-zA-Z0-9-_]+$"
|
|
188
|
+
},
|
|
189
|
+
name: {
|
|
190
|
+
type: "string",
|
|
191
|
+
minLength: 1,
|
|
192
|
+
pattern: "^[a-zA-Z0-9 -_]+$"
|
|
193
|
+
},
|
|
194
|
+
info: {
|
|
195
|
+
type: "string",
|
|
196
|
+
default: "",
|
|
197
|
+
nullable: true
|
|
198
|
+
},
|
|
199
|
+
version: {
|
|
200
|
+
type: "string",
|
|
201
|
+
minLength: 1,
|
|
202
|
+
pattern: "^\\d+(?:\\.\\d+)*(?:-.*)?$"
|
|
203
|
+
},
|
|
204
|
+
entry: {
|
|
205
|
+
type: "string",
|
|
206
|
+
minLength: 1,
|
|
207
|
+
fileExists: true
|
|
208
|
+
},
|
|
209
|
+
external: {
|
|
210
|
+
type: "string",
|
|
211
|
+
minLength: 1,
|
|
212
|
+
fileExists: true,
|
|
213
|
+
nullable: true
|
|
214
|
+
},
|
|
215
|
+
over: {
|
|
216
|
+
type: "string",
|
|
217
|
+
minLength: 1,
|
|
218
|
+
fileExists: true,
|
|
219
|
+
nullable: true
|
|
220
|
+
},
|
|
221
|
+
out: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
dir: {
|
|
225
|
+
type: "string",
|
|
226
|
+
minLength: 1,
|
|
227
|
+
pattern: "^[^<>:\"|?*]+$",
|
|
228
|
+
default: "dist",
|
|
229
|
+
nullable: true
|
|
230
|
+
},
|
|
231
|
+
js: {
|
|
232
|
+
type: "string",
|
|
233
|
+
minLength: 4,
|
|
234
|
+
pattern: "^[\\w\\-. ]+\\.js$",
|
|
235
|
+
default: "extension.js",
|
|
236
|
+
nullable: true
|
|
237
|
+
},
|
|
238
|
+
sef: {
|
|
239
|
+
type: "string",
|
|
240
|
+
minLength: 5,
|
|
241
|
+
pattern: "^[\\w\\-. ]+\\.sef$",
|
|
242
|
+
default: "extension.sef",
|
|
243
|
+
nullable: true
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
required: [],
|
|
247
|
+
nullable: true,
|
|
248
|
+
additionalProperties: false
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
required: [
|
|
252
|
+
"name",
|
|
253
|
+
"id",
|
|
254
|
+
"version",
|
|
255
|
+
"entry"
|
|
256
|
+
],
|
|
257
|
+
additionalProperties: false
|
|
258
|
+
});
|
|
259
|
+
async function loadConfig(rootDir) {
|
|
260
|
+
for (const ext of DEFAULT_CONFIG_EXTENSIONS) {
|
|
261
|
+
const configPath = path.join(rootDir, `sammi.config${ext}`);
|
|
262
|
+
if (!fs.existsSync(configPath)) continue;
|
|
263
|
+
try {
|
|
264
|
+
const { createJiti } = await import("jiti");
|
|
265
|
+
return validateExtensionConfig(await createJiti(rootDir, {
|
|
266
|
+
interopDefault: true,
|
|
267
|
+
moduleCache: true
|
|
268
|
+
}).import(configPath, { default: true }), configPath);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
throw new Error(`Error loading ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const jsonPath = path.join(rootDir, "sammi.config.json");
|
|
274
|
+
if (fs.existsSync(jsonPath)) try {
|
|
275
|
+
const raw = fs.readFileSync(jsonPath, "utf-8");
|
|
276
|
+
return validateExtensionConfig(JSON.parse(raw), jsonPath);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
throw new Error(`Error loading ${jsonPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
279
|
+
}
|
|
280
|
+
throw new Error("SAMMI Next extension config file not found in the root dir.");
|
|
281
|
+
}
|
|
282
|
+
function validateExtensionConfig(config, configPath) {
|
|
283
|
+
if (!configValidator(config)) {
|
|
284
|
+
const errors = configValidator.errors?.map((err) => ` - ${err.instancePath} ${err.message}`).join("\n");
|
|
285
|
+
throw new Error(`Invalid config from ${configPath}:\n${errors}`);
|
|
286
|
+
}
|
|
287
|
+
return config;
|
|
288
|
+
}
|
|
289
|
+
function resolveExtensionConfig(config, rootDir) {
|
|
290
|
+
const resolved = {
|
|
291
|
+
id: config.id,
|
|
292
|
+
name: config.name,
|
|
293
|
+
version: config.version,
|
|
294
|
+
info: config.info || "",
|
|
295
|
+
entry: path.resolve(rootDir, config.entry),
|
|
296
|
+
external: config.external ? path.resolve(rootDir, config.external) : "",
|
|
297
|
+
over: config.over ? path.resolve(rootDir, config.over) : "",
|
|
298
|
+
out: {
|
|
299
|
+
dir: config.out?.dir || "dist",
|
|
300
|
+
js: config.out?.js || "extension.js",
|
|
301
|
+
sef: config.out?.sef || "extension.sef"
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
if (!fs.existsSync(resolved.entry)) throw new Error(`Entry file not found: ${resolved.entry}`);
|
|
305
|
+
if (resolved.external && !fs.existsSync(resolved.external)) throw new Error(`External file not found: ${resolved.external}`);
|
|
306
|
+
if (resolved.over && !fs.existsSync(resolved.over)) throw new Error(`Over file not found: ${resolved.over}`);
|
|
307
|
+
return resolved;
|
|
308
|
+
}
|
|
309
|
+
async function resolveBuildConfig(root, command, build_cli) {
|
|
310
|
+
const mode = BuildModes.findIndex((m) => {
|
|
311
|
+
return m.toLowerCase() === command.toLowerCase();
|
|
312
|
+
});
|
|
313
|
+
if (mode < 0) throw new Error(`Invalid mode: ${command}. It must be one of: ${BuildModes.join(", ")}`);
|
|
314
|
+
const rootDir = root ?? process.cwd();
|
|
315
|
+
const config = await loadConfig(rootDir);
|
|
316
|
+
config.out ??= {};
|
|
317
|
+
config.out.dir = build_cli.outDir ?? config.out.dir;
|
|
318
|
+
config.out.js = build_cli.outJs ?? config.out.js;
|
|
319
|
+
config.out.sef = build_cli.outSef ?? config.out.sef;
|
|
320
|
+
return {
|
|
321
|
+
rootDir,
|
|
322
|
+
mode,
|
|
323
|
+
config: resolveExtensionConfig(config, rootDir)
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
//#endregion
|
|
6
328
|
//#region src/node/cli.ts
|
|
7
329
|
const cli = cac("sammi-next");
|
|
8
330
|
const filterDuplicateOptions = (options) => {
|
package/dist/node/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":["VERSION"],"sources":["../../src/node/cli.ts"],"sourcesContent":["import cac from \"cac\";\nimport { buildExtension, BuildMode } from \"./build\";\nimport colors from 'picocolors';\nimport { resolveBuildConfig } from \"./config\";\nimport { VERSION } from \"./constants\";\n\nconst cli = cac('sammi-next');\n\nexport interface GlobalCLIOptions {\n m?: string\n mode?: string\n}\n\nconst filterDuplicateOptions = <T extends object>(options: T) => {\n for (const [key, value] of Object.entries(options)) {\n if (!Array.isArray(value)) continue;\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n options[key as keyof T] = value[value.length - 1];\n }\n};\n\nfunction cleanGlobalCLIOptions<Options extends GlobalCLIOptions>(options: Options): Omit<Options, keyof GlobalCLIOptions> {\n const ret = { ...options };\n delete ret.m\n delete ret.mode\n\n return ret;\n}\n\ncli.option('-m, --mode <mode>', '[string] set build mode');\n\nexport interface BuildCLIOptions {\n outDir?: string\n outJs?: string\n outSef?: string\n}\n\ncli\n .command('[root]', 'build extension')\n .option('--outDir <dir>', '[string] output directory (default: \"dist\")')\n .option('--outJs <name>', '[string] output file name for the JS (default: extension.js)')\n .option('--outSef <name>', '[string] output file name for the SEF (default: extension.sef)')\n .action(\n async (\n root: string | undefined,\n options: GlobalCLIOptions,\n ) => {\n filterDuplicateOptions(options);\n\n const buildOptions: BuildCLIOptions = cleanGlobalCLIOptions(options);\n\n try {\n const buildConfig = await resolveBuildConfig(\n root,\n options.mode || BuildMode[BuildMode.PRODUCTION],\n buildOptions,\n );\n\n await buildExtension(buildConfig);\n } catch (e) {\n const error = e as Error;\n console.error(colors.red(`error during build:\\n${error.stack}`));\n process.exit(1);\n }\n }\n )\n\ncli\n .command('dev [root]', 'build extension in dev mode')\n .option('--outDir <dir>', '[string] output directory (default: \"dist\")')\n .option('--outJs <name>', '[string] output file name for the JS (default: extension.js)')\n .option('--outSef <name>', '[string] output file name for the SEF (default: extension.sef)')\n .action(\n async (\n root: string | undefined,\n options: GlobalCLIOptions,\n ) => {\n filterDuplicateOptions(options);\n\n const buildOptions: BuildCLIOptions = cleanGlobalCLIOptions(options);\n\n try {\n const buildConfig = await resolveBuildConfig(\n root,\n options.mode || BuildMode[BuildMode.DEV],\n buildOptions,\n );\n\n await buildExtension(buildConfig);\n } catch (e) {\n const error = e as Error;\n console.error(colors.red(`error during build:\\n${error.stack}`));\n process.exit(1);\n }\n }\n )\n\ncli.help();\ncli.version(VERSION);\n\ncli.parse();\n"],"mappings":";;;;;;AAMA,MAAM,MAAM,IAAI,aAAa;AAO7B,MAAM,0BAA4C,YAAe;AAC7D,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,EAAE;AAChD,MAAI,CAAC,MAAM,QAAQ,MAAM,CAAE;AAG3B,UAAQ,OAAkB,MAAM,MAAM,SAAS;;;AAIvD,SAAS,sBAAwD,SAAyD;CACtH,MAAM,MAAM,EAAE,GAAG,SAAS;AAC1B,QAAO,IAAI;AACX,QAAO,IAAI;AAEX,QAAO;;AAGX,IAAI,OAAO,qBAAqB,0BAA0B;AAQ1D,IACK,QAAQ,UAAU,kBAAkB,CACpC,OAAO,kBAAkB,gDAA8C,CACvE,OAAO,kBAAkB,+DAA+D,CACxF,OAAO,mBAAmB,iEAAiE,CAC3F,OACG,OACI,MACA,YACC;AACD,wBAAuB,QAAQ;CAE/B,MAAM,eAAgC,sBAAsB,QAAQ;AAEpE,KAAI;AAOA,QAAM,eANc,MAAM,mBACtB,MACA,QAAQ,QAAQ,UAAU,UAAU,aACpC,aACH,CAEgC;UAC5B,GAAG;EACR,MAAM,QAAQ;AACd,UAAQ,MAAM,OAAO,IAAI,wBAAwB,MAAM,QAAQ,CAAC;AAChE,UAAQ,KAAK,EAAE;;EAG1B;AAEL,IACK,QAAQ,cAAc,8BAA8B,CACpD,OAAO,kBAAkB,gDAA8C,CACvE,OAAO,kBAAkB,+DAA+D,CACxF,OAAO,mBAAmB,iEAAiE,CAC3F,OACG,OACI,MACA,YACC;AACD,wBAAuB,QAAQ;CAE/B,MAAM,eAAgC,sBAAsB,QAAQ;AAEpE,KAAI;AAOA,QAAM,eANc,MAAM,mBACtB,MACA,QAAQ,QAAQ,UAAU,UAAU,MACpC,aACH,CAEgC;UAC5B,GAAG;EACR,MAAM,QAAQ;AACd,UAAQ,MAAM,OAAO,IAAI,wBAAwB,MAAM,QAAQ,CAAC;AAChE,UAAQ,KAAK,EAAE;;EAG1B;AAEL,IAAI,MAAM;AACV,IAAI,QAAQA,QAAQ;AAEpB,IAAI,OAAO"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":["path","VERSION","res","VERSION"],"sources":["../../package.json","../../src/node/constants.ts","../../src/node/utils.ts","../../src/node/build.ts","../../src/node/config.ts","../../src/node/cli.ts"],"sourcesContent":["","import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport colors from 'picocolors'\n\nexport { version as VERSION } from '../../package.json';\n\nexport const DEFAULT_CONFIG_EXTENSIONS: string[] = [\n '.mjs',\n '.js',\n '.mts',\n '.ts',\n]\n\nexport const GLOBAL_NAME = \"SAMMIExtensions\";\n\nexport const BUILD_PREFIX = colors.blue(\"[sammi-next]\");\nexport const GREEN_CHECK = colors.green('✔');\nexport const RED_X = colors.red('✗');\n\nfunction findPackageDir() {\n let initPath = fileURLToPath(import.meta.url);\n while (!initPath.endsWith('sammi-next')) {\n initPath = path.resolve(initPath, '..');\n }\n return initPath;\n}\nexport const SAMMI_NEXT_PACKAGE_DIR = findPackageDir();\n","export function displayTime(time: number): string {\n if (time < 1_000)\n return `${time}ms`;\n\n time = time / 1_000;\n\n if (time < 60)\n return `${time.toFixed(2)}s`\n\n const mins = Math.floor(time / 60);\n const seconds = Math.round(time % 60);\n\n if (seconds === 60)\n return `${mins + 1}m`\n\n return `${mins}m${seconds < 1 ? '' : ` ${seconds}s`}`;\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport colors from 'picocolors';\nimport chokidar from 'chokidar';\nimport { ResolvedExtensionConfig } from \"@shared/config-types\";\nimport { build, TsdownBundle } from \"tsdown\";\nimport { BUILD_PREFIX, GLOBAL_NAME, GREEN_CHECK, RED_X, SAMMI_NEXT_PACKAGE_DIR, VERSION } from \"./constants\";\nimport { displayTime } from './utils';\n// import nodePolyfills from '@rolldown/plugin-node-polyfills';\n\nexport enum BuildMode {\n DEV,\n PRODUCTION,\n}\n\nexport const BuildModes = Object.keys(BuildMode).filter(key => isNaN(Number(key)));\n\nexport interface BuildOptions {\n config: ResolvedExtensionConfig;\n rootDir: string;\n mode?: BuildMode;\n}\n\nexport type ResolvedBuildOptions = Required<BuildOptions>;\n\nfunction readOptionalFile(path: string): string | undefined {\n if (!fs.existsSync(path)) return;\n\n return fs.readFileSync(path, 'utf-8');\n}\n\nconst CommandRE = /\\w+\\(\\w+,\\s*{\\s*default:/gm;\n\nfunction generateSEF(options: ResolvedBuildOptions): string {\n const { config, rootDir, mode } = options;\n const content = [];\n\n const required_files: {\n header: string,\n content: string,\n }[] = [\n {\n header: \"extension_name\",\n content: config.name,\n },\n {\n header: \"extension_info\",\n content: config.info,\n },\n {\n header: \"extension_version\",\n content: config.version,\n },\n ]\n\n for (const field of required_files) {\n content.push(`[${field.header}]`, field.content, \"\");\n }\n\n const external = readOptionalFile(config.external);\n content.push(\"[insert_external]\", external ? `<div id=\"${config.id}-external\">${external}</div>` : \"\", \"\");\n\n const js_script = fs.readFileSync(path.join(rootDir, config.out.dir, config.out.js), \"utf-8\");\n content.push(\"[insert_command]\", CommandRE.test(js_script) ? `${GLOBAL_NAME}.${config.id}.default()` : \"\", \"\");\n content.push(\"[insert_hook]\", \"\", \"\") // TODO: maybe add hook retro-compatibility\n content.push(\"[insert_script]\", js_script, \"\");\n\n let over = readOptionalFile(config.over);\n if (over && mode === BuildMode.PRODUCTION) {\n over = JSON.stringify(JSON.parse(over));\n }\n content.push(\"[insert_over]\", over && over != \"{}\" ? over : \"\", \"\");\n return content.join(\"\\n\");\n}\n\nfunction generatePreview(options: ResolvedBuildOptions): string {\n const { config, rootDir } = options;\n\n const external = readOptionalFile(config.external);\n const js_script = fs.readFileSync(path.join(rootDir, config.out.dir, config.out.js), \"utf-8\");\n return fs\n .readFileSync(path.join(SAMMI_NEXT_PACKAGE_DIR, \".sammi\", \"preview.blueprint.html\"), \"utf-8\")\n .replace(/{{EXTERNAL}}/g, external ? `<div id=\"${config.id}-external\">${external}</div>` : \"\")\n .replace(/{{SCRIPT}}/g, js_script);\n}\n\nasync function buildOnce(options: ResolvedBuildOptions) {\n const { config, rootDir, mode } = options;\n\n const startTime = Date.now();\n const bundle = await build({\n entry: [config.entry],\n outDir: path.join(rootDir, config.out.dir),\n platform: 'browser',\n format: 'iife',\n target: ['es2022'],\n sourcemap: false,\n minify: mode === BuildMode.PRODUCTION,\n banner: {\n js: `/* ${config.name} v${config.version} - Built with SAMMI Next v${VERSION} */`,\n },\n noExternal: ['**'],\n outputOptions: {\n entryFileNames: config.out.js,\n extend: true,\n name: `${GLOBAL_NAME}.${config.id}`,\n exports: 'named',\n },\n // plugins: [\n // nodePolyfills(),\n // ],\n });\n const tsdownTime = Date.now();\n console.info(GREEN_CHECK, BUILD_PREFIX, `built ${config.out.js} in ${displayTime(tsdownTime - startTime)}`);\n\n fs.writeFileSync(path.join(rootDir, config.out.dir, config.out.sef), generateSEF(options), 'utf-8');\n const sefTime = Date.now();\n console.info(GREEN_CHECK, BUILD_PREFIX, `built ${config.out.sef} in ${displayTime(sefTime - tsdownTime)}`);\n\n fs.writeFileSync(path.join(rootDir, config.out.dir, \"preview.html\"), generatePreview(options), 'utf-8');\n const previewTime = Date.now();\n console.info(GREEN_CHECK, BUILD_PREFIX, `built preview.html in ${displayTime(previewTime - sefTime)}`);\n return { bundle, startTime };\n}\n\nexport async function buildExtension(options: ResolvedBuildOptions) {\n const { config, mode } = options;\n\n console.info(\n colors.cyan(\n `SAMMI Next v${VERSION} ${colors.green(\n `building \"${config.name}\" extension in ${BuildMode[mode].toLowerCase()} mode...`\n )}`\n ),\n );\n\n let bundle: TsdownBundle[] | undefined;\n let startTime: number | undefined;\n\n try {\n const res = await buildOnce(options);\n bundle = res.bundle;\n startTime = res.startTime;\n\n if (options.mode !== BuildMode.DEV) return bundle;\n\n console.info(BUILD_PREFIX, colors.cyan(\"watching for file changes...\"));\n\n const watchPaths = [\n path.dirname(config.entry),\n config.external,\n config.over,\n ].filter(Boolean);\n\n const watcher = chokidar.watch(watchPaths, { ignoreInitial: true });\n let timer: NodeJS.Timeout | null = null;\n\n watcher.on('all', (event, p) => {\n console.info(colors.cyan(`${event}: ${p}`));\n if (timer)\n clearTimeout(timer);\n\n timer = setTimeout(() => {\n buildOnce(options).then(res => {\n bundle = res.bundle;\n startTime = res.startTime;\n }).catch(e => console.error(e));\n }, 100);\n });\n\n process.on('SIGINT', () => {\n console.info(\"\\nStopping watch mode...\");\n watcher\n .close()\n .then(() => process.exit(0))\n .catch(e => {\n console.error(e);\n process.exit(1);\n });\n });\n return watcher;\n } catch (error) {\n if (startTime) {\n console.error(RED_X, BUILD_PREFIX, `Build failed in ${displayTime(Date.now() - startTime)}`);\n startTime = undefined;\n }\n throw error;\n }\n}\n","\n\nimport { ExtensionConfig, ResolvedExtensionConfig } from \"@shared/config-types\";\nimport Ajv, { JSONSchemaType } from \"ajv\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { DEFAULT_CONFIG_EXTENSIONS } from \"./constants\";\nimport { BuildCLIOptions } from \"./cli\";\nimport { BuildModes, ResolvedBuildOptions } from \"./build\";\n\nconst ajv = new Ajv();\n\najv.addKeyword({\n keyword: \"fileExists\",\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n validate: (_schema: any, data: any) => {\n if (!data) return true;\n\n return fs.existsSync(data as string);\n },\n errors: false\n});\n\nconst schema: JSONSchemaType<ExtensionConfig> = {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n minLength: 1,\n pattern: \"^[a-zA-Z0-9-_]+$\",\n },\n name: {\n type: \"string\",\n minLength: 1,\n pattern: \"^[a-zA-Z0-9 -_]+$\",\n },\n info: {\n type: \"string\",\n default: \"\",\n nullable: true,\n },\n version: {\n type: \"string\",\n minLength: 1,\n pattern: \"^\\\\d+(?:\\\\.\\\\d+)*(?:-.*)?$\",\n },\n entry: {\n type: \"string\",\n minLength: 1,\n fileExists: true,\n },\n external: {\n type: \"string\",\n minLength: 1,\n fileExists: true,\n nullable: true,\n },\n over: {\n type: \"string\",\n minLength: 1,\n fileExists: true,\n nullable: true,\n },\n out: {\n type: \"object\",\n properties: {\n dir: {\n type: \"string\",\n minLength: 1,\n pattern: \"^[^<>:\\\"|?*]+$\",\n default: \"dist\",\n nullable: true,\n },\n js: {\n type: \"string\",\n minLength: 4,\n pattern: \"^[\\\\w\\\\-. ]+\\\\.js$\",\n default: \"extension.js\",\n nullable: true,\n },\n sef: {\n type: \"string\",\n minLength: 5,\n pattern: \"^[\\\\w\\\\-. ]+\\\\.sef$\",\n default: \"extension.sef\",\n nullable: true,\n }\n },\n required: [],\n nullable: true,\n additionalProperties: false,\n }\n },\n required: [\"name\", \"id\", \"version\", \"entry\"],\n additionalProperties: false,\n};\n\nconst configValidator = ajv.compile(schema);\n\nexport async function loadConfig(rootDir: string) {\n for (const ext of DEFAULT_CONFIG_EXTENSIONS) {\n const configPath = path.join(rootDir, `sammi.config${ext}`);\n\n if (!fs.existsSync(configPath)) continue;\n\n try {\n const { createJiti } = await import('jiti');\n const jiti = createJiti(rootDir, {\n interopDefault: true,\n moduleCache: true,\n });\n\n const config = await jiti.import(configPath, { default: true });\n\n return validateExtensionConfig(config, configPath);\n } catch (error) {\n throw new Error(`Error loading ${configPath}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n const jsonPath = path.join(rootDir, 'sammi.config.json');\n if (fs.existsSync(jsonPath)) {\n try {\n const raw = fs.readFileSync(jsonPath, 'utf-8');\n const config = JSON.parse(raw) as unknown;\n return validateExtensionConfig(config, jsonPath);\n } catch (error) {\n throw new Error(`Error loading ${jsonPath}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n throw new Error('SAMMI Next extension config file not found in the root dir.');\n}\n\n\nfunction validateExtensionConfig(config: unknown, configPath: string): ExtensionConfig {\n if (!configValidator(config)) {\n const errors = configValidator.errors?.map(err => ` - ${err.instancePath} ${err.message}`).join('\\n');\n throw new Error(`Invalid config from ${configPath}:\\n${errors}`);\n }\n\n return config;\n}\n\nexport function resolveExtensionConfig(config: ExtensionConfig, rootDir: string): ResolvedExtensionConfig {\n const resolved: ResolvedExtensionConfig = {\n id: config.id,\n name: config.name,\n version: config.version,\n info: config.info || '',\n entry: path.resolve(rootDir, config.entry),\n external: config.external ? path.resolve(rootDir, config.external) : '',\n over: config.over ? path.resolve(rootDir, config.over) : '',\n out: {\n dir: config.out?.dir || 'dist',\n js: config.out?.js || 'extension.js',\n sef: config.out?.sef || 'extension.sef',\n },\n };\n\n if (!fs.existsSync(resolved.entry))\n throw new Error(`Entry file not found: ${resolved.entry}`);\n\n if (resolved.external && !fs.existsSync(resolved.external))\n throw new Error(`External file not found: ${resolved.external}`);\n\n if (resolved.over && !fs.existsSync(resolved.over))\n throw new Error(`Over file not found: ${resolved.over}`);\n\n return resolved;\n}\n\nexport async function resolveBuildConfig(\n root: string | undefined,\n command: string,\n build_cli: BuildCLIOptions,\n) {\n const mode = BuildModes.findIndex(m => {\n return m.toLowerCase() === command.toLowerCase();\n });\n if (mode < 0)\n throw new Error(`Invalid mode: ${command}. It must be one of: ${BuildModes.join(', ')}`);\n\n const rootDir = root ?? process.cwd();\n const config = await loadConfig(rootDir);\n\n config.out ??= {};\n\n config.out.dir = build_cli.outDir ?? config.out.dir;\n config.out.js = build_cli.outJs ?? config.out.js;\n config.out.sef = build_cli.outSef ?? config.out.sef;\n\n const resolvedConfig = resolveExtensionConfig(config, rootDir);\n\n const resolved: ResolvedBuildOptions = {\n rootDir,\n mode,\n config: resolvedConfig\n }\n return resolved;\n}\n","import cac from \"cac\";\nimport { buildExtension, BuildMode } from \"./build\";\nimport colors from 'picocolors';\nimport { resolveBuildConfig } from \"./config\";\nimport { VERSION } from \"./constants\";\n\nconst cli = cac('sammi-next');\n\nexport interface GlobalCLIOptions {\n m?: string\n mode?: string\n}\n\nconst filterDuplicateOptions = <T extends object>(options: T) => {\n for (const [key, value] of Object.entries(options)) {\n if (!Array.isArray(value)) continue;\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n options[key as keyof T] = value[value.length - 1];\n }\n};\n\nfunction cleanGlobalCLIOptions<Options extends GlobalCLIOptions>(options: Options): Omit<Options, keyof GlobalCLIOptions> {\n const ret = { ...options };\n delete ret.m\n delete ret.mode\n\n return ret;\n}\n\ncli.option('-m, --mode <mode>', '[string] set build mode');\n\nexport interface BuildCLIOptions {\n outDir?: string\n outJs?: string\n outSef?: string\n}\n\ncli\n .command('[root]', 'build extension')\n .option('--outDir <dir>', '[string] output directory (default: \"dist\")')\n .option('--outJs <name>', '[string] output file name for the JS (default: extension.js)')\n .option('--outSef <name>', '[string] output file name for the SEF (default: extension.sef)')\n .action(\n async (\n root: string | undefined,\n options: GlobalCLIOptions,\n ) => {\n filterDuplicateOptions(options);\n\n const buildOptions: BuildCLIOptions = cleanGlobalCLIOptions(options);\n\n try {\n const buildConfig = await resolveBuildConfig(\n root,\n options.mode || BuildMode[BuildMode.PRODUCTION],\n buildOptions,\n );\n\n await buildExtension(buildConfig);\n } catch (e) {\n const error = e as Error;\n console.error(colors.red(`error during build:\\n${error.stack}`));\n process.exit(1);\n }\n }\n )\n\ncli\n .command('dev [root]', 'build extension in dev mode')\n .option('--outDir <dir>', '[string] output directory (default: \"dist\")')\n .option('--outJs <name>', '[string] output file name for the JS (default: extension.js)')\n .option('--outSef <name>', '[string] output file name for the SEF (default: extension.sef)')\n .action(\n async (\n root: string | undefined,\n options: GlobalCLIOptions,\n ) => {\n filterDuplicateOptions(options);\n\n const buildOptions: BuildCLIOptions = cleanGlobalCLIOptions(options);\n\n try {\n const buildConfig = await resolveBuildConfig(\n root,\n options.mode || BuildMode[BuildMode.DEV],\n buildOptions,\n );\n\n await buildExtension(buildConfig);\n } catch (e) {\n const error = e as Error;\n console.error(colors.red(`error during build:\\n${error.stack}`));\n process.exit(1);\n }\n }\n )\n\ncli.help();\ncli.version(VERSION);\n\ncli.parse();\n"],"mappings":"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"}
|
|
@@ -1,6 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
import { ExtensionConfig as ExtensionConfig$1 } from "../shared/config-types.
|
|
1
|
+
/// <reference types="sammi-bridge-types" />
|
|
2
|
+
import { ExtensionConfig as ExtensionConfig$1 } from "../shared/config-types.js";
|
|
3
3
|
|
|
4
|
+
//#region src/runtime/types.d.ts
|
|
5
|
+
declare global {
|
|
6
|
+
var SAMMIExtensions: Record<string, SAMMINextExtension | undefined>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Represents useful data from the sammi.config.js.
|
|
10
|
+
*/
|
|
11
|
+
interface ExtensionConfig {
|
|
12
|
+
/** Name of the extension. It is visible in SAMMI Bridge and SAMMI Core. */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Unique id of the extension. */
|
|
15
|
+
id: string;
|
|
16
|
+
/** Descriptive text about the extension, e.g. what the extension does. Is displayed to the users in SAMMI Bridge-Extensions tab when they hover over the extension name inside the list of installed extensions. */
|
|
17
|
+
info?: string;
|
|
18
|
+
/** Extension version, using numbers and dots (e.g., 2.01). Used by the automatic version checker in Bridge, which can notify users of updates. */
|
|
19
|
+
version: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Interface that represents most of the structures built with SAMMI Next.
|
|
23
|
+
*/
|
|
24
|
+
interface SAMMINextExtension {
|
|
25
|
+
readonly default?: () => void;
|
|
26
|
+
readonly _config?: ExtensionConfig;
|
|
27
|
+
readonly [key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
4
30
|
//#region src/runtime/index.d.ts
|
|
5
31
|
interface initExtensionOptions {
|
|
6
32
|
/**
|
|
@@ -26,7 +52,12 @@ interface initExtensionOptions {
|
|
|
26
52
|
* @param options Whether to skip the process of proxying your extension (allowing other scripts to modify your exports).
|
|
27
53
|
* @returns Parsed and cleaned config.
|
|
28
54
|
*/
|
|
29
|
-
declare function initExtension(config: Record<string, any>, options
|
|
55
|
+
declare function initExtension(config: Record<string, any>, options: Omit<initExtensionOptions, 'skipProxying'> & {
|
|
56
|
+
skipProxying: true;
|
|
57
|
+
}): ExtensionConfig;
|
|
58
|
+
declare function initExtension(config: Record<string, any>, options?: Omit<initExtensionOptions, 'skipProxying'> & {
|
|
59
|
+
skipProxying: false;
|
|
60
|
+
}): Readonly<ExtensionConfig>;
|
|
30
61
|
/**
|
|
31
62
|
* Retrieves an [insert_external] section by its extension id.
|
|
32
63
|
*
|
|
@@ -98,4 +129,4 @@ interface insertCommandSectionOptions {
|
|
|
98
129
|
declare function insertCommandSection(callback: () => void, options?: insertCommandSectionOptions): () => void;
|
|
99
130
|
//#endregion
|
|
100
131
|
export { type ExtensionConfig, type ExtensionConfig$1 as FullExtensionConfig, type SAMMINextExtension, getExternalSection, initExtension, insertCommandSection };
|
|
101
|
-
//# sourceMappingURL=index.d.
|
|
132
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/runtime/types.ts","../../src/runtime/index.ts"],"mappings":";;;;QAAQ,MAAA;EAAA,IACA,eAAA,EAAiB,MAAA,SAAe,kBAAA;AAAA;;;;UAMvB,eAAA;EAPT;EASJ,IAAA;EARoC;EAUpC,EAAA;EAVmE;EAYnE,IAAA;EAZmE;EAcnE,OAAA;AAAA;;;;UAMa,kBAAA;EAAA,SACJ,OAAA;EAAA,SACA,OAAA,GAAU,eAAA;EAAA,UACT,GAAA;AAAA;;;UClBJ,oBAAA;;;;;;;;EAQN,YAAA;AAAA;;;;ADPJ;;;;;;;;;;AAcA;iBCYgB,aAAA,CAEZ,MAAA,EAAQ,MAAA,eACR,OAAA,EAAS,IAAA,CAAK,oBAAA;EACV,YAAA;AAAA,IAEL,eAAA;AAAA,iBACa,aAAA,CAEZ,MAAA,EAAQ,MAAA,eACR,OAAA,GAAU,IAAA,CAAK,oBAAA;EACX,YAAA;AAAA,IAEL,QAAA,CAAS,eAAA;;;;;;;AA7CuD;;;;;AAgCnE;;iBAyEgB,kBAAA,CAAmB,YAAA,WAAuB,cAAA;AAAA,UAIhD,2BAAA;EA1EQ;;;;;;;EAkFd,kBAAA;EAlFc;;;;;;EAyFd,sBAAA;AAAA;;;;;;;;;;;;;;;;;;;AAnBJ;;;;;AAEC;;;;;AA4DD;;;;;;;;;iBAAgB,oBAAA,CACZ,QAAA,cACA,OAAA,GAAU,2BAAA"}
|
|
@@ -1,23 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/// <reference types="sammi-bridge-types" />
|
|
3
2
|
//#region src/runtime/index.ts
|
|
3
|
+
window.SAMMIExtensions ??= {};
|
|
4
4
|
const PROXY_PREFIX = "[SAMMI-NEXT-PROXY]";
|
|
5
5
|
const defaultInitExtensionOptions = { skipProxying: false };
|
|
6
|
-
|
|
7
|
-
* Removes unnecessary data from a raw extension config object and parses it into an ExtensionConfig.
|
|
8
|
-
*
|
|
9
|
-
* Exports the config through the `SAMMIExtensions` namespace with the `_config` key.
|
|
10
|
-
*
|
|
11
|
-
* Generates a Proxy of the extension object to avoid other scripts trying to overwrite it.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* const extension_config = SAMMIExtensions['extension-id']._config;
|
|
15
|
-
*
|
|
16
|
-
* @param config Unparsed config object.
|
|
17
|
-
* @param options Whether to skip the process of proxying your extension (allowing other scripts to modify your exports).
|
|
18
|
-
* @returns Parsed and cleaned config.
|
|
19
|
-
*/
|
|
20
|
-
function initExtension(config, options = { skipProxying: false }) {
|
|
6
|
+
function initExtension(config, options) {
|
|
21
7
|
let response = {
|
|
22
8
|
id: config.id,
|
|
23
9
|
name: config.name,
|
|
@@ -108,10 +94,7 @@ const defaultInsertCommandSectionOptions = {
|
|
|
108
94
|
* @param options The initialization options.
|
|
109
95
|
* @returns wrapped export default callback
|
|
110
96
|
*/
|
|
111
|
-
function insertCommandSection(callback, options
|
|
112
|
-
waitForSammiclient: true,
|
|
113
|
-
waitForSAMMIExtensions: false
|
|
114
|
-
}) {
|
|
97
|
+
function insertCommandSection(callback, options) {
|
|
115
98
|
const settings = Object.assign(defaultInsertCommandSectionOptions, options);
|
|
116
99
|
const wrapper = () => {
|
|
117
100
|
if (!sammiclient && settings.waitForSammiclient) {
|
|
@@ -129,4 +112,4 @@ function insertCommandSection(callback, options = {
|
|
|
129
112
|
|
|
130
113
|
//#endregion
|
|
131
114
|
export { getExternalSection, initExtension, insertCommandSection };
|
|
132
|
-
//# sourceMappingURL=index.
|
|
115
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/runtime/index.ts"],"sourcesContent":["/// <reference types=\"sammi-bridge-types\" />\nimport type { ExtensionConfig, SAMMINextExtension } from \"./types\";\n\nwindow.SAMMIExtensions ??= {};\nconst PROXY_PREFIX = \"[SAMMI-NEXT-PROXY]\";\n\ninterface initExtensionOptions {\n /**\n * Whether to skip the process of proxying your extension.\n *\n * Warning: this allows other scripts to modify your exports.\n *\n * @default false\n */\n skipProxying?: boolean\n}\nconst defaultInitExtensionOptions: initExtensionOptions = {\n skipProxying: false,\n}\n/**\n * Removes unnecessary data from a raw extension config object and parses it into an ExtensionConfig.\n *\n * Exports the config through the `SAMMIExtensions` namespace with the `_config` key.\n *\n * Generates a Proxy of the extension object to avoid other scripts trying to overwrite it.\n *\n * @example\n * const extension_config = SAMMIExtensions['extension-id']._config;\n *\n * @param config Unparsed config object.\n * @param options Whether to skip the process of proxying your extension (allowing other scripts to modify your exports).\n * @returns Parsed and cleaned config.\n */\nexport function initExtension(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n config: Record<string, any>,\n options: Omit<initExtensionOptions, 'skipProxying'> & {\n skipProxying: true\n },\n): ExtensionConfig\nexport function initExtension(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n config: Record<string, any>,\n options?: Omit<initExtensionOptions, 'skipProxying'> & {\n skipProxying: false\n },\n): Readonly<ExtensionConfig>\nexport function initExtension(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n config: Record<string, any>,\n options?: initExtensionOptions\n): Readonly<ExtensionConfig> | ExtensionConfig {\n let response: ExtensionConfig = {\n id: config.id as string,\n name: config.name as string,\n version: config.version as string,\n info: config.info as string | undefined,\n };\n\n window.SAMMIExtensions = window.SAMMIExtensions || {};\n SAMMIExtensions[response.id] = SAMMIExtensions[response.id] || {};\n if (SAMMIExtensions[response.id]?._config)\n throw new Error('The extension with the provided id was already initialized');\n\n const settings = Object.assign(defaultInitExtensionOptions, options);\n if (!settings.skipProxying)\n response = Object.freeze(response);\n\n // @ts-expect-error This is before proxying the extension, so no error.\n SAMMIExtensions[response.id]!._config ??= response;\n if (settings.skipProxying) return response;\n\n SAMMIExtensions[response.id] = new Proxy(SAMMIExtensions[response.id]!, {\n set(target, prop) {\n console.error(PROXY_PREFIX, \": Blocked setting value of:\", prop, \"on:\", target._config?.id);\n return true;\n },\n defineProperty(target, property) {\n console.error(PROXY_PREFIX, \": Blocked property redefinition of:\", property, \"on:\", target._config?.id);\n return true;\n },\n deleteProperty(target, p) {\n console.error(PROXY_PREFIX, \": Blocked deletion of:\", p, \"on:\", target._config?.id);\n return true;\n },\n setPrototypeOf(target) {\n console.error(PROXY_PREFIX, \": Blocked changing prototype of:\", target._config?.id);\n return true;\n },\n });\n return response;\n}\n\n/**\n * Retrieves an [insert_external] section by its extension id.\n *\n * ### Original documentation from https://sammi.solutions/extensions/build\n * This section appears inside the extension’s tab in Bridge, and it provides a visual interface for the extension if needed. It’s written in HTML and CSS.\n *\n * @example\n * const EXTERNAL = getExternalSection(config.id);\n * EXTERNAL.querySelector(\"button\")?.addEventListener('click', () => console.log('Hello world!'));\n *\n * @param extension_id The id of the SAMMI Next extension to retrieve its [insert_external] section.\n * @returns the div element that wraps your extension.\n */\nexport function getExternalSection(extension_id: string): HTMLDivElement {\n return document.getElementById(`${extension_id}-external`) as HTMLDivElement;\n}\n\ninterface insertCommandSectionOptions {\n /**\n * Whether to wait for `sammiclient` to exist.\n * This is enabled by default because every extension that uses `SAMMI` depends on `sammiclient`.\n * You can disable it if your [insert_command] section doesn't use neither `SAMMI` nor `sammiclient`.\n *\n * @default true\n */\n waitForSammiclient?: boolean;\n /**\n * Whether to wait for `SAMMIExtensions` to exist.\n * You must set this to `true` if you use the `SAMMIExtensions` namespace inside your [insert_command] section.\n *\n * @default false\n */\n waitForSAMMIExtensions?: boolean;\n}\nconst defaultInsertCommandSectionOptions: insertCommandSectionOptions = {\n waitForSammiclient: true,\n waitForSAMMIExtensions: false,\n}\n/**\n * Generates your [insert_command] section.\n * This should be used in the default export of your extension.\n *\n * By default, it wraps your callback to wait for `sammiclient` and `SAMMI` are initialized.\n * Its behavior can be changed in the options param.\n *\n * ### Original documentation from https://sammi.solutions/extensions/build\n * In this section, you can define Extension Commands.\n * These commands will be available to users in SAMMI Core when they install your extension.\n * You can define multiple commands in this section.\n * Refer to the SAMMI Bridge documentation for Extension Command details.\n * In this section, you can also automatically call your main extension function that should run as soon as SAMMI connects to Bridge.\n *\n * @example\n * export default insertCommandSection(() => {\n * SAMMI.extCommand(\"Extension Sample Command\", 3355443, 52).catch(e => console.error(e));\n * sammiclient.on(\"Extension Sample Command\", () => {\n * const handler = async () => {\n * await SAMMI.notification('Command Sample');\n * };\n * handler().catch((e) => console.error(e));\n * });\n * });\n * export default insertCommandSection(\n * () => console.log(\"Hello world\"),\n * { waitForSammiclient: false }\n * );\n * export default insertCommandSection(\n * () => console.log(SAMMIExtensions[\"extension-id\"]),\n * { waitForSAMMIExtensions: true }\n * );\n *\n * @param callback A callback with the logic that you will insert in your [insert_command] section.\n * @param options The initialization options.\n * @returns wrapped export default callback\n */\nexport function insertCommandSection(\n callback: () => void,\n options?: insertCommandSectionOptions,\n): () => void {\n const settings = Object.assign(defaultInsertCommandSectionOptions, options);\n\n const wrapper = () => {\n if (!sammiclient && settings.waitForSammiclient) {\n setTimeout(wrapper, 0);\n return;\n }\n\n if (!SAMMIExtensions && settings.waitForSAMMIExtensions) {\n setTimeout(wrapper, 0);\n return;\n }\n\n callback();\n };\n\n return wrapper;\n}\n\nexport type { ExtensionConfig as FullExtensionConfig } from '@shared/config-types';\nexport type { ExtensionConfig, SAMMINextExtension };\n"],"mappings":";;AAGA,OAAO,oBAAoB,EAAE;AAC7B,MAAM,eAAe;AAYrB,MAAM,8BAAoD,EACtD,cAAc,OACjB;AA6BD,SAAgB,cAEZ,QACA,SAC2C;CAC3C,IAAI,WAA4B;EAC5B,IAAI,OAAO;EACX,MAAM,OAAO;EACb,SAAS,OAAO;EAChB,MAAM,OAAO;EAChB;AAED,QAAO,kBAAkB,OAAO,mBAAmB,EAAE;AACrD,iBAAgB,SAAS,MAAM,gBAAgB,SAAS,OAAO,EAAE;AACjE,KAAI,gBAAgB,SAAS,KAAK,QAC9B,OAAM,IAAI,MAAM,6DAA6D;CAEjF,MAAM,WAAW,OAAO,OAAO,6BAA6B,QAAQ;AACpE,KAAI,CAAC,SAAS,aACV,YAAW,OAAO,OAAO,SAAS;AAGtC,iBAAgB,SAAS,IAAK,YAAY;AAC1C,KAAI,SAAS,aAAc,QAAO;AAElC,iBAAgB,SAAS,MAAM,IAAI,MAAM,gBAAgB,SAAS,KAAM;EACpE,IAAI,QAAQ,MAAM;AACd,WAAQ,MAAM,cAAc,+BAA+B,MAAM,OAAO,OAAO,SAAS,GAAG;AAC3F,UAAO;;EAEX,eAAe,QAAQ,UAAU;AAC7B,WAAQ,MAAM,cAAc,uCAAuC,UAAU,OAAO,OAAO,SAAS,GAAG;AACvG,UAAO;;EAEX,eAAe,QAAQ,GAAG;AACtB,WAAQ,MAAM,cAAc,0BAA0B,GAAG,OAAO,OAAO,SAAS,GAAG;AACnF,UAAO;;EAEX,eAAe,QAAQ;AACnB,WAAQ,MAAM,cAAc,oCAAoC,OAAO,SAAS,GAAG;AACnF,UAAO;;EAEd,CAAC;AACF,QAAO;;;;;;;;;;;;;;;AAgBX,SAAgB,mBAAmB,cAAsC;AACrE,QAAO,SAAS,eAAe,GAAG,aAAa,WAAW;;AAoB9D,MAAM,qCAAkE;CACpE,oBAAoB;CACpB,wBAAwB;CAC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCD,SAAgB,qBACZ,UACA,SACU;CACV,MAAM,WAAW,OAAO,OAAO,oCAAoC,QAAQ;CAE3E,MAAM,gBAAgB;AAClB,MAAI,CAAC,eAAe,SAAS,oBAAoB;AAC7C,cAAW,SAAS,EAAE;AACtB;;AAGJ,MAAI,CAAC,mBAAmB,SAAS,wBAAwB;AACrD,cAAW,SAAS,EAAE;AACtB;;AAGJ,YAAU;;AAGd,QAAO"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference types="sammi-bridge-types" />
|
|
1
2
|
//#region src/shared/config-types.d.ts
|
|
2
3
|
/**
|
|
3
4
|
* Represents the full extension config from sammi.config.ts.
|
|
@@ -80,7 +81,7 @@ interface ExtensionConfig {
|
|
|
80
81
|
*
|
|
81
82
|
* @example
|
|
82
83
|
* ```ts
|
|
83
|
-
* // sammi.config.
|
|
84
|
+
* // sammi.config.ts
|
|
84
85
|
* import { defineConfig } from 'sammi-next/config';
|
|
85
86
|
*
|
|
86
87
|
* export default defineConfig({
|
|
@@ -102,4 +103,4 @@ interface ResolvedExtensionConfig extends Required<Omit<ExtensionConfig, 'out'>>
|
|
|
102
103
|
}
|
|
103
104
|
//#endregion
|
|
104
105
|
export { ExtensionConfig, ResolvedExtensionConfig, defineConfig };
|
|
105
|
-
//# sourceMappingURL=config-types.d.
|
|
106
|
+
//# sourceMappingURL=config-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-types.d.
|
|
1
|
+
{"version":3,"file":"config-types.d.ts","names":[],"sources":["../../src/shared/config-types.ts"],"mappings":";;;AAKA;;;UAAiB,eAAA;EAKb;;;;EAAA,EAAA;EAyCA;;;;EAnCA,IAAA;EAmEI;;;AAoBR;;;;EA9EI,IAAA;EA8EyB;;;;AAS7B;;;;;EA5EI,OAAA;EA6Ec;;;;EAvEd,KAAA;EAsE6C;;;;;;;EA7D7C,QAAA;EA8DyC;;;;;;;EArDzC,IAAA;;EAGA,GAAA;;;;;;IAMI,GAAA;;;;;;IAOA,EAAA;;;;;;IAOA,GAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;iBAoBQ,YAAA,CAAa,MAAA,EAAQ,eAAA,GAAkB,eAAA;;;;;;UAStC,uBAAA,SAAgC,QAAA,CAAS,IAAA,CAAK,eAAA;EAC3D,GAAA,EAAK,QAAA,CAAS,WAAA,CAAY,eAAA;AAAA"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
/// <reference types="sammi-bridge-types" />
|
|
1
2
|
//#region src/shared/config-types.ts
|
|
2
3
|
/**
|
|
3
4
|
* Helper function to define a SAMMI Next extension config with type safety.
|
|
4
5
|
*
|
|
5
6
|
* @example
|
|
6
7
|
* ```ts
|
|
7
|
-
* // sammi.config.
|
|
8
|
+
* // sammi.config.ts
|
|
8
9
|
* import { defineConfig } from 'sammi-next/config';
|
|
9
10
|
*
|
|
10
11
|
* export default defineConfig({
|
|
@@ -21,4 +22,4 @@ function defineConfig(config) {
|
|
|
21
22
|
|
|
22
23
|
//#endregion
|
|
23
24
|
export { defineConfig };
|
|
24
|
-
//# sourceMappingURL=config-types.
|
|
25
|
+
//# sourceMappingURL=config-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-types.js","names":[],"sources":["../../src/shared/config-types.ts"],"sourcesContent":["\n/**\n * Represents the full extension config from sammi.config.ts.\n * Used by the CLI to build the extension.\n*/\nexport interface ExtensionConfig {\n /**\n * Specify a unique id for your extension here.\n * Please use alphanumeric characters, dashes, and underscores only.\n */\n id: string;\n\n /**\n * This section names your extension, and is visible in SAMMI Bridge and SAMMI Core.\n * Please use alphanumeric characters and spaces only.\n */\n name: string;\n\n /**\n * This section is for descriptive text about the extension, e.g. what the extension does.\n * This information is displayed to the users in SAMMI Bridge-Extensions tab when they hover\n * over the extension name inside the list of installed extensions.\n *\n * @default \"\"\n */\n info?: string;\n\n /**\n * Specify your extension version here, using numbers and dots (e.g., 2.01).\n * This is crucial for the automatic version checker in Bridge, which can notify users of updates.\n *\n * Note: the regex pattern used for validation is `^\\d+(?:\\.\\d+)*(?:-.*)?$`.\n * Although SAMMI Next allows beta/alpha suffixes due to its RegExp,\n * please note that the official SAMMI Bridge automatic version checker ignores them.\n * In other words, the users only will be notified of release updates.\n */\n version: string;\n\n /**\n * Specify your script.ts path here.\n * In your [insert_script] section, you’re encouraged to write your own TypeScript code.\n */\n entry: string;\n\n /**\n * Specify your external.html path here.\n * Your [insert_external] section appears inside the extension’s tab in Bridge,\n * and it provides a visual interface for the extension if needed. It’s written in HTML and CSS.\n *\n * @default undefined\n */\n external?: string;\n\n /**\n * Specify your over.json path here.\n * In your [insert_over] section you simply copy and paste your deck from SAMMI Core you wish to distribute with your extension.\n * When users install your extension, the deck will be automatically added to their SAMMI Core.\n *\n * @default undefined\n */\n over?: string;\n\n /** Configuration related with the extension building. */\n out?: {\n /**\n * The path where the extension will build to.\n *\n * @default \"dist\"\n */\n dir?: string;\n\n /**\n * The file name of the final JavaScript file.\n *\n * @default \"extension.js\"\n */\n js?: string;\n\n /**\n * The file name of the final SAMMI Extension File.\n *\n * @default \"extension.sef\"\n */\n sef?: string;\n }\n}\n\n/**\n * Helper function to define a SAMMI Next extension config with type safety.\n *\n * @example\n * ```ts\n * // sammi.config.ts\n * import { defineConfig } from 'sammi-next/config';\n *\n * export default defineConfig({\n * id: 'my-extension',\n * name: 'My Extension',\n * version: '1.0.0',\n * entry: 'src/script.ts',\n * });\n * ```\n */\nexport function defineConfig(config: ExtensionConfig): ExtensionConfig {\n return config;\n}\n\n/**\n * Resolved extension config with defaults applied.\n * Used internally by the builder.\n * @internal\n */\nexport interface ResolvedExtensionConfig extends Required<Omit<ExtensionConfig, 'out'>> {\n out: Required<NonNullable<ExtensionConfig['out']>>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAuGA,SAAgB,aAAa,QAA0C;AACnE,QAAO"}
|