@reliverse/dler 1.7.50 → 1.7.52

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 CHANGED
@@ -17,7 +17,7 @@
17
17
  ### ⚡ developer experience
18
18
 
19
19
  - **performance optimized** for speed with modern build pipelines and caching
20
- - **18 built-in commands** — comprehensive [dler commands](#dler-commands) for every workflow
20
+ - **16 built-in commands** — comprehensive [dler commands](#dler-commands) for every workflow
21
21
  - **path resolution magic** converts typescript aliases to relative imports automatically
22
22
  - **highly configurable** via dedicated configuration files with sensible defaults
23
23
  - **dual interface** — cli for everyday use, sdk for advanced programmatic control
@@ -148,7 +148,7 @@ bun dev # bun src/cli.ts --dev
148
148
 
149
149
  ## dler commands
150
150
 
151
- dler ships with a flexible command system (prev. plugins) and **18 built-in commands** (from [reliverse addons](https://reliverse.org/addons) collection).
151
+ dler ships with a flexible command system (prev. plugins) and **16 built-in commands** (from [reliverse addons](https://reliverse.org/addons) collection).
152
152
 
153
153
  feel free to create your own commands. commands can be implemented as built-in directly in `src/app/<command>/impl/*` and then imported from `src/app/<command>/cmd.ts`; or implemented in your own library and then imported from `src/app/<command>/cmd.ts`.
154
154
 
@@ -156,7 +156,7 @@ if you run just `dler` — it will display a list of commands which you can laun
156
156
 
157
157
  ## **available commands**
158
158
 
159
- [build](#1-build) — [pub](#2-pub) — [agg](#3-agg) — [check](#4-check) — [conv](#5-conv) — [copy](#6-copy) — [init](#7-init) — [inject](#8-inject) — [libs](#9-libs) — [merge](#10-merge) — [migrate](#11-migrate) — [rempts](#12-rempts) — [rename](#13-rename) — [spell](#14-magic) — [split](#15-split) — [pack](#16-pack) — [unpack](#17-unpack)
159
+ [build](#1-build) — [pub](#2-pub) — [agg](#3-agg) — [check](#4-check) — [conv](#5-conv) — [copy](#6-copy) — [init](#7-init) — [inject](#8-inject) — [libs](#9-libs) — [merge](#10-merge) — [migrate](#11-migrate) — [rempts](#12-rempts) — [rename](#13-rename) — [spell](#14-magic) — [split](#15-split) — [pack](#16-pack)
160
160
 
161
161
  ### 1. `build`
162
162
 
@@ -798,7 +798,7 @@ output/
798
798
  └── mod.ts
799
799
  ```
800
800
 
801
- ### 17. `unpack`
801
+ **--unpack**:
802
802
 
803
803
  creates file structure from packed templates. This command is the counterpart to `pack` and is used to extract and restore template files from a packed template package.
804
804
 
@@ -818,16 +818,16 @@ creates file structure from packed templates. This command is the counterpart to
818
818
 
819
819
  ```bash
820
820
  # Basic usage
821
- dler unpack ./dist-templates --output ./my-project
821
+ dler pack ./dist-templates --output ./my-project --unpack
822
822
 
823
823
  # With custom output directory
824
- dler unpack ./dist-templates --output ./custom-location
824
+ dler pack ./dist-templates --output ./custom-location --unpack
825
825
 
826
826
  # Preview changes without applying
827
- dler unpack ./dist-templates --output ./my-project --dry-run
827
+ dler pack ./dist-templates --output ./my-project --dry-run --unpack
828
828
 
829
829
  # Clean up existing template files before unpacking
830
- dler unpack ./dist-templates --output ./my-project --cleanup
830
+ dler pack ./dist-templates --output ./my-project --cleanup --unpack
831
831
  ```
832
832
 
833
833
  **arguments:**
@@ -35,7 +35,7 @@ async function isCommandInPackageJson(command) {
35
35
  const packageName = COMMAND_TO_PACKAGE[command];
36
36
  if (!packageName) return false;
37
37
  return packageName in (packageJson.dependencies || {}) || packageName in (packageJson.devDependencies || {});
38
- } catch (error) {
38
+ } catch (_error) {
39
39
  return false;
40
40
  }
41
41
  }
@@ -1,5 +1,28 @@
1
+ interface FileMetadata {
2
+ updatedAt?: string;
3
+ updatedHash?: string;
4
+ }
5
+ interface TemplatesFileContent {
6
+ content: FileContent;
7
+ type: "text" | "json" | "binary";
8
+ hasError?: boolean;
9
+ error?: string;
10
+ jsonComments?: Record<number, string>;
11
+ binaryHash?: string;
12
+ metadata?: FileMetadata;
13
+ }
14
+ type FileContent = string | Record<string, unknown>;
15
+ /** Escape back-`s, ${ and newlines for safe template literal embedding */
16
+ export declare const escapeTemplateString: (str: string) => string;
17
+ export declare const unescapeTemplateString: (str: string) => string;
18
+ export declare const hashFile: (file: string) => Promise<string>;
19
+ export declare const getFileMetadata: (file: string) => Promise<FileMetadata>;
20
+ /** Recursively walk a directory */
21
+ export declare const walkDir: (dir: string) => Promise<string[]>;
22
+ /** Process a file and return the TemplatesFileContent structure */
23
+ export declare const readFileForTemplate: (absPath: string, relPath: string, binariesOutDir: string) => Promise<TemplatesFileContent>;
1
24
  declare const _default: import("@reliverse/rempts").Command<{
2
- dir: {
25
+ input: {
3
26
  type: "positional";
4
27
  required: true;
5
28
  description: string;
@@ -11,7 +34,7 @@ declare const _default: import("@reliverse/rempts").Command<{
11
34
  };
12
35
  whitelabel: {
13
36
  type: "string";
14
- default: any;
37
+ default: string;
15
38
  description: string;
16
39
  };
17
40
  cdn: {
@@ -28,10 +51,6 @@ declare const _default: import("@reliverse/rempts").Command<{
28
51
  default: true;
29
52
  description: string;
30
53
  };
31
- /**
32
- * - Without --files: All files are checked and updated if they're newer or have different content
33
- * - With --files: Only specified files are checked and updated if they're newer or have different content
34
- */
35
54
  files: {
36
55
  type: "string";
37
56
  description: string;
@@ -40,5 +59,10 @@ declare const _default: import("@reliverse/rempts").Command<{
40
59
  type: "string";
41
60
  description: string;
42
61
  };
62
+ unpack: {
63
+ type: "boolean";
64
+ default: false;
65
+ description: string;
66
+ };
43
67
  }>;
44
68
  export default _default;
@@ -1,73 +1,283 @@
1
1
  import path from "@reliverse/pathkit";
2
2
  import { relinka } from "@reliverse/relinka";
3
- import { defineArgs, defineCommand } from "@reliverse/rempts";
3
+ import { defineCommand, defineArgs } from "@reliverse/rempts";
4
4
  import { createJiti } from "jiti";
5
5
  import { createHash } from "node:crypto";
6
- import { promises as fs } from "node:fs";
7
- import {
8
- WHITELABEL_DEFAULT,
9
- TEMPLATE_VAR,
10
- TPLS_DIR,
11
- BINARIES_DIR
12
- } from "../../libs/sdk/sdk-impl/utils/pack-unpack/pu-constants.js";
13
- import {
14
- escapeTemplateString,
15
- readFileForTemplate,
16
- walkDir
17
- } from "../../libs/sdk/sdk-impl/utils/pack-unpack/pu-file-utils.js";
18
- const jiti = createJiti(import.meta.url);
19
- const hashFile = async (file) => {
20
- const buff = await fs.readFile(file);
21
- return createHash("sha1").update(buff).digest("hex").slice(0, 10);
6
+ import fs from "node:fs/promises";
7
+ import stripJsonComments from "strip-json-comments";
8
+ import { isBinaryExt } from "../../libs/sdk/sdk-impl/utils/binary.js";
9
+ const WHITELABEL_DEFAULT = "DLER";
10
+ const TEMPLATE_VAR = (name, whitelabel) => `${whitelabel}_TPL_${name.toUpperCase()}`;
11
+ const TPLS_DIR = "templates";
12
+ const BINARIES_DIR = "binaries";
13
+ export const escapeTemplateString = (str) => str.replace(/`/g, "\\`").replace(/\\\${/g, "\\u0024{").replace(/\$\{/g, "\\${").replace(/\\\{\{/g, "{{").replace(/\\\}\}/g, "}}").replace(/\r?\n/g, "\\n");
14
+ export const unescapeTemplateString = (str) => str.replace(/\\n/g, "\n").replace(/\\u0024\{/g, "\\${").replace(/\\\\`/g, "`").replace(/\\\\/g, "\\");
15
+ export const hashFile = async (file) => {
16
+ const buf = await fs.readFile(file);
17
+ return createHash("sha1").update(buf).digest("hex").slice(0, 10);
22
18
  };
23
- const getFileMetadata = async (file) => {
24
- const stats = await fs.stat(file);
25
- const hash = await hashFile(file);
26
- return {
27
- updatedAt: stats.mtime.toISOString(),
28
- updatedHash: hash
29
- };
19
+ export const getFileMetadata = async (file) => {
20
+ try {
21
+ const [stats, hash] = await Promise.all([fs.stat(file), hashFile(file)]);
22
+ return {
23
+ updatedAt: stats.mtime.toISOString(),
24
+ updatedHash: hash
25
+ };
26
+ } catch (err) {
27
+ relinka("warn", `Failed to get metadata for ${file}: ${err.message}`);
28
+ return {
29
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
30
+ updatedHash: ""
31
+ };
32
+ }
33
+ };
34
+ export const walkDir = async (dir) => {
35
+ let res = [];
36
+ const entries = await fs.readdir(dir, { withFileTypes: true });
37
+ for (const entry of entries) {
38
+ const full = path.join(dir, entry.name);
39
+ if (entry.isDirectory()) {
40
+ res = res.concat(await walkDir(full));
41
+ } else {
42
+ res.push(full);
43
+ }
44
+ }
45
+ return res;
46
+ };
47
+ export const readFileForTemplate = async (absPath, relPath, binariesOutDir) => {
48
+ const metadata = await getFileMetadata(absPath);
49
+ try {
50
+ if (await isBinaryExt(absPath)) {
51
+ const hash = metadata.updatedHash;
52
+ const ext2 = path.extname(absPath);
53
+ const target = path.join(binariesOutDir, `${hash}${ext2}`);
54
+ try {
55
+ await fs.mkdir(binariesOutDir, { recursive: true });
56
+ await fs.copyFile(absPath, target).catch(async (err) => {
57
+ if (err.code !== "EEXIST") throw err;
58
+ });
59
+ } catch (err) {
60
+ relinka("error", `Failed copying binary ${relPath}: ${err.message}`);
61
+ return {
62
+ content: "",
63
+ type: "binary",
64
+ hasError: true,
65
+ error: err.message,
66
+ binaryHash: hash,
67
+ metadata
68
+ };
69
+ }
70
+ return {
71
+ content: "",
72
+ type: "binary",
73
+ binaryHash: hash,
74
+ metadata
75
+ };
76
+ }
77
+ const raw = await fs.readFile(absPath, "utf8");
78
+ const ext = path.extname(absPath).toLowerCase();
79
+ if (ext === ".json") {
80
+ const comments = {};
81
+ const lines = raw.split(/\r?\n/);
82
+ lines.forEach((line, idx) => {
83
+ const trimmed = line.trim();
84
+ if (trimmed.startsWith("//") || trimmed.startsWith("/*")) comments[idx + 1] = line;
85
+ });
86
+ try {
87
+ const parsed = JSON.parse(stripJsonComments(raw));
88
+ return {
89
+ content: parsed,
90
+ type: "json",
91
+ jsonComments: Object.keys(comments).length ? comments : void 0,
92
+ metadata
93
+ };
94
+ } catch (err) {
95
+ relinka("warn", `Failed to parse JSON file ${relPath}: ${err.message}`);
96
+ return {
97
+ content: {},
98
+ type: "json",
99
+ hasError: true,
100
+ error: err.message,
101
+ jsonComments: Object.keys(comments).length ? comments : void 0,
102
+ metadata
103
+ };
104
+ }
105
+ }
106
+ return {
107
+ content: raw,
108
+ type: "text",
109
+ metadata
110
+ };
111
+ } catch (err) {
112
+ relinka("warn", `Failed to read file ${relPath}: ${err.message}`);
113
+ return {
114
+ content: "",
115
+ type: "text",
116
+ hasError: true,
117
+ error: err.message,
118
+ metadata
119
+ };
120
+ }
121
+ };
122
+ const findTemplatesObject = (mod) => {
123
+ if (mod.DLER_TEMPLATES && typeof mod.DLER_TEMPLATES === "object") {
124
+ return mod.DLER_TEMPLATES;
125
+ }
126
+ if (mod.default && typeof mod.default === "object") {
127
+ return mod.default;
128
+ }
129
+ for (const v of Object.values(mod)) {
130
+ if (v && typeof v === "object" && Object.values(v).every((t) => t && typeof t === "object" && "config" in t)) {
131
+ return v;
132
+ }
133
+ }
134
+ return {};
135
+ };
136
+ const restoreFile = async (outRoot, relPath, meta, binsDir, force = false) => {
137
+ const dest = path.join(outRoot, relPath);
138
+ await fs.mkdir(path.dirname(dest), { recursive: true });
139
+ try {
140
+ if (!force) {
141
+ await fs.access(dest);
142
+ relinka("log", `Skipping existing file (use --force to overwrite): ${relPath}`);
143
+ return;
144
+ }
145
+ } catch {
146
+ }
147
+ if (meta.type === "binary") {
148
+ const ext = path.extname(relPath);
149
+ const src = path.join(binsDir, `${meta.binaryHash}${ext}`);
150
+ try {
151
+ await fs.copyFile(src, dest);
152
+ } catch (err) {
153
+ relinka("error", `Missing binary ${src} for ${relPath}: ${err.message}`);
154
+ }
155
+ } else if (meta.type === "json") {
156
+ const jsonStr = JSON.stringify(meta.content, null, 2);
157
+ await fs.writeFile(dest, jsonStr, "utf8");
158
+ } else {
159
+ await fs.writeFile(dest, meta.content, "utf8");
160
+ }
161
+ if (meta.metadata?.updatedAt) {
162
+ const ts = new Date(meta.metadata.updatedAt);
163
+ await fs.utimes(dest, ts, ts);
164
+ }
165
+ };
166
+ const unpackTemplates = async (aggregatorPath, outDir, force = false) => {
167
+ const jiti = createJiti(process.cwd());
168
+ let mod;
169
+ try {
170
+ mod = await jiti.import(aggregatorPath);
171
+ } catch (err) {
172
+ throw new Error(`Failed to import aggregator file: ${err.message}`);
173
+ }
174
+ const templatesObj = findTemplatesObject(mod);
175
+ if (!Object.keys(templatesObj).length) {
176
+ throw new Error("No templates found in aggregator file.");
177
+ }
178
+ const binsDir = path.join(path.dirname(aggregatorPath), TPLS_DIR, BINARIES_DIR);
179
+ let unpackedFiles = 0;
180
+ for (const [tplName, tpl] of Object.entries(templatesObj)) {
181
+ relinka("info", `Unpacking template: ${tplName}`);
182
+ for (const [relPath, meta] of Object.entries(tpl.config.files)) {
183
+ try {
184
+ await restoreFile(outDir, relPath, meta, binsDir, force);
185
+ unpackedFiles++;
186
+ } catch (err) {
187
+ relinka("error", `Failed restoring ${relPath}: ${err.message}`);
188
+ }
189
+ }
190
+ }
191
+ relinka("success", `Unpacked ${unpackedFiles} files into ${outDir}`);
192
+ };
193
+ const writeTypesFile = async (outRoot, outputName) => {
194
+ const typesFile = path.join(outRoot, `${outputName}-types.ts`);
195
+ const code = `// Auto-generated type declarations for templates.
196
+ export interface FileMetadata { updatedAt?: string; updatedHash?: string; }
197
+ export interface TemplatesFileContent { content: string | Record<string, unknown>; type: 'text' | 'json' | 'binary'; hasError?: boolean; error?: string; jsonComments?: Record<number, string>; binaryHash?: string; metadata?: FileMetadata; }
198
+ export interface Template { name: string; description: string; config: { files: Record<string, TemplatesFileContent> }; updatedAt?: string; }
199
+ `;
200
+ await fs.writeFile(typesFile, code, "utf8");
30
201
  };
31
202
  export default defineCommand({
32
203
  meta: {
33
204
  name: "pack",
34
- version: "1.1.0",
35
- description: "Packs a directory of templates into TS modules"
205
+ version: "1.2.0",
206
+ description: "Pack templates into TS modules or --unpack them back to files"
36
207
  },
37
208
  args: defineArgs({
38
- dir: { type: "positional", required: true, description: "Directory to process" },
39
- output: { type: "string", default: "my-templates", description: "Output dir" },
40
- whitelabel: { type: "string", default: WHITELABEL_DEFAULT, description: "Rename DLER" },
209
+ input: {
210
+ type: "positional",
211
+ required: true,
212
+ description: "Input directory (pack) or aggregator file (unpack)"
213
+ },
214
+ output: {
215
+ type: "string",
216
+ default: "my-templates",
217
+ description: "Output dir"
218
+ },
219
+ whitelabel: {
220
+ type: "string",
221
+ default: WHITELABEL_DEFAULT,
222
+ description: "Rename DLER"
223
+ },
41
224
  cdn: {
42
225
  type: "string",
43
226
  description: "Remote CDN for binary assets upload (not yet implemented)"
44
227
  },
45
- force: { type: "boolean", default: false, description: "Force overwrite existing files" },
228
+ force: {
229
+ type: "boolean",
230
+ default: false,
231
+ description: "Force overwrite existing files"
232
+ },
46
233
  update: {
47
234
  type: "boolean",
48
235
  default: true,
49
- description: "Update existing templates and add new ones if needed (default: true)"
236
+ description: "Update existing templates and add new ones (pack mode only)"
50
237
  },
51
- /**
52
- * - Without --files: All files are checked and updated if they're newer or have different content
53
- * - With --files: Only specified files are checked and updated if they're newer or have different content
54
- */
55
238
  files: {
56
239
  type: "string",
57
- description: "Comma-separated list of specific files to update (relative to template dir)"
240
+ description: "Comma-separated list of specific files to update"
58
241
  },
59
242
  lastUpdate: {
60
243
  type: "string",
61
- description: "Override lastUpdate timestamp (format: 2025-06-06T14:33:09.240Z)"
244
+ description: "Override lastUpdate timestamp (pack mode only)"
245
+ },
246
+ unpack: {
247
+ type: "boolean",
248
+ default: false,
249
+ description: "Unpack templates from an aggregator file"
62
250
  }
63
251
  }),
64
252
  async run({ args }) {
65
253
  if (args.cdn) throw new Error("Remote CDN support is not implemented yet.");
66
- const dirToProcess = path.resolve(args.dir);
254
+ if (args.unpack) {
255
+ const aggrPath = path.resolve(args.input);
256
+ const outDir2 = path.resolve(args.output);
257
+ try {
258
+ const stat = await fs.stat(aggrPath);
259
+ if (!stat.isFile() || !aggrPath.endsWith(".ts")) {
260
+ throw new Error("Input for --unpack must be a TypeScript aggregator file (*.ts)");
261
+ }
262
+ } catch (err) {
263
+ relinka("error", err.message);
264
+ process.exit(1);
265
+ }
266
+ relinka("info", `Unpacking templates from ${aggrPath} to ${outDir2}`);
267
+ try {
268
+ await unpackTemplates(aggrPath, outDir2, args.force);
269
+ } catch (err) {
270
+ relinka("error", err.message);
271
+ process.exit(1);
272
+ }
273
+ return;
274
+ }
275
+ const dirToProcess = path.resolve(args.input);
67
276
  const outDir = path.resolve(args.output);
68
277
  const outDirName = path.basename(outDir);
69
278
  const typesFile = `${outDirName}-types.ts`;
70
279
  const modFile = `${outDirName}-mod.ts`;
280
+ relinka("info", `Packing templates from ${dirToProcess} to ${outDir}`);
71
281
  const filesToUpdate = args.files ? new Set(args.files.split(",").map((f) => f.trim())) : null;
72
282
  let existingTemplates = {};
73
283
  try {
@@ -84,6 +294,7 @@ export default defineCommand({
84
294
  if (args.update) {
85
295
  try {
86
296
  const modPath = path.join(outDir, modFile);
297
+ const jiti = createJiti(process.cwd());
87
298
  const mod2 = await jiti.import(modPath);
88
299
  existingTemplates = mod2?.DLER_TEMPLATES || mod2?.default || {};
89
300
  } catch (loadError) {
@@ -105,36 +316,13 @@ export default defineCommand({
105
316
  try {
106
317
  await fs.access(path.join(outDir, typesFile));
107
318
  } catch {
108
- const typesContent = `export type FileContent = string | Record<string, unknown>;
109
- export interface FileMetadata {
110
- updatedAt?: string;
111
- updatedHash?: string;
112
- }
113
- export interface TemplatesFileContent {
114
- content: FileContent;
115
- type: "text" | "json" | "binary";
116
- hasError?: boolean;
117
- jsonComments?: Record<number, string>;
118
- binaryHash?: string;
119
- metadata?: FileMetadata;
120
- }
121
- export interface TemplateConfig {
122
- files: Record<string, TemplatesFileContent>;
123
- }
124
- export interface Template {
125
- name: string;
126
- description: string;
127
- config: TemplateConfig;
128
- updatedAt?: string;
129
- }
130
- export interface Templates extends Record<string, Template> {}
131
- `;
132
- await fs.writeFile(path.join(outDir, typesFile), typesContent);
319
+ await writeTypesFile(outDir, outDirName);
133
320
  }
134
321
  const aggregatedImports = [];
135
322
  const aggregatedEntries = [];
136
323
  const mapEntries = [];
137
324
  for (const tplName of templateDirs) {
325
+ relinka("info", `Processing template: ${tplName}`);
138
326
  const absTplDir = path.join(dirToProcess, tplName);
139
327
  const allFiles = await walkDir(absTplDir);
140
328
  const filesRecord = {};
@@ -151,11 +339,15 @@ export interface Templates extends Record<string, Template> {}
151
339
  const fileMetadata = await getFileMetadata(absFile);
152
340
  const existingFile = existingFiles[rel];
153
341
  const existingMetadata = existingFile?.metadata;
154
- if (existingMetadata && (existingMetadata.updatedHash === fileMetadata.updatedHash || fileMetadata.updatedAt <= existingMetadata.updatedAt)) {
342
+ if (existingMetadata?.updatedHash && fileMetadata.updatedHash && (existingMetadata.updatedHash === fileMetadata.updatedHash || fileMetadata.updatedAt && existingMetadata.updatedAt && fileMetadata.updatedAt <= existingMetadata.updatedAt)) {
155
343
  filesRecord[rel] = existingFile;
156
344
  continue;
157
345
  }
158
- const meta = await readFileForTemplate(absFile);
346
+ const meta = await readFileForTemplate(
347
+ absFile,
348
+ rel,
349
+ path.join(outDir, TPLS_DIR, BINARIES_DIR)
350
+ );
159
351
  if (meta.type === "binary") {
160
352
  const hash = await hashFile(absFile);
161
353
  const ext = path.extname(absFile);
@@ -176,12 +368,6 @@ export interface Templates extends Record<string, Template> {}
176
368
  continue;
177
369
  }
178
370
  if (meta.type === "json") {
179
- if (rel.endsWith("package.json")) {
180
- meta.content.__satisfies = "PackageJson";
181
- }
182
- if (rel.endsWith("tsconfig.json")) {
183
- meta.content.__satisfies = "TSConfig";
184
- }
185
371
  }
186
372
  filesRecord[rel] = {
187
373
  ...meta,
@@ -190,10 +376,11 @@ export interface Templates extends Record<string, Template> {}
190
376
  }
191
377
  const varName = TEMPLATE_VAR(tplName, args.whitelabel);
192
378
  const code = [];
193
- const hasPackageJson = Object.values(filesRecord).some(
379
+ const hasPackageJson = Object.keys(filesRecord).some((rel) => rel.endsWith("package.json")) || Object.values(filesRecord).some(
194
380
  (f) => f.type === "json" && f.content && typeof f.content === "object" && "name" in f.content
195
381
  );
196
- const hasTSConfig = Object.values(filesRecord).some(
382
+ const tsconfigPathRe = /(?:^|\/)tsconfig(?:\.[^/]+)?\.json$/;
383
+ const hasTSConfig = Object.keys(filesRecord).some((rel) => tsconfigPathRe.test(rel)) || Object.values(filesRecord).some(
197
384
  (f) => f.type === "json" && f.content && typeof f.content === "object" && "compilerOptions" in f.content
198
385
  );
199
386
  if (hasPackageJson || hasTSConfig) {
@@ -219,7 +406,7 @@ export interface Templates extends Record<string, Template> {}
219
406
  if (meta.jsonComments)
220
407
  code.push(` jsonComments: ${JSON.stringify(meta.jsonComments, null, 2)},`);
221
408
  if (meta.metadata) {
222
- const metadataStr = JSON.stringify(meta.metadata, null, 2).replace(/^/gm, " ").replace(/^ {7} {/m, " {").replace(/^ {8}}/m, " }").replace(/"([a-zA-Z0-9_]+)":/g, "$1:").replace(/}$/m, "},");
409
+ const metadataStr = JSON.stringify(meta.metadata, null, 2).replace(/^/gm, " ").replace(/^ {7} {/m, " {").replace(/^ {8}}/m, " }").replace(/"([a-zA-Z0-9_]+)":/g, "$1:").replace(/\n(\s*)}/, ",\n$1}").replace(/}$/m, "},");
223
410
  code.push(` metadata:${metadataStr}`);
224
411
  }
225
412
  if (meta.type === "binary") {
@@ -230,31 +417,33 @@ export interface Templates extends Record<string, Template> {}
230
417
  code.push(` content: \`${escapeTemplateString(meta.content)}\`,`);
231
418
  code.push(' type: "text",');
232
419
  } else {
233
- const clone = { ...meta.content };
420
+ let clone;
234
421
  let sat = "";
235
- if (rel.endsWith("package.json")) {
236
- sat = " satisfies PackageJson";
237
- } else if (rel.endsWith("tsconfig.json")) {
238
- sat = " satisfies TSConfig";
422
+ try {
423
+ clone = JSON.parse(JSON.stringify(meta.content));
424
+ if (rel.endsWith("package.json")) {
425
+ sat = " satisfies PackageJson";
426
+ } else if (tsconfigPathRe.test(rel)) {
427
+ sat = " satisfies TSConfig";
428
+ }
429
+ } catch (error) {
430
+ clone = {};
431
+ relinka("error", `Failed to process JSON content for ${rel}: ${error}`);
239
432
  }
240
- const jsonStr = JSON.stringify(
241
- clone,
242
- (key, value) => {
243
- if (typeof key === "string" && /^[a-zA-Z0-9_]+$/.test(key)) {
244
- return value;
245
- }
246
- return value;
247
- },
248
- 2
249
- ).split("\n").map((line, i) => {
433
+ const jsonStr = JSON.stringify(clone, null, 2).split("\n").map((line, i) => {
250
434
  if (i === 0) return line;
251
- return " " + line.replace(/"([a-zA-Z0-9_]+)":/g, "$1:");
252
- }).join("\n").replace(/,?\s*}(\s*)$/, "}$1").replace(/,\s*,/g, ",");
435
+ return ` ${line.replace(/"([a-zA-Z0-9_]+)":/g, "$1:")}`;
436
+ }).join("\n").replace(/\n(\s*)}/, ",\n$1}").replace(/,\s*,/g, ",");
253
437
  code.push(` content: ${jsonStr}${sat},`);
254
438
  code.push(' type: "json",');
255
439
  }
256
- if (meta.hasError) code.push(" hasError: true,");
257
- code.push(` }${isLast ? "," : ","}`);
440
+ if (meta.hasError) {
441
+ code.push(" hasError: true,");
442
+ if (meta.error) {
443
+ code.push(` error: "${escapeTemplateString(meta.error)}",`);
444
+ }
445
+ }
446
+ code.push(` }${isLast ? "" : ","}`);
258
447
  });
259
448
  code.push(" },");
260
449
  code.push(" },");
@@ -281,6 +470,13 @@ export interface Templates extends Record<string, Template> {}
281
470
  } else if (!args.update) {
282
471
  relinka("log", `Creating template: ${tplName}`);
283
472
  }
473
+ const filesWithErrors = Object.entries(filesRecord).filter(([_, meta]) => meta.hasError);
474
+ if (filesWithErrors.length > 0) {
475
+ relinka("warn", `Template ${tplName} has files with errors:`);
476
+ for (const [file, meta] of filesWithErrors) {
477
+ relinka("warn", ` - ${file}: ${meta.error || "Unknown error"}`);
478
+ }
479
+ }
284
480
  await fs.writeFile(templatePath, code.join("\n"));
285
481
  aggregatedImports.push(`import { ${varName} } from "./${TPLS_DIR}/${tplName}";`);
286
482
  aggregatedEntries.push(` ${tplName}: ${varName},`);
@@ -294,11 +490,11 @@ export interface Templates extends Record<string, Template> {}
294
490
  ...aggregatedEntries,
295
491
  "};",
296
492
  "",
297
- `export const ${WL}_TEMPLATES = ${WL}_TEMPLATES_OBJ as const;`,
493
+ `export const ${WL}_TEMPLATES = ${WL}_TEMPLATES_OBJ;`,
298
494
  "",
299
- `export interface ${WL}_TEMPLATE_NAMES extends keyof typeof ${WL}_TEMPLATES {}`,
495
+ `export type ${WL}_TEMPLATE_NAMES = keyof typeof ${WL}_TEMPLATES;`,
300
496
  "",
301
- `export const dlerTemplatesMap: Record<string, ${WL}_TEMPLATE_NAMES> = {`,
497
+ `export const ${WL.toLowerCase()}TemplatesMap: Record<string, ${WL}_TEMPLATE_NAMES> = {`,
302
498
  ...mapEntries,
303
499
  "};"
304
500
  ];
@@ -306,9 +502,15 @@ export interface Templates extends Record<string, Template> {}
306
502
  const templatePaths = templateDirs.map(
307
503
  (tpl) => path.relative(process.cwd(), path.join(outDir, TPLS_DIR, `${tpl}.ts`))
308
504
  );
309
- relinka("log", `Packed ${templateDirs.length} templates into ${modFile}:`);
505
+ relinka("success", `Packed ${templateDirs.length} templates into ${modFile}:`);
310
506
  for (const p of templatePaths) {
311
507
  relinka("log", `- ${p}`);
312
508
  }
509
+ const binaryCount = Object.values(existingTemplates).reduce((count, template) => {
510
+ return count + Object.values(template.config.files).filter((file) => file.type === "binary").length;
511
+ }, 0);
512
+ if (binaryCount > 0) {
513
+ relinka("info", ` - ${TPLS_DIR}/${BINARIES_DIR}/* (${binaryCount} binary files)`);
514
+ }
313
515
  }
314
516
  });
@@ -1,20 +1,20 @@
1
1
  import type { RseConfig } from "./rse-types";
2
2
  export declare const defineConfigRse: (userConfig?: Partial<RseConfig>) => {
3
- version?: string | undefined;
4
3
  $schema?: "./schema.json" | "https://reliverse.org/schema.json" | undefined;
5
4
  projectName?: string | undefined;
6
5
  projectAuthor?: string | undefined;
7
6
  projectDescription?: string | undefined;
7
+ version?: string | undefined;
8
8
  projectLicense?: string | undefined;
9
9
  projectRepository?: string | undefined;
10
10
  projectDomain?: string | undefined;
11
11
  projectGitService?: "none" | "github" | "gitlab" | "bitbucket" | undefined;
12
12
  projectDeployService?: "none" | "vercel" | "netlify" | "railway" | "deno" | undefined;
13
- projectPackageManager?: "bun" | "npm" | "yarn" | "pnpm" | undefined;
14
- projectState?: "created" | "creating" | undefined;
15
- projectCategory?: "cli" | "unknown" | "browser" | "website" | "vscode" | "library" | "mobile" | undefined;
13
+ projectPackageManager?: "npm" | "pnpm" | "bun" | "yarn" | undefined;
14
+ projectState?: "creating" | "created" | undefined;
15
+ projectCategory?: "cli" | "unknown" | "website" | "vscode" | "browser" | "library" | "mobile" | undefined;
16
16
  projectSubcategory?: "unknown" | "e-commerce" | "tool" | undefined;
17
- projectFramework?: "rempts" | "npm-jsr" | "unknown" | "vue" | "svelte" | "vscode" | "nextjs" | "vite" | "remix" | "astro" | "nuxt" | "solid" | "qwik" | "wxt" | "lynx" | "react-native" | "expo" | "capacitor" | "ionic" | "electron" | "tauri" | "neutralino" | "citty" | "commander" | "cac" | "meow" | "yargs" | "webextension" | "browser-extension" | undefined;
17
+ projectFramework?: "rempts" | "npm-jsr" | "unknown" | "vscode" | "nextjs" | "vite" | "svelte" | "remix" | "astro" | "nuxt" | "solid" | "qwik" | "vue" | "wxt" | "lynx" | "react-native" | "expo" | "capacitor" | "ionic" | "electron" | "tauri" | "neutralino" | "citty" | "commander" | "cac" | "meow" | "yargs" | "webextension" | "browser-extension" | undefined;
18
18
  projectTemplate?: "unknown" | "blefnk/relivator-nextjs-template" | "blefnk/relivator-docker-template" | "blefnk/next-react-ts-src-minimal" | "blefnk/all-in-one-nextjs-template" | "blefnk/create-t3-app" | "blefnk/create-next-app" | "blefnk/astro-starlight-template" | "blefnk/versator-nextjs-template" | "blefnk/relivator-lynxjs-template" | "blefnk/relivator-react-native-template" | "reliverse/template-browser-extension" | "microsoft/vscode-extension-samples" | "microsoft/vscode-extension-template" | "rsetarter-template" | "blefnk/deno-cli-tutorial" | undefined;
19
19
  projectTemplateDate?: string | undefined;
20
20
  features?: {
@@ -38,15 +38,15 @@ export declare const defineConfigRse: (userConfig?: Partial<RseConfig>) => {
38
38
  analytics?: "unknown" | "vercel" | undefined;
39
39
  authentication?: "unknown" | "better-auth" | "clerk" | "next-auth" | "supabase-auth" | "auth0" | undefined;
40
40
  api?: "rest" | "unknown" | "hono" | "trpc" | "graphql" | undefined;
41
- testing?: "bun" | "unknown" | "jest" | "vitest" | "playwright" | "cypress" | undefined;
41
+ testing?: "unknown" | "bun" | "vitest" | "jest" | "playwright" | "cypress" | undefined;
42
42
  stateManagement?: "unknown" | "zustand" | "jotai" | "redux-toolkit" | undefined;
43
43
  formManagement?: "unknown" | "react-hook-form" | "formik" | undefined;
44
- styling?: "unknown" | "sass" | "tailwind" | "styled-components" | "css-modules" | undefined;
44
+ styling?: "unknown" | "tailwind" | "styled-components" | "css-modules" | "sass" | undefined;
45
45
  uiComponents?: "unknown" | "shadcn-ui" | "chakra-ui" | "material-ui" | undefined;
46
46
  databaseLibrary?: "unknown" | "drizzle" | "prisma" | "supabase" | undefined;
47
- databaseProvider?: "unknown" | "pg" | "mysql" | "sqlite" | "mongodb" | undefined;
48
- linting?: "eslint" | "unknown" | undefined;
49
- formatting?: "biome" | "unknown" | undefined;
47
+ databaseProvider?: "sqlite" | "unknown" | "pg" | "mysql" | "mongodb" | undefined;
48
+ linting?: "unknown" | "eslint" | undefined;
49
+ formatting?: "unknown" | "biome" | undefined;
50
50
  payment?: "unknown" | "stripe" | undefined;
51
51
  monitoring?: "unknown" | "sentry" | undefined;
52
52
  logging?: "unknown" | "axiom" | undefined;
@@ -75,7 +75,7 @@ export declare const defineConfigRse: (userConfig?: Partial<RseConfig>) => {
75
75
  indentStyle?: "space" | "tab" | undefined;
76
76
  quoteMark?: "single" | "double" | undefined;
77
77
  semicolons?: boolean | undefined;
78
- trailingComma?: "all" | "none" | "es5" | undefined;
78
+ trailingComma?: "none" | "es5" | "all" | undefined;
79
79
  bracketSpacing?: boolean | undefined;
80
80
  arrowParens?: "always" | "avoid" | undefined;
81
81
  tabWidth?: number | undefined;
@@ -83,7 +83,7 @@ export declare const defineConfigRse: (userConfig?: Partial<RseConfig>) => {
83
83
  dontRemoveComments?: boolean | undefined;
84
84
  shouldAddComments?: boolean | undefined;
85
85
  typeOrInterface?: "type" | "interface" | "mixed" | undefined;
86
- importOrRequire?: "import" | "require" | "mixed" | undefined;
86
+ importOrRequire?: "mixed" | "import" | "require" | undefined;
87
87
  cjsToEsm?: boolean | undefined;
88
88
  modernize?: {
89
89
  replaceFs?: boolean | undefined;
@@ -96,7 +96,7 @@ export declare const defineConfigRse: (userConfig?: Partial<RseConfig>) => {
96
96
  importSymbol?: string | undefined;
97
97
  } | undefined;
98
98
  monorepo?: {
99
- type?: "bun" | "pnpm" | "none" | "turborepo" | "nx" | undefined;
99
+ type?: "none" | "turborepo" | "nx" | "pnpm" | "bun" | undefined;
100
100
  packages?: string[] | undefined;
101
101
  sharedPackages?: string[] | undefined;
102
102
  } | undefined;
@@ -113,7 +113,7 @@ export declare const defineConfigRse: (userConfig?: Partial<RseConfig>) => {
113
113
  repoBranch?: string | undefined;
114
114
  repoPrivacy?: "unknown" | "public" | "private" | undefined;
115
115
  projectArchitecture?: "unknown" | "fullstack" | "separated" | undefined;
116
- projectRuntime?: "bun" | "node" | "deno" | undefined;
116
+ projectRuntime?: "bun" | "deno" | "node" | undefined;
117
117
  skipPromptsUseAutoBehavior?: boolean | undefined;
118
118
  deployBehavior?: "prompt" | "autoYes" | "autoNo" | undefined;
119
119
  depsBehavior?: "prompt" | "autoYes" | "autoNo" | undefined;
@@ -12,7 +12,7 @@ export function repairAndParseJSON(raw) {
12
12
  try {
13
13
  const repaired = jsonrepair(raw);
14
14
  return JSON.parse(repaired);
15
- } catch (error) {
15
+ } catch (_error) {
16
16
  return null;
17
17
  }
18
18
  }
@@ -10,7 +10,7 @@ import { getBackupAndTempPaths } from "./rse-utils.js";
10
10
  function deepMerge(target, source) {
11
11
  const result = { ...target };
12
12
  for (const key in source) {
13
- if (!Object.prototype.hasOwnProperty.call(source, key)) continue;
13
+ if (!Object.hasOwn(source, key)) continue;
14
14
  const sourceValue = source[key];
15
15
  const targetValue = target[key];
16
16
  if (sourceValue !== void 0) {
@@ -1,5 +1,5 @@
1
1
  import { endPrompt, startPrompt } from "@reliverse/rempts";
2
- const version = "1.7.50";
2
+ const version = "1.7.52";
3
3
  export async function showStartPrompt(isDev) {
4
4
  await startPrompt({
5
5
  titleColor: "inverse",
@@ -9,7 +9,7 @@ export async function ensureDlerConfig(isDev) {
9
9
  const configExists = await fs.pathExists(configPath);
10
10
  if (configExists) return;
11
11
  try {
12
- let pkgDescription = void 0;
12
+ let pkgDescription;
13
13
  try {
14
14
  const pkg = await readPackageJSON();
15
15
  if (pkg && typeof pkg.description === "string" && pkg.description.trim()) {
@@ -75,10 +75,6 @@ export type { CommentStyle, FileExtension, CommentMapping } from "./sdk-impl/uti
75
75
  export { DEFAULT_COMMENT, COMMENT_MAP, getCommentPrefix } from "./sdk-impl/utils/comments.js";
76
76
  export { detectFileType, detectBufferType, detectStreamType, isBinary, getMimeType, } from "./sdk-impl/utils/file-type.js";
77
77
  export { finalizeBuild, finalizePub } from "./sdk-impl/utils/finalize.js";
78
- export { WHITELABEL_DEFAULT, JSON_EXTS, TEMPLATE_VAR, TPLS_DIR, BINARIES_DIR, isJsonExt, } from "./sdk-impl/utils/pack-unpack/pu-constants.js";
79
- export { walkDir, detectFileTypePU, readFileForTemplate, escapeTemplateString, } from "./sdk-impl/utils/pack-unpack/pu-file-utils.js";
80
- export type { FileContent, FileType, FileMetadata, TemplatesFileContent, TemplateConfig, Template, Templates, } from "./sdk-impl/utils/pack-unpack/pu-types.js";
81
- export { extractJsonComments, stripComments } from "./sdk-impl/utils/pack-unpack/pub-json-utils.js";
82
78
  export { resolveCrossLibs, resolveAllCrossLibs } from "./sdk-impl/utils/resolve-cross-libs.js";
83
79
  export { useAggregator } from "./sdk-impl/utils/tools-agg.js";
84
80
  export { printUsage } from "./sdk-impl/utils/tools-impl.js";
@@ -164,21 +164,6 @@ export {
164
164
  getMimeType
165
165
  } from "./sdk-impl/utils/file-type.js";
166
166
  export { finalizeBuild, finalizePub } from "./sdk-impl/utils/finalize.js";
167
- export {
168
- WHITELABEL_DEFAULT,
169
- JSON_EXTS,
170
- TEMPLATE_VAR,
171
- TPLS_DIR,
172
- BINARIES_DIR,
173
- isJsonExt
174
- } from "./sdk-impl/utils/pack-unpack/pu-constants.js";
175
- export {
176
- walkDir,
177
- detectFileTypePU,
178
- readFileForTemplate,
179
- escapeTemplateString
180
- } from "./sdk-impl/utils/pack-unpack/pu-file-utils.js";
181
- export { extractJsonComments, stripComments } from "./sdk-impl/utils/pack-unpack/pub-json-utils.js";
182
167
  export { resolveCrossLibs, resolveAllCrossLibs } from "./sdk-impl/utils/resolve-cross-libs.js";
183
168
  export { useAggregator } from "./sdk-impl/utils/tools-agg.js";
184
169
  export { printUsage } from "./sdk-impl/utils/tools-impl.js";
package/package.json CHANGED
@@ -9,11 +9,11 @@
9
9
  "@reliverse/rempts": "^1.7.28",
10
10
  "@reliverse/runtime": "^1.0.3",
11
11
  "@rollup/plugin-alias": "^5.1.1",
12
- "@rollup/plugin-commonjs": "^28.0.5",
12
+ "@rollup/plugin-commonjs": "^28.0.6",
13
13
  "@rollup/plugin-json": "^6.1.0",
14
14
  "@rollup/plugin-node-resolve": "^16.0.1",
15
15
  "@rollup/plugin-replace": "^6.0.2",
16
- "@rollup/pluginutils": "^5.1.4",
16
+ "@rollup/pluginutils": "^5.2.0",
17
17
  "@sinclair/typebox": "^0.34.35",
18
18
  "autoprefixer": "^10.4.21",
19
19
  "c12": "^3.0.4",
@@ -44,6 +44,7 @@
44
44
  "rollup-plugin-dts": "^6.2.1",
45
45
  "scule": "^1.3.0",
46
46
  "semver": "^7.7.2",
47
+ "strip-json-comments": "^5.0.2",
47
48
  "tinyglobby": "^0.2.14",
48
49
  "ts-morph": "^26.0.0",
49
50
  "untyped": "^2.0.0"
@@ -53,7 +54,7 @@
53
54
  "license": "MIT",
54
55
  "name": "@reliverse/dler",
55
56
  "type": "module",
56
- "version": "1.7.50",
57
+ "version": "1.7.52",
57
58
  "keywords": [
58
59
  "reliverse",
59
60
  "cli",
@@ -1,37 +0,0 @@
1
- declare const _default: import("@reliverse/rempts").Command<{
2
- templatesDir: {
3
- type: "positional";
4
- required: true;
5
- description: string;
6
- };
7
- output: {
8
- type: "string";
9
- default: string;
10
- description: string;
11
- };
12
- cdn: {
13
- type: "string";
14
- description: string;
15
- };
16
- deleteTemplates: {
17
- type: "boolean";
18
- description: string;
19
- default: false;
20
- };
21
- "dry-run": {
22
- type: "boolean";
23
- description: string;
24
- default: false;
25
- };
26
- force: {
27
- type: "boolean";
28
- description: string;
29
- default: false;
30
- };
31
- cleanup: {
32
- type: "boolean";
33
- description: string;
34
- default: false;
35
- };
36
- }>;
37
- export default _default;
@@ -1,200 +0,0 @@
1
- import path from "@reliverse/pathkit";
2
- import { relinka } from "@reliverse/relinka";
3
- import { defineArgs, defineCommand } from "@reliverse/rempts";
4
- import { createJiti } from "jiti";
5
- import { promises as fs } from "node:fs";
6
- import { TPLS_DIR, BINARIES_DIR } from "../../libs/sdk/sdk-impl/utils/pack-unpack/pu-constants.js";
7
- const jiti = createJiti(import.meta.url);
8
- async function removeEmptyDirs(dirPath) {
9
- if (!await fs.access(dirPath).then(() => true).catch(() => false))
10
- return;
11
- const entries = await fs.readdir(dirPath);
12
- if (entries.length === 0) {
13
- await fs.rmdir(dirPath);
14
- const parentDir = path.dirname(dirPath);
15
- if (parentDir !== dirPath) {
16
- await removeEmptyDirs(parentDir);
17
- }
18
- }
19
- }
20
- async function deleteTemplates(templatesDir, templatesDirName, dryRun) {
21
- const modFile = `${templatesDirName}-mod.ts`;
22
- const typesFile = `${templatesDirName}-types.ts`;
23
- const implDir = path.join(templatesDir, TPLS_DIR);
24
- const binariesDir = path.join(implDir, BINARIES_DIR);
25
- const filesToRemove = [path.join(templatesDir, modFile), path.join(templatesDir, typesFile)];
26
- try {
27
- const templateFiles = await fs.readdir(implDir);
28
- for (const file of templateFiles) {
29
- if (file.endsWith(".ts")) {
30
- filesToRemove.push(path.join(implDir, file));
31
- }
32
- }
33
- } catch (error) {
34
- if (error.code !== "ENOENT") {
35
- throw error;
36
- }
37
- }
38
- for (const file of filesToRemove) {
39
- if (dryRun) {
40
- relinka("log", `[DRY RUN] Would remove: ${file}`);
41
- continue;
42
- }
43
- try {
44
- await fs.unlink(file);
45
- relinka("log", `Removed: ${file}`);
46
- } catch (error) {
47
- if (error.code !== "ENOENT") {
48
- relinka("warn", `Failed to remove ${file}: ${error.message}`);
49
- }
50
- }
51
- }
52
- try {
53
- if (await fs.access(binariesDir).then(() => true).catch(() => false)) {
54
- if (dryRun) {
55
- relinka("log", `[DRY RUN] Would remove directory: ${binariesDir}`);
56
- } else {
57
- await fs.rm(binariesDir, { recursive: true });
58
- relinka("log", `Removed directory: ${binariesDir}`);
59
- }
60
- }
61
- } catch (error) {
62
- relinka("warn", `Failed to remove binaries directory: ${error.message}`);
63
- }
64
- await removeEmptyDirs(implDir);
65
- await removeEmptyDirs(templatesDir);
66
- }
67
- export default defineCommand({
68
- meta: {
69
- name: "unpack",
70
- version: "1.1.0",
71
- description: "Creates file structure from packed templates"
72
- },
73
- args: defineArgs({
74
- templatesDir: { type: "positional", required: true, description: "Dir containing *-mod.ts" },
75
- output: { type: "string", default: "unpacked", description: "Where to write files" },
76
- cdn: {
77
- type: "string",
78
- description: "Remote CDN base for binary assets download (not yet implemented)"
79
- },
80
- deleteTemplates: {
81
- type: "boolean",
82
- description: "Delete template implementation files (*-mod.ts, *-types.ts, etc.)",
83
- default: false
84
- },
85
- "dry-run": {
86
- type: "boolean",
87
- description: "Preview changes without applying them",
88
- default: false
89
- },
90
- force: {
91
- type: "boolean",
92
- description: "Force overwrite existing files",
93
- default: false
94
- },
95
- cleanup: {
96
- type: "boolean",
97
- description: "Delete output directory before unpacking",
98
- default: false
99
- }
100
- }),
101
- async run({ args }) {
102
- if (args.cdn) throw new Error("Remote CDN support is not implemented yet.");
103
- const templatesDir = path.resolve(args.templatesDir);
104
- const templatesDirName = path.basename(templatesDir);
105
- const modFile = `${templatesDirName}-mod.ts`;
106
- const modPath = path.join(templatesDir, modFile);
107
- if (args.deleteTemplates) {
108
- await deleteTemplates(templatesDir, templatesDirName, args["dry-run"]);
109
- if (args["dry-run"]) {
110
- relinka("log", "[DRY RUN] Templates deletion completed");
111
- } else {
112
- relinka("log", "Templates deletion completed");
113
- }
114
- return;
115
- }
116
- if (args.cleanup) {
117
- try {
118
- await fs.rm(args.output, { recursive: true, force: true });
119
- await fs.mkdir(args.output, { recursive: true });
120
- relinka("log", `Cleaned output directory: ${args.output}`);
121
- } catch (error) {
122
- if (error.code !== "ENOENT") {
123
- throw error;
124
- }
125
- }
126
- }
127
- const mod = await jiti.import(modPath);
128
- const templatesObj = mod?.DLER_TEMPLATES || mod?.default || (() => {
129
- throw new Error(`Invalid ${modFile}`);
130
- })();
131
- for (const tpl of Object.values(templatesObj)) {
132
- await restoreTemplate(tpl, templatesDir, args.output, args.force);
133
- }
134
- const relativeOutput = path.relative(process.cwd(), args.output);
135
- relinka(
136
- "log",
137
- `Unpacked ${Object.keys(templatesObj).length} templates from ${modFile} into ${relativeOutput}${args.force ? " (--force is true, dir was overwritten)" : ""}`
138
- );
139
- }
140
- });
141
- const restoreTemplate = async (tpl, templatesRoot, outRoot, force) => {
142
- const existingFiles = [];
143
- for (const [rel, meta] of Object.entries(tpl.config.files)) {
144
- const destAbs = path.join(outRoot, rel);
145
- if (!force) {
146
- try {
147
- await fs.access(destAbs);
148
- existingFiles.push(rel);
149
- continue;
150
- } catch {
151
- }
152
- }
153
- await fs.mkdir(path.dirname(destAbs), { recursive: true });
154
- switch (meta.type) {
155
- case "binary": {
156
- if (!meta.binaryHash) {
157
- await fs.writeFile(destAbs, "");
158
- break;
159
- }
160
- const ext = path.extname(rel);
161
- const binPath = path.join(
162
- templatesRoot,
163
- TPLS_DIR,
164
- BINARIES_DIR,
165
- `${meta.binaryHash}${ext}`
166
- );
167
- await fs.copyFile(binPath, destAbs);
168
- break;
169
- }
170
- case "text": {
171
- await fs.writeFile(destAbs, meta.content, "utf8");
172
- break;
173
- }
174
- case "json": {
175
- const txt = JSON.stringify(meta.content, null, 2);
176
- const withComments = meta.jsonComments ? injectComments(txt, meta.jsonComments) : txt;
177
- await fs.writeFile(destAbs, withComments, "utf8");
178
- break;
179
- }
180
- }
181
- }
182
- if (existingFiles.length > 0) {
183
- throw new Error(
184
- `Cannot unpack: ${existingFiles.length} file(s) already exist. Use --force to overwrite existing files`
185
- );
186
- }
187
- };
188
- const injectComments = (json, comments) => {
189
- const lines = json.split("\n");
190
- const entries = Object.entries(comments).map(([k, v]) => [Number(k), v]).sort((a, b) => a[0] - b[0]);
191
- let offset = 0;
192
- for (const [ln, comment] of entries) {
193
- const idx = ln - 1 + offset;
194
- const indent = lines[idx]?.match(/^\s*/)?.[0] ?? "";
195
- const block = comment.split("\n").map((l) => indent + l);
196
- lines.splice(idx, 0, ...block);
197
- offset += block.length;
198
- }
199
- return lines.join("\n") + "\n";
200
- };
@@ -1,6 +0,0 @@
1
- export declare const WHITELABEL_DEFAULT = "DLER";
2
- export declare const JSON_EXTS: Set<string>;
3
- export declare const TEMPLATE_VAR: (tpl: string, wl: string) => string;
4
- export declare const TPLS_DIR = "tpls";
5
- export declare const BINARIES_DIR = "binaries";
6
- export declare const isJsonExt: (f: string) => boolean;
@@ -1,7 +0,0 @@
1
- import path from "@reliverse/pathkit";
2
- export const WHITELABEL_DEFAULT = "DLER";
3
- export const JSON_EXTS = /* @__PURE__ */ new Set(["json", "jsonc", "json5", "jsonl"]);
4
- export const TEMPLATE_VAR = (tpl, wl) => `${tpl.toUpperCase()}_${wl.toUpperCase()}_TEMPLATE`;
5
- export const TPLS_DIR = "tpls";
6
- export const BINARIES_DIR = "binaries";
7
- export const isJsonExt = (f) => JSON_EXTS.has(path.extname(f).slice(1).toLowerCase());
@@ -1,5 +0,0 @@
1
- import type { FileType, TemplatesFileContent } from "./pu-types";
2
- export declare const walkDir: (dir: string) => Promise<string[]>;
3
- export declare const detectFileTypePU: (file: string) => Promise<FileType>;
4
- export declare const readFileForTemplate: (absPath: string) => Promise<TemplatesFileContent>;
5
- export declare const escapeTemplateString: (src: string) => string;
@@ -1,40 +0,0 @@
1
- import path from "@reliverse/pathkit";
2
- import { promises as fs } from "node:fs";
3
- import { isBinaryExt } from "../binary.js";
4
- import { isJsonExt } from "./pu-constants.js";
5
- import { extractJsonComments, stripComments } from "./pub-json-utils.js";
6
- export const walkDir = async (dir) => {
7
- const entries = [];
8
- for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
9
- const full = path.join(dir, entry.name);
10
- if (entry.isDirectory()) {
11
- entries.push(...await walkDir(full));
12
- } else {
13
- entries.push(full);
14
- }
15
- }
16
- return entries;
17
- };
18
- export const detectFileTypePU = async (file) => {
19
- if (await isBinaryExt(file)) return "binary";
20
- if (isJsonExt(file)) return "json";
21
- return "text";
22
- };
23
- export const readFileForTemplate = async (absPath) => {
24
- const type = await detectFileTypePU(absPath);
25
- try {
26
- if (type === "binary") {
27
- return { type, content: "" };
28
- }
29
- const data = await fs.readFile(absPath, "utf8");
30
- if (type === "json") {
31
- const jsonComments = extractJsonComments(data);
32
- const json = JSON.parse(stripComments(data));
33
- return { type, content: json, jsonComments };
34
- }
35
- return { type, content: data };
36
- } catch {
37
- return { type, content: "", hasError: true };
38
- }
39
- };
40
- export const escapeTemplateString = (src) => src.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
@@ -1,24 +0,0 @@
1
- export type FileContent = string | Record<string, unknown>;
2
- export type FileType = "text" | "json" | "binary";
3
- export interface FileMetadata {
4
- updatedAt: string;
5
- updatedHash: string;
6
- }
7
- export interface TemplatesFileContent {
8
- content: FileContent;
9
- type: FileType;
10
- hasError?: boolean;
11
- jsonComments?: Record<number, string>;
12
- binaryHash?: string;
13
- metadata?: FileMetadata;
14
- }
15
- export interface TemplateConfig {
16
- files: Record<string, TemplatesFileContent>;
17
- }
18
- export interface Template {
19
- name: string;
20
- description: string;
21
- config: TemplateConfig;
22
- updatedAt: string;
23
- }
24
- export type Templates = Record<string, Template>;
@@ -1,17 +0,0 @@
1
- /**
2
- * Helpers for reading / writing JSON-with-comments files while
3
- * preserving (or later reinjecting) on–line and block comments.
4
- *
5
- * Supported comment styles:
6
- * // single-line
7
- * /* … *\/
8
- * /** … *\/
9
- */
10
- export declare const extractJsonComments: (raw: string) => Record<number, string> | undefined;
11
- /**
12
- * Very small "strip comments" helper:
13
- * removes // … and /* … *\/ (including multi-line).
14
- * This is intentionally simple; for complex JSONC we may want
15
- * use something like `strip-json-comments` in the future.
16
- */
17
- export declare const stripComments: (raw: string) => string;
@@ -1,46 +0,0 @@
1
- export const extractJsonComments = (raw) => {
2
- const out = {};
3
- const lines = raw.split("\n");
4
- for (let i = 0; i < lines.length; ) {
5
- const line = lines[i];
6
- if (!line) {
7
- i += 1;
8
- continue;
9
- }
10
- const trimmed = line.trimStart();
11
- const indent = line.length - trimmed.length;
12
- if (trimmed.startsWith("//")) {
13
- out[i + 1] = trimmed.slice(2).trimStart();
14
- i += 1;
15
- continue;
16
- }
17
- if (trimmed.startsWith("/*")) {
18
- const isSupported = trimmed.startsWith("/**") || trimmed.startsWith("/*");
19
- if (!isSupported) {
20
- i += 1;
21
- continue;
22
- }
23
- const buff = [trimmed];
24
- let j = i + 1;
25
- while (j < lines.length) {
26
- const currentLine = lines[j];
27
- if (!currentLine) {
28
- j += 1;
29
- continue;
30
- }
31
- if (currentLine.trimEnd().endsWith("*/")) {
32
- buff.push(currentLine.slice(indent));
33
- break;
34
- }
35
- buff.push(currentLine.slice(indent));
36
- j += 1;
37
- }
38
- out[i + 1] = buff.join("\n");
39
- i = j + 1;
40
- continue;
41
- }
42
- i += 1;
43
- }
44
- return Object.keys(out).length ? out : void 0;
45
- };
46
- export const stripComments = (raw) => raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\r\n]*/g, "");