@reliverse/dler 1.7.49 → 1.7.51

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:**
@@ -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,41 +1,216 @@
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" },
209
+ input: {
210
+ type: "positional",
211
+ required: true,
212
+ description: "Input directory (pack) or aggregator file (unpack)"
213
+ },
39
214
  output: { type: "string", default: "my-templates", description: "Output dir" },
40
215
  whitelabel: { type: "string", default: WHITELABEL_DEFAULT, description: "Rename DLER" },
41
216
  cdn: {
@@ -46,28 +221,45 @@ export default defineCommand({
46
221
  update: {
47
222
  type: "boolean",
48
223
  default: true,
49
- description: "Update existing templates and add new ones if needed (default: true)"
50
- },
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
- files: {
56
- type: "string",
57
- description: "Comma-separated list of specific files to update (relative to template dir)"
224
+ description: "Update existing templates and add new ones (pack mode only)"
58
225
  },
59
- lastUpdate: {
60
- type: "string",
61
- description: "Override lastUpdate timestamp (format: 2025-06-06T14:33:09.240Z)"
226
+ files: { type: "string", description: "Comma-separated list of specific files to update" },
227
+ lastUpdate: { type: "string", description: "Override lastUpdate timestamp (pack mode only)" },
228
+ unpack: {
229
+ type: "boolean",
230
+ default: false,
231
+ description: "Unpack templates from an aggregator file"
62
232
  }
63
233
  }),
64
234
  async run({ args }) {
65
235
  if (args.cdn) throw new Error("Remote CDN support is not implemented yet.");
66
- const dirToProcess = path.resolve(args.dir);
236
+ if (args.unpack) {
237
+ const aggrPath = path.resolve(args.input);
238
+ const outDir2 = path.resolve(args.output);
239
+ try {
240
+ const stat = await fs.stat(aggrPath);
241
+ if (!stat.isFile() || !aggrPath.endsWith(".ts")) {
242
+ throw new Error("Input for --unpack must be a TypeScript aggregator file (*.ts)");
243
+ }
244
+ } catch (err) {
245
+ relinka("error", err.message);
246
+ process.exit(1);
247
+ }
248
+ relinka("info", `Unpacking templates from ${aggrPath} to ${outDir2}`);
249
+ try {
250
+ await unpackTemplates(aggrPath, outDir2, args.force);
251
+ } catch (err) {
252
+ relinka("error", err.message);
253
+ process.exit(1);
254
+ }
255
+ return;
256
+ }
257
+ const dirToProcess = path.resolve(args.input);
67
258
  const outDir = path.resolve(args.output);
68
259
  const outDirName = path.basename(outDir);
69
260
  const typesFile = `${outDirName}-types.ts`;
70
261
  const modFile = `${outDirName}-mod.ts`;
262
+ relinka("info", `Packing templates from ${dirToProcess} to ${outDir}`);
71
263
  const filesToUpdate = args.files ? new Set(args.files.split(",").map((f) => f.trim())) : null;
72
264
  let existingTemplates = {};
73
265
  try {
@@ -84,6 +276,7 @@ export default defineCommand({
84
276
  if (args.update) {
85
277
  try {
86
278
  const modPath = path.join(outDir, modFile);
279
+ const jiti = createJiti(process.cwd());
87
280
  const mod2 = await jiti.import(modPath);
88
281
  existingTemplates = mod2?.DLER_TEMPLATES || mod2?.default || {};
89
282
  } catch (loadError) {
@@ -105,36 +298,13 @@ export default defineCommand({
105
298
  try {
106
299
  await fs.access(path.join(outDir, typesFile));
107
300
  } 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);
301
+ await writeTypesFile(outDir, outDirName);
133
302
  }
134
303
  const aggregatedImports = [];
135
304
  const aggregatedEntries = [];
136
305
  const mapEntries = [];
137
306
  for (const tplName of templateDirs) {
307
+ relinka("info", `Processing template: ${tplName}`);
138
308
  const absTplDir = path.join(dirToProcess, tplName);
139
309
  const allFiles = await walkDir(absTplDir);
140
310
  const filesRecord = {};
@@ -151,11 +321,15 @@ export interface Templates extends Record<string, Template> {}
151
321
  const fileMetadata = await getFileMetadata(absFile);
152
322
  const existingFile = existingFiles[rel];
153
323
  const existingMetadata = existingFile?.metadata;
154
- if (existingMetadata && (existingMetadata.updatedHash === fileMetadata.updatedHash || fileMetadata.updatedAt <= existingMetadata.updatedAt)) {
324
+ if (existingMetadata && existingMetadata.updatedHash && fileMetadata.updatedHash && (existingMetadata.updatedHash === fileMetadata.updatedHash || fileMetadata.updatedAt && existingMetadata.updatedAt && fileMetadata.updatedAt <= existingMetadata.updatedAt)) {
155
325
  filesRecord[rel] = existingFile;
156
326
  continue;
157
327
  }
158
- const meta = await readFileForTemplate(absFile);
328
+ const meta = await readFileForTemplate(
329
+ absFile,
330
+ rel,
331
+ path.join(outDir, TPLS_DIR, BINARIES_DIR)
332
+ );
159
333
  if (meta.type === "binary") {
160
334
  const hash = await hashFile(absFile);
161
335
  const ext = path.extname(absFile);
@@ -176,12 +350,6 @@ export interface Templates extends Record<string, Template> {}
176
350
  continue;
177
351
  }
178
352
  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
353
  }
186
354
  filesRecord[rel] = {
187
355
  ...meta,
@@ -230,31 +398,33 @@ export interface Templates extends Record<string, Template> {}
230
398
  code.push(` content: \`${escapeTemplateString(meta.content)}\`,`);
231
399
  code.push(' type: "text",');
232
400
  } else {
233
- const clone = { ...meta.content };
401
+ let clone;
234
402
  let sat = "";
235
- if (rel.endsWith("package.json")) {
236
- sat = " satisfies PackageJson";
237
- } else if (rel.endsWith("tsconfig.json")) {
238
- sat = " satisfies TSConfig";
403
+ try {
404
+ clone = JSON.parse(JSON.stringify(meta.content));
405
+ if (rel.endsWith("package.json")) {
406
+ sat = " satisfies PackageJson";
407
+ } else if (rel.endsWith("tsconfig.json")) {
408
+ sat = " satisfies TSConfig";
409
+ }
410
+ } catch (error) {
411
+ clone = {};
412
+ relinka("error", `Failed to process JSON content for ${rel}: ${error}`);
239
413
  }
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) => {
414
+ const jsonStr = JSON.stringify(clone, null, 2).split("\n").map((line, i) => {
250
415
  if (i === 0) return line;
251
416
  return " " + line.replace(/"([a-zA-Z0-9_]+)":/g, "$1:");
252
- }).join("\n").replace(/},?\s*$/, "},").replace(/,\s*,/g, ",");
417
+ }).join("\n").replace(/,?\s*}(\s*)$/, "}$1").replace(/,\s*,/g, ",");
253
418
  code.push(` content: ${jsonStr}${sat},`);
254
419
  code.push(' type: "json",');
255
420
  }
256
- if (meta.hasError) code.push(" hasError: true,");
257
- code.push(` }${isLast ? "," : ","}`);
421
+ if (meta.hasError) {
422
+ code.push(" hasError: true,");
423
+ if (meta.error) {
424
+ code.push(` error: "${escapeTemplateString(meta.error)}",`);
425
+ }
426
+ }
427
+ code.push(` }${isLast ? "" : ","}`);
258
428
  });
259
429
  code.push(" },");
260
430
  code.push(" },");
@@ -281,6 +451,13 @@ export interface Templates extends Record<string, Template> {}
281
451
  } else if (!args.update) {
282
452
  relinka("log", `Creating template: ${tplName}`);
283
453
  }
454
+ const filesWithErrors = Object.entries(filesRecord).filter(([_, meta]) => meta.hasError);
455
+ if (filesWithErrors.length > 0) {
456
+ relinka("warn", `Template ${tplName} has files with errors:`);
457
+ for (const [file, meta] of filesWithErrors) {
458
+ relinka("warn", ` - ${file}: ${meta.error || "Unknown error"}`);
459
+ }
460
+ }
284
461
  await fs.writeFile(templatePath, code.join("\n"));
285
462
  aggregatedImports.push(`import { ${varName} } from "./${TPLS_DIR}/${tplName}";`);
286
463
  aggregatedEntries.push(` ${tplName}: ${varName},`);
@@ -294,11 +471,11 @@ export interface Templates extends Record<string, Template> {}
294
471
  ...aggregatedEntries,
295
472
  "};",
296
473
  "",
297
- `export const ${WL}_TEMPLATES = ${WL}_TEMPLATES_OBJ as const;`,
474
+ `export const ${WL}_TEMPLATES = ${WL}_TEMPLATES_OBJ;`,
298
475
  "",
299
- `export interface ${WL}_TEMPLATE_NAMES extends keyof typeof ${WL}_TEMPLATES {}`,
476
+ `export type ${WL}_TEMPLATE_NAMES = keyof typeof ${WL}_TEMPLATES;`,
300
477
  "",
301
- `export const dlerTemplatesMap: Record<string, ${WL}_TEMPLATE_NAMES> = {`,
478
+ `export const ${WL.toLowerCase()}TemplatesMap: Record<string, ${WL}_TEMPLATE_NAMES> = {`,
302
479
  ...mapEntries,
303
480
  "};"
304
481
  ];
@@ -306,9 +483,15 @@ export interface Templates extends Record<string, Template> {}
306
483
  const templatePaths = templateDirs.map(
307
484
  (tpl) => path.relative(process.cwd(), path.join(outDir, TPLS_DIR, `${tpl}.ts`))
308
485
  );
309
- relinka("log", `Packed ${templateDirs.length} templates into ${modFile}:`);
486
+ relinka("success", `Packed ${templateDirs.length} templates into ${modFile}:`);
310
487
  for (const p of templatePaths) {
311
488
  relinka("log", `- ${p}`);
312
489
  }
490
+ const binaryCount = Object.values(existingTemplates).reduce((count, template) => {
491
+ return count + Object.values(template.config.files).filter((file) => file.type === "binary").length;
492
+ }, 0);
493
+ if (binaryCount > 0) {
494
+ relinka("info", ` - ${TPLS_DIR}/${BINARIES_DIR}/* (${binaryCount} binary files)`);
495
+ }
313
496
  }
314
497
  });
@@ -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?: "npm" | "bun" | "yarn" | "pnpm" | undefined;
14
- projectState?: "created" | "creating" | undefined;
15
- projectCategory?: "browser" | "cli" | "unknown" | "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" | "vue" | "unknown" | "vscode" | "nextjs" | "vite" | "svelte" | "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?: {
@@ -33,16 +33,14 @@ export declare const defineConfigRse: (userConfig?: Partial<RseConfig>) => {
33
33
  themes?: string[] | undefined;
34
34
  } | undefined;
35
35
  preferredLibraries?: {
36
- search?: "unknown" | "algolia" | undefined;
37
- cdn?: "unknown" | "cloudflare" | undefined;
38
36
  i18n?: "unknown" | "next-intl" | undefined;
39
37
  analytics?: "unknown" | "vercel" | undefined;
40
38
  authentication?: "unknown" | "better-auth" | "clerk" | "next-auth" | "supabase-auth" | "auth0" | undefined;
41
- api?: "rest" | "unknown" | "hono" | "trpc" | "graphql" | undefined;
42
- testing?: "bun" | "unknown" | "vitest" | "jest" | "playwright" | "cypress" | undefined;
39
+ api?: "unknown" | "hono" | "trpc" | "graphql" | "rest" | undefined;
40
+ testing?: "unknown" | "bun" | "vitest" | "jest" | "playwright" | "cypress" | undefined;
43
41
  stateManagement?: "unknown" | "zustand" | "jotai" | "redux-toolkit" | undefined;
44
42
  formManagement?: "unknown" | "react-hook-form" | "formik" | undefined;
45
- styling?: "sass" | "unknown" | "tailwind" | "styled-components" | "css-modules" | undefined;
43
+ styling?: "unknown" | "tailwind" | "styled-components" | "css-modules" | "sass" | undefined;
46
44
  uiComponents?: "unknown" | "shadcn-ui" | "chakra-ui" | "material-ui" | undefined;
47
45
  databaseLibrary?: "unknown" | "drizzle" | "prisma" | "supabase" | undefined;
48
46
  databaseProvider?: "unknown" | "pg" | "mysql" | "sqlite" | "mongodb" | undefined;
@@ -53,6 +51,7 @@ export declare const defineConfigRse: (userConfig?: Partial<RseConfig>) => {
53
51
  logging?: "unknown" | "axiom" | undefined;
54
52
  forms?: "unknown" | "react-hook-form" | undefined;
55
53
  notifications?: "unknown" | "sonner" | undefined;
54
+ search?: "unknown" | "algolia" | undefined;
56
55
  uploads?: "unknown" | "uploadthing" | undefined;
57
56
  validation?: "unknown" | "zod" | "typebox" | "valibot" | undefined;
58
57
  documentation?: "unknown" | "starlight" | "nextra" | undefined;
@@ -60,6 +59,7 @@ export declare const defineConfigRse: (userConfig?: Partial<RseConfig>) => {
60
59
  mail?: "unknown" | "resend" | undefined;
61
60
  cache?: "unknown" | "redis" | undefined;
62
61
  storage?: "unknown" | "cloudflare" | undefined;
62
+ cdn?: "unknown" | "cloudflare" | undefined;
63
63
  cms?: "unknown" | "contentlayer" | undefined;
64
64
  seo?: "unknown" | "next-seo" | undefined;
65
65
  motion?: "unknown" | "framer" | 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?: "none" | "all" | "es5" | undefined;
78
+ trailingComma?: "none" | "es5" | "all" | undefined;
79
79
  bracketSpacing?: boolean | undefined;
80
80
  arrowParens?: "always" | "avoid" | undefined;
81
81
  tabWidth?: number | 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?: "none" | "bun" | "pnpm" | "turborepo" | "nx" | undefined;
99
+ type?: "none" | "turborepo" | "nx" | "pnpm" | "bun" | undefined;
100
100
  packages?: string[] | undefined;
101
101
  sharedPackages?: string[] | undefined;
102
102
  } | undefined;
@@ -1,5 +1,5 @@
1
1
  import { endPrompt, startPrompt } from "@reliverse/rempts";
2
- const version = "1.7.49";
2
+ const version = "1.7.51";
3
3
  export async function showStartPrompt(isDev) {
4
4
  await startPrompt({
5
5
  titleColor: "inverse",
@@ -195,7 +195,6 @@ async function resolveAllCrossLibs(alias = "~", subFolders = ["npm", "jsr"], bui
195
195
  throw error;
196
196
  }
197
197
  } else {
198
- relinka("error", `[resolveAllCrossLibs] Bin directory does not exist: ${binDir}`);
199
198
  }
200
199
  }
201
200
  })
@@ -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
@@ -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.49",
57
+ "version": "1.7.51",
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, "");