paralayer 1.11.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1,208 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ paralayer
4
+ } from "./chunk-YSWJGCOE.js";
2
5
 
3
6
  // src/bin.ts
4
7
  import minimist from "minimist";
5
-
6
- // src/paralayer.ts
7
- import { watch } from "chokidar";
8
- import { Queue, Unit, safe } from "dropcap/utils";
9
- import { mkdir, readFile, rm, writeFile } from "fs/promises";
10
- import { basename, extname, join, relative } from "path";
11
- var Paralayer = class extends Unit {
12
- files = {};
13
- options;
14
- started = false;
15
- ready = false;
16
- ready$ = Promise.withResolvers();
17
- queue = new Queue();
18
- extensions = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
19
- previousLayers = [];
20
- watcher = null;
21
- constructor(options) {
22
- super();
23
- this.options = options;
24
- }
25
- async start() {
26
- if (this.started) return;
27
- this.started = true;
28
- await safe(rm(this.options.output, { recursive: true }));
29
- this.watcher = watch(this.options.input);
30
- this.watcher.on("all", this.onAll);
31
- this.watcher.on("ready", this.onReady);
32
- await this.ready$.promise;
33
- await this.queue.add(() => this.build());
34
- if (!this.options.watch) await this.watcher.close();
35
- const setupJsPath = join(this.options.output, "setup.js");
36
- return await readFile(setupJsPath, "utf-8");
37
- }
38
- // ---------------------------------------------------------------------------
39
- // HANDLERS
40
- // ---------------------------------------------------------------------------
41
- onAll = async (event, path) => {
42
- if (!["add", "change", "unlink"].includes(event)) return;
43
- const ext = extname(path);
44
- if (!this.extensions.has(ext)) return;
45
- if (!this.getLayer(path)) return;
46
- if (!this.ready) {
47
- this.files[path] = null;
48
- return;
49
- }
50
- if (event === "unlink") {
51
- delete this.files[path];
52
- await this.queue.add(() => this.build());
53
- }
54
- if (event === "add" || event === "change") {
55
- this.files[path] = null;
56
- await this.queue.add(() => this.build());
57
- }
58
- };
59
- onReady = async () => {
60
- this.ready = true;
61
- this.ready$.resolve();
62
- };
63
- // ---------------------------------------------------------------------------
64
- // BUILD
65
- // ---------------------------------------------------------------------------
66
- async build() {
67
- const paths2 = Object.keys(this.files);
68
- if (paths2.length === 0) return;
69
- const pathsByLayers = Object.groupBy(paths2, (path) => this.getLayer(path));
70
- const allLayers = Object.keys(pathsByLayers);
71
- await mkdir(this.options.output, { recursive: true });
72
- await Promise.all(
73
- paths2.map(async (path) => {
74
- if (this.files[path]) return;
75
- const content = await readFile(path, "utf-8");
76
- const names = this.extractExportedClassNames(content);
77
- this.files[path] = { content, names };
78
- })
79
- );
80
- for (const layer in pathsByLayers) {
81
- const layerFile = join(this.options.output, `layer.${layer}.ts`);
82
- const layerPaths = pathsByLayers[layer].toSorted();
83
- const layerContent = this.generateLayerContent(layer, layerPaths);
84
- await this.write(layerFile, layerContent);
85
- if (this.isTopLayer(layer)) {
86
- const indexFile = join(this.options.output, `index.${layer}.ts`);
87
- const indexContent = this.generateIndexContent(layer, allLayers);
88
- await this.write(indexFile, indexContent);
89
- }
90
- }
91
- const removedLayers = this.previousLayers.filter((l) => !allLayers.includes(l));
92
- this.previousLayers = allLayers;
93
- for (const layer of removedLayers) {
94
- const layerFile = join(this.options.output, `layer.${layer}.ts`);
95
- await rm(layerFile);
96
- if (this.isTopLayer(layer)) {
97
- const indexFile = join(this.options.output, `index.${layer}.ts`);
98
- await rm(indexFile);
99
- }
100
- }
101
- const setupFile = join(this.options.output, "setup.js");
102
- const setupContent = this.generateSetupContent(allLayers);
103
- await this.write(setupFile, setupContent);
104
- }
105
- // ---------------------------------------------------------------------------
106
- // HELPERS
107
- // ---------------------------------------------------------------------------
108
- extractExportedClassNames(content) {
109
- if (content.includes("paralayer-ignore")) return [];
110
- return content.split("export class ").slice(1).map((part) => part.split(" ")[0].split("<")[0]);
111
- }
112
- generateLayerContent(layer, layerPaths) {
113
- const layerName = this.getLayerName(layer, "camel");
114
- const LayerName = this.getLayerName(layer, "Pascal");
115
- const allNames = layerPaths.flatMap((path) => this.files[path]?.names ?? []);
116
- const imports = layerPaths.map((path) => {
117
- const file = this.files[path];
118
- if (!file) return "";
119
- if (file.names.length === 0) return "";
120
- const names = file.names;
121
- const types = file.names.map((name) => `type ${name} as ${name}Type`);
122
- const relativePath = relative(this.options.output, path);
123
- return `import { ${[...names, ...types].join(", ")} } from '${relativePath}'`;
124
- }).filter(Boolean);
125
- const assign = [`Object.assign(${layerName}, {`, ...allNames.map((name) => ` ${name},`), `})`];
126
- let extendPascal = "";
127
- let extendCamel = "";
128
- if (this.options.globalLayerName && layer !== this.options.globalLayerName) {
129
- extendPascal = `extends ${this.getLayerName(this.options.globalLayerName, "Pascal")} `;
130
- extendCamel = `extends ${this.getLayerName(this.options.globalLayerName, "camel")} `;
131
- }
132
- const globals = [
133
- `declare global {`,
134
- ` const ${layerName}: ${LayerName}`,
135
- ``,
136
- ` interface ${LayerName} ${extendPascal}{`,
137
- ...allNames.map((name) => ` ${name}: typeof ${name}`),
138
- ` }`,
139
- ``,
140
- ` interface ${layerName} ${extendCamel}{`,
141
- ...allNames.map((name) => ` ${name}: ${name}`),
142
- ` }`,
143
- ``,
144
- ` namespace ${layerName} {`,
145
- ...allNames.map((name) => ` export type ${name} = ${name}Type`),
146
- ` }`,
147
- `}`
148
- ];
149
- return [...imports, "", ...assign, "", ...globals, ""].join("\n");
150
- }
151
- generateIndexContent(topLayer, allLayers) {
152
- const imports = allLayers.filter((layer) => layer.includes(topLayer)).sort((layer1, layer2) => {
153
- if (layer1.length !== layer2.length) return layer2.length - layer1.length;
154
- return layer1.localeCompare(layer2);
155
- }).map((layer) => `import './layer.${layer}.ts'`);
156
- if (this.options.globalLayerName && topLayer !== this.options.globalLayerName) {
157
- imports.unshift(`import './layer.${this.options.globalLayerName}.ts'`);
158
- }
159
- return [...imports].join("\n");
160
- }
161
- generateSetupContent(allLayers) {
162
- const layers = allLayers.toSorted((layer1, layer2) => {
163
- if (layer1.length !== layer2.length) return layer1.length - layer2.length;
164
- return layer1.localeCompare(layer2);
165
- });
166
- const vars = layers.map((layer) => {
167
- const layerName = this.getLayerName(layer, "camel");
168
- if (this.options.globalize) return `globalThis.${layerName} = {}`;
169
- return `const ${layerName} = {}`;
170
- });
171
- return [...vars].join("\n");
172
- }
173
- getLayer(path) {
174
- const name = basename(path);
175
- const layer = name.split(".").slice(1, -1).sort().join(".");
176
- if (layer) return layer;
177
- return this.options.defaultLayerName ?? "";
178
- }
179
- getLayerName(layer, style) {
180
- const LayerName = layer.split(".").map(this.capitalize).join("");
181
- if (style === "camel") return this.decapitalize(LayerName);
182
- if (style === "Pascal") return LayerName;
183
- throw this.never();
184
- }
185
- capitalize(string) {
186
- return string.charAt(0).toUpperCase() + string.slice(1);
187
- }
188
- decapitalize(string) {
189
- return string.charAt(0).toLowerCase() + string.slice(1);
190
- }
191
- isTopLayer(layer) {
192
- return !layer.includes(".");
193
- }
194
- async write(path, content) {
195
- const [prevContent] = await safe(() => readFile(path, "utf-8"));
196
- if (content === prevContent) return;
197
- await writeFile(path, content, "utf-8");
198
- }
199
- };
200
- async function paralayer(options) {
201
- const setupLayersJs = await new Paralayer(options).start();
202
- return setupLayersJs;
203
- }
204
-
205
- // src/bin.ts
206
8
  var argv = minimist(process.argv.slice(2), {
207
9
  string: ["defaultLayerName"],
208
10
  boolean: ["watch", "globalize"],
@@ -226,3 +28,4 @@ await paralayer({
226
28
  globalize: argv.globalize,
227
29
  defaultLayerName: argv.defaultLayerName
228
30
  });
31
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport minimist from 'minimist'\nimport { paralayer } from './paralayer.ts'\n\nconst argv = minimist(process.argv.slice(2), {\n string: ['defaultLayerName'],\n boolean: ['watch', 'globalize'],\n default: { watch: false, globalize: false, defaultLayerName: null },\n})\n\nconst paths = argv._\nconst input = paths.slice(0, -1)\nconst output = paths.at(-1)\n\nif (input.length === 0) {\n console.error('[paralayer] Input directory is not provided')\n process.exit(1)\n}\n\nif (!output) {\n console.error('[paralayer] Output directory is not provided')\n process.exit(1)\n}\n\nawait paralayer({\n input: input,\n output: output,\n watch: argv.watch,\n globalize: argv.globalize,\n defaultLayerName: argv.defaultLayerName,\n})\n"],"mappings":";;;;;;AAEA,OAAO,cAAc;AAGrB,IAAM,OAAO,SAAS,QAAQ,KAAK,MAAM,CAAC,GAAG;AAAA,EAC3C,QAAQ,CAAC,kBAAkB;AAAA,EAC3B,SAAS,CAAC,SAAS,WAAW;AAAA,EAC9B,SAAS,EAAE,OAAO,OAAO,WAAW,OAAO,kBAAkB,KAAK;AACpE,CAAC;AAED,IAAM,QAAQ,KAAK;AACnB,IAAM,QAAQ,MAAM,MAAM,GAAG,EAAE;AAC/B,IAAM,SAAS,MAAM,GAAG,EAAE;AAE1B,IAAI,MAAM,WAAW,GAAG;AACtB,UAAQ,MAAM,6CAA6C;AAC3D,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAI,CAAC,QAAQ;AACX,UAAQ,MAAM,8CAA8C;AAC5D,UAAQ,KAAK,CAAC;AAChB;AAEA,MAAM,UAAU;AAAA,EACd;AAAA,EACA;AAAA,EACA,OAAO,KAAK;AAAA,EACZ,WAAW,KAAK;AAAA,EAChB,kBAAkB,KAAK;AACzB,CAAC;","names":[]}
@@ -0,0 +1,206 @@
1
+ // src/paralayer.ts
2
+ import { watch } from "chokidar";
3
+ import { Queue, Unit, is, safe } from "dropcap/utils";
4
+ import { mkdir, readFile, rm, writeFile } from "fs/promises";
5
+ import { basename, extname, join, relative } from "path";
6
+ var Paralayer = class extends Unit {
7
+ files = {};
8
+ options;
9
+ started = false;
10
+ ready = false;
11
+ ready$ = Promise.withResolvers();
12
+ queue = new Queue();
13
+ extensions = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
14
+ previousLayers = [];
15
+ watcher = null;
16
+ constructor(options) {
17
+ super();
18
+ this.options = options;
19
+ }
20
+ async start() {
21
+ if (this.started) return;
22
+ this.started = true;
23
+ await safe(rm(this.options.output, { recursive: true }));
24
+ this.watcher = watch(this.options.input);
25
+ this.watcher.on("all", this.onAll);
26
+ this.watcher.on("ready", this.onReady);
27
+ await this.ready$.promise;
28
+ await this.queue.add(() => this.build());
29
+ if (!this.options.watch) await this.watcher.close();
30
+ const defineJsPath = join(this.options.output, "define.js");
31
+ return await readFile(defineJsPath, "utf-8");
32
+ }
33
+ // ---------------------------------------------------------------------------
34
+ // HANDLERS
35
+ // ---------------------------------------------------------------------------
36
+ onAll = async (event, path) => {
37
+ if (!["add", "change", "unlink"].includes(event)) return;
38
+ const ext = extname(path);
39
+ if (!this.extensions.has(ext)) return;
40
+ if (!this.getLayer(path)) return;
41
+ if (!this.ready) {
42
+ this.files[path] = null;
43
+ return;
44
+ }
45
+ if (event === "unlink") {
46
+ delete this.files[path];
47
+ await this.queue.add(() => this.build());
48
+ }
49
+ if (event === "add" || event === "change") {
50
+ this.files[path] = null;
51
+ await this.queue.add(() => this.build());
52
+ }
53
+ };
54
+ onReady = async () => {
55
+ this.ready = true;
56
+ this.ready$.resolve();
57
+ };
58
+ // ---------------------------------------------------------------------------
59
+ // BUILD
60
+ // ---------------------------------------------------------------------------
61
+ async build() {
62
+ const paths = Object.keys(this.files);
63
+ if (paths.length === 0) return;
64
+ const pathsByLayers = Object.groupBy(paths, (path) => this.getLayer(path));
65
+ const allLayers = Object.keys(pathsByLayers);
66
+ await mkdir(this.options.output, { recursive: true });
67
+ await Promise.all(
68
+ paths.map(async (path) => {
69
+ if (this.files[path]) return;
70
+ const content = await readFile(path, "utf-8");
71
+ const names = this.extractExportedClassNames(content);
72
+ this.files[path] = { content, names };
73
+ })
74
+ );
75
+ for (const layer in pathsByLayers) {
76
+ const layerFile = join(this.options.output, `layer.${layer}.ts`);
77
+ const layerPaths = pathsByLayers[layer].toSorted();
78
+ const layerContent = this.generateLayerContent(layer, layerPaths);
79
+ await this.write(layerFile, layerContent);
80
+ if (this.isTopLayer(layer)) {
81
+ const indexFile = join(this.options.output, `index.${layer}.ts`);
82
+ const indexContent = this.generateIndexContent(layer, allLayers);
83
+ await this.write(indexFile, indexContent);
84
+ }
85
+ }
86
+ const removedLayers = this.previousLayers.filter((l) => !allLayers.includes(l));
87
+ this.previousLayers = allLayers;
88
+ for (const layer of removedLayers) {
89
+ const layerFile = join(this.options.output, `layer.${layer}.ts`);
90
+ await rm(layerFile);
91
+ if (this.isTopLayer(layer)) {
92
+ const indexFile = join(this.options.output, `index.${layer}.ts`);
93
+ await rm(indexFile);
94
+ }
95
+ }
96
+ const defineFile = join(this.options.output, "define.js");
97
+ const defineContent = this.generateDefineContent(allLayers);
98
+ await this.write(defineFile, defineContent);
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // HELPERS
102
+ // ---------------------------------------------------------------------------
103
+ extractExportedClassNames(content) {
104
+ if (content.includes("paralayer-ignore")) return [];
105
+ return content.split(/^export class /m).slice(1).map((part) => part.split(" ")[0]?.split("<")[0]).filter(is.present);
106
+ }
107
+ generateLayerContent(layer, layerPaths) {
108
+ const layerName = this.getLayerName(layer, "camel");
109
+ const LayerName = this.getLayerName(layer, "Pascal");
110
+ const allNames = layerPaths.flatMap((path) => this.files[path]?.names ?? []);
111
+ const imports = layerPaths.map((path) => {
112
+ const file = this.files[path];
113
+ if (!file) return "";
114
+ if (file.names.length === 0) return "";
115
+ const names = file.names;
116
+ const types = file.names.map((name) => `type ${name} as ${name}Type`);
117
+ const relativePath = relative(this.options.output, path);
118
+ return `import { ${[...names, ...types].join(", ")} } from '${relativePath}'`;
119
+ }).filter(Boolean);
120
+ const assign = [`Object.assign(${layerName}, {`, ...allNames.map((name) => ` ${name},`), `})`];
121
+ let extendPascal = "";
122
+ let extendCamel = "";
123
+ if (this.options.globalLayerName && layer !== this.options.globalLayerName) {
124
+ extendPascal = `extends ${this.getLayerName(this.options.globalLayerName, "Pascal")} `;
125
+ extendCamel = `extends ${this.getLayerName(this.options.globalLayerName, "camel")} `;
126
+ }
127
+ const globals = [
128
+ `declare global {`,
129
+ ` const ${layerName}: ${LayerName}`,
130
+ ``,
131
+ ` interface ${LayerName} ${extendPascal}{`,
132
+ ...allNames.map((name) => ` ${name}: typeof ${name}`),
133
+ ` }`,
134
+ ``,
135
+ ` interface ${layerName} ${extendCamel}{`,
136
+ ...allNames.map((name) => ` ${name}: ${name}`),
137
+ ` }`,
138
+ ``,
139
+ ` namespace ${layerName} {`,
140
+ ...allNames.map((name) => ` export type ${name} = ${name}Type`),
141
+ ` }`,
142
+ `}`
143
+ ];
144
+ return [...imports, "", ...assign, "", ...globals, ""].join("\n");
145
+ }
146
+ generateIndexContent(topLayer, allLayers) {
147
+ const imports = allLayers.filter((layer) => layer.includes(topLayer)).sort((layer1, layer2) => {
148
+ if (layer1.length !== layer2.length) return layer2.length - layer1.length;
149
+ return layer1.localeCompare(layer2);
150
+ }).map((layer) => `import './layer.${layer}.ts'`);
151
+ if (this.options.globalLayerName && topLayer !== this.options.globalLayerName) {
152
+ imports.unshift(`import './layer.${this.options.globalLayerName}.ts'`);
153
+ }
154
+ return [...imports].join("\n");
155
+ }
156
+ generateDefineContent(allLayers) {
157
+ const layers = allLayers.toSorted((layer1, layer2) => {
158
+ if (layer1.length !== layer2.length) return layer1.length - layer2.length;
159
+ return layer1.localeCompare(layer2);
160
+ });
161
+ const vars = layers.map((layer) => {
162
+ const layerName = this.getLayerName(layer, "camel");
163
+ if (this.options.globalize) return `globalThis.${layerName} = {}`;
164
+ return `const ${layerName} = {}`;
165
+ });
166
+ return [...vars].join("\n");
167
+ }
168
+ getLayer(path) {
169
+ const name = basename(path);
170
+ const layer = name.split(".").slice(1, -1).sort().join(".");
171
+ if (layer) return layer;
172
+ return this.options.defaultLayerName ?? "";
173
+ }
174
+ getLayerName(layer, style) {
175
+ const LayerName = layer.split(".").map(this.capitalize).join("");
176
+ if (style === "camel") return this.decapitalize(LayerName);
177
+ if (style === "Pascal") return LayerName;
178
+ throw this.never();
179
+ }
180
+ capitalize(string) {
181
+ return string.charAt(0).toUpperCase() + string.slice(1);
182
+ }
183
+ decapitalize(string) {
184
+ return string.charAt(0).toLowerCase() + string.slice(1);
185
+ }
186
+ isTopLayer(layer) {
187
+ return !layer.includes(".");
188
+ }
189
+ async write(path, content) {
190
+ const [prevContent] = await safe(() => readFile(path, "utf-8"));
191
+ if (content === prevContent) return;
192
+ await writeFile(path, content, "utf-8");
193
+ }
194
+ };
195
+ async function paralayer(options) {
196
+ const defineLayersJs = await new Paralayer(options).start();
197
+ return defineLayersJs;
198
+ }
199
+ var paralayer_default = paralayer;
200
+
201
+ export {
202
+ Paralayer,
203
+ paralayer,
204
+ paralayer_default
205
+ };
206
+ //# sourceMappingURL=chunk-YSWJGCOE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/paralayer.ts"],"sourcesContent":["import { watch, type FSWatcher } from 'chokidar'\nimport { Queue, Unit, is, safe } from 'dropcap/utils'\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { basename, extname, join, relative } from 'node:path'\n\nexport type DirPath = string\n\nexport type File = {\n content: string\n names: string[]\n}\n\nexport type Options = {\n input: DirPath | DirPath[]\n output: DirPath\n watch?: boolean\n /** Whether the layer variables should be exposed globally. */\n globalize?: boolean\n /** If provided, other layers will extend this layer. */\n globalLayerName?: string | null\n /** If a file name does not have any layer tags, default layer name will be used. */\n defaultLayerName?: string | null\n}\n\nexport class Paralayer extends Unit {\n private files: { [path: string]: File | null } = {}\n private options: Options\n private started = false\n private ready = false\n private ready$ = Promise.withResolvers<void>()\n private queue = new Queue()\n private extensions = new Set(['.ts', '.tsx', '.js', '.jsx'])\n private previousLayers: string[] = []\n private watcher: FSWatcher | null = null\n\n constructor(options: Options) {\n super()\n this.options = options\n }\n\n async start() {\n if (this.started) return\n this.started = true\n\n await safe(rm(this.options.output, { recursive: true }))\n\n this.watcher = watch(this.options.input)\n this.watcher.on('all', this.onAll)\n this.watcher.on('ready', this.onReady)\n\n await this.ready$.promise\n await this.queue.add(() => this.build())\n if (!this.options.watch) await this.watcher.close()\n\n // Return content of define.js\n const defineJsPath = join(this.options.output, 'define.js')\n return await readFile(defineJsPath, 'utf-8')\n }\n\n // ---------------------------------------------------------------------------\n // HANDLERS\n // ---------------------------------------------------------------------------\n\n private onAll = async (event: string, path: string) => {\n // Process only file events\n if (!['add', 'change', 'unlink'].includes(event)) return\n\n // Not supported file extension? -> Ignore\n const ext = extname(path)\n if (!this.extensions.has(ext)) return\n\n // No layer in the file name? -> Ignore\n if (!this.getLayer(path)) return\n\n // Initial scan? -> Just register file\n if (!this.ready) {\n this.files[path] = null\n return\n }\n\n // File removed? -> Remove and rebuild\n if (event === 'unlink') {\n delete this.files[path]\n await this.queue.add(() => this.build())\n }\n\n // File added/changed? -> Reset its content and rebuild\n if (event === 'add' || event === 'change') {\n this.files[path] = null\n await this.queue.add(() => this.build())\n }\n }\n\n private onReady = async () => {\n this.ready = true\n this.ready$.resolve()\n }\n\n // ---------------------------------------------------------------------------\n // BUILD\n // ---------------------------------------------------------------------------\n\n private async build() {\n // Group file paths by layers\n const paths = Object.keys(this.files)\n if (paths.length === 0) return\n const pathsByLayers = Object.groupBy(paths, path => this.getLayer(path))\n const allLayers = Object.keys(pathsByLayers)\n\n // Ensure output directory exists\n await mkdir(this.options.output, { recursive: true })\n\n // Ensure all files are read\n await Promise.all(\n paths.map(async path => {\n if (this.files[path]) return\n const content = await readFile(path, 'utf-8')\n const names = this.extractExportedClassNames(content)\n this.files[path] = { content, names }\n }),\n )\n\n // Create layer files\n for (const layer in pathsByLayers) {\n // Generate layer.[layer].ts\n const layerFile = join(this.options.output, `layer.${layer}.ts`)\n const layerPaths = pathsByLayers[layer]!.toSorted()\n const layerContent = this.generateLayerContent(layer, layerPaths)\n await this.write(layerFile, layerContent)\n\n // Top layer? -> Generate index.[layer].ts\n if (this.isTopLayer(layer)) {\n const indexFile = join(this.options.output, `index.${layer}.ts`)\n const indexContent = this.generateIndexContent(layer, allLayers)\n await this.write(indexFile, indexContent)\n }\n }\n\n // Determine removed layers\n const removedLayers = this.previousLayers.filter(l => !allLayers.includes(l))\n this.previousLayers = allLayers\n\n // Delete files of removed layers\n for (const layer of removedLayers) {\n // Delete layer file\n const layerFile = join(this.options.output, `layer.${layer}.ts`)\n await rm(layerFile)\n\n // Top layer? -> Remove index file\n if (this.isTopLayer(layer)) {\n const indexFile = join(this.options.output, `index.${layer}.ts`)\n await rm(indexFile)\n }\n }\n\n // Generate define.js file\n const defineFile = join(this.options.output, 'define.js')\n const defineContent = this.generateDefineContent(allLayers)\n await this.write(defineFile, defineContent)\n }\n\n // ---------------------------------------------------------------------------\n // HELPERS\n // ---------------------------------------------------------------------------\n\n private extractExportedClassNames(content: string) {\n if (content.includes('paralayer-ignore')) return []\n return content\n .split(/^export class /m)\n .slice(1)\n .map(part => part.split(' ')[0]?.split('<')[0])\n .filter(is.present)\n }\n\n private generateLayerContent(layer: string, layerPaths: string[]) {\n const layerName = this.getLayerName(layer, 'camel')\n const LayerName = this.getLayerName(layer, 'Pascal')\n const allNames = layerPaths.flatMap(path => this.files[path]?.names ?? [])\n\n const imports = layerPaths\n .map(path => {\n const file = this.files[path]\n if (!file) return ''\n if (file.names.length === 0) return ''\n const names = file.names\n const types = file.names.map(name => `type ${name} as ${name}Type`)\n const relativePath = relative(this.options.output, path)\n return `import { ${[...names, ...types].join(', ')} } from '${relativePath}'`\n })\n .filter(Boolean)\n\n const assign = [`Object.assign(${layerName}, {`, ...allNames.map(name => ` ${name},`), `})`]\n\n let extendPascal = ''\n let extendCamel = ''\n if (this.options.globalLayerName && layer !== this.options.globalLayerName) {\n extendPascal = `extends ${this.getLayerName(this.options.globalLayerName, 'Pascal')} `\n extendCamel = `extends ${this.getLayerName(this.options.globalLayerName, 'camel')} `\n }\n\n const globals = [\n `declare global {`,\n ` const ${layerName}: ${LayerName}`,\n ``,\n ` interface ${LayerName} ${extendPascal}{`,\n ...allNames.map(name => ` ${name}: typeof ${name}`),\n ` }`,\n ``,\n ` interface ${layerName} ${extendCamel}{`,\n ...allNames.map(name => ` ${name}: ${name}`),\n ` }`,\n ``,\n ` namespace ${layerName} {`,\n ...allNames.map(name => ` export type ${name} = ${name}Type`),\n ` }`,\n `}`,\n ]\n\n return [...imports, '', ...assign, '', ...globals, ''].join('\\n')\n }\n\n private generateIndexContent(topLayer: string, allLayers: string[]) {\n const imports = allLayers\n .filter(layer => layer.includes(topLayer))\n .sort((layer1, layer2) => {\n if (layer1.length !== layer2.length) return layer2.length - layer1.length\n return layer1.localeCompare(layer2)\n })\n .map(layer => `import './layer.${layer}.ts'`)\n\n if (this.options.globalLayerName && topLayer !== this.options.globalLayerName) {\n imports.unshift(`import './layer.${this.options.globalLayerName}.ts'`)\n }\n\n return [...imports].join('\\n')\n }\n\n private generateDefineContent(allLayers: string[]) {\n const layers = allLayers.toSorted((layer1, layer2) => {\n if (layer1.length !== layer2.length) return layer1.length - layer2.length\n return layer1.localeCompare(layer2)\n })\n\n const vars = layers.map(layer => {\n const layerName = this.getLayerName(layer, 'camel')\n if (this.options.globalize) return `globalThis.${layerName} = {}`\n return `const ${layerName} = {}`\n })\n\n return [...vars].join('\\n')\n }\n\n private getLayer(path: string) {\n const name = basename(path)\n const layer = name.split('.').slice(1, -1).sort().join('.')\n if (layer) return layer\n return this.options.defaultLayerName ?? ''\n }\n\n private getLayerName(layer: string, style: 'camel' | 'Pascal') {\n const LayerName = layer.split('.').map(this.capitalize).join('')\n if (style === 'camel') return this.decapitalize(LayerName)\n if (style === 'Pascal') return LayerName\n throw this.never()\n }\n\n private capitalize(string: string) {\n return string.charAt(0).toUpperCase() + string.slice(1)\n }\n\n private decapitalize(string: string) {\n return string.charAt(0).toLowerCase() + string.slice(1)\n }\n\n private isTopLayer(layer: string) {\n return !layer.includes('.')\n }\n\n private async write(path: string, content: string) {\n const [prevContent] = await safe(() => readFile(path, 'utf-8'))\n if (content === prevContent) return\n await writeFile(path, content, 'utf-8')\n }\n}\n\nexport async function paralayer(options: Options) {\n const defineLayersJs = await new Paralayer(options).start()\n return defineLayersJs\n}\n\nexport default paralayer\n"],"mappings":"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","names":[]}
@@ -34,7 +34,7 @@ declare class Paralayer extends Unit {
34
34
  private extractExportedClassNames;
35
35
  private generateLayerContent;
36
36
  private generateIndexContent;
37
- private generateSetupContent;
37
+ private generateDefineContent;
38
38
  private getLayer;
39
39
  private getLayerName;
40
40
  private capitalize;
package/dist/paralayer.js CHANGED
@@ -1,204 +1,11 @@
1
- // src/paralayer.ts
2
- import { watch } from "chokidar";
3
- import { Queue, Unit, safe } from "dropcap/utils";
4
- import { mkdir, readFile, rm, writeFile } from "fs/promises";
5
- import { basename, extname, join, relative } from "path";
6
- var Paralayer = class extends Unit {
7
- files = {};
8
- options;
9
- started = false;
10
- ready = false;
11
- ready$ = Promise.withResolvers();
12
- queue = new Queue();
13
- extensions = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
14
- previousLayers = [];
15
- watcher = null;
16
- constructor(options) {
17
- super();
18
- this.options = options;
19
- }
20
- async start() {
21
- if (this.started) return;
22
- this.started = true;
23
- await safe(rm(this.options.output, { recursive: true }));
24
- this.watcher = watch(this.options.input);
25
- this.watcher.on("all", this.onAll);
26
- this.watcher.on("ready", this.onReady);
27
- await this.ready$.promise;
28
- await this.queue.add(() => this.build());
29
- if (!this.options.watch) await this.watcher.close();
30
- const setupJsPath = join(this.options.output, "setup.js");
31
- return await readFile(setupJsPath, "utf-8");
32
- }
33
- // ---------------------------------------------------------------------------
34
- // HANDLERS
35
- // ---------------------------------------------------------------------------
36
- onAll = async (event, path) => {
37
- if (!["add", "change", "unlink"].includes(event)) return;
38
- const ext = extname(path);
39
- if (!this.extensions.has(ext)) return;
40
- if (!this.getLayer(path)) return;
41
- if (!this.ready) {
42
- this.files[path] = null;
43
- return;
44
- }
45
- if (event === "unlink") {
46
- delete this.files[path];
47
- await this.queue.add(() => this.build());
48
- }
49
- if (event === "add" || event === "change") {
50
- this.files[path] = null;
51
- await this.queue.add(() => this.build());
52
- }
53
- };
54
- onReady = async () => {
55
- this.ready = true;
56
- this.ready$.resolve();
57
- };
58
- // ---------------------------------------------------------------------------
59
- // BUILD
60
- // ---------------------------------------------------------------------------
61
- async build() {
62
- const paths = Object.keys(this.files);
63
- if (paths.length === 0) return;
64
- const pathsByLayers = Object.groupBy(paths, (path) => this.getLayer(path));
65
- const allLayers = Object.keys(pathsByLayers);
66
- await mkdir(this.options.output, { recursive: true });
67
- await Promise.all(
68
- paths.map(async (path) => {
69
- if (this.files[path]) return;
70
- const content = await readFile(path, "utf-8");
71
- const names = this.extractExportedClassNames(content);
72
- this.files[path] = { content, names };
73
- })
74
- );
75
- for (const layer in pathsByLayers) {
76
- const layerFile = join(this.options.output, `layer.${layer}.ts`);
77
- const layerPaths = pathsByLayers[layer].toSorted();
78
- const layerContent = this.generateLayerContent(layer, layerPaths);
79
- await this.write(layerFile, layerContent);
80
- if (this.isTopLayer(layer)) {
81
- const indexFile = join(this.options.output, `index.${layer}.ts`);
82
- const indexContent = this.generateIndexContent(layer, allLayers);
83
- await this.write(indexFile, indexContent);
84
- }
85
- }
86
- const removedLayers = this.previousLayers.filter((l) => !allLayers.includes(l));
87
- this.previousLayers = allLayers;
88
- for (const layer of removedLayers) {
89
- const layerFile = join(this.options.output, `layer.${layer}.ts`);
90
- await rm(layerFile);
91
- if (this.isTopLayer(layer)) {
92
- const indexFile = join(this.options.output, `index.${layer}.ts`);
93
- await rm(indexFile);
94
- }
95
- }
96
- const setupFile = join(this.options.output, "setup.js");
97
- const setupContent = this.generateSetupContent(allLayers);
98
- await this.write(setupFile, setupContent);
99
- }
100
- // ---------------------------------------------------------------------------
101
- // HELPERS
102
- // ---------------------------------------------------------------------------
103
- extractExportedClassNames(content) {
104
- if (content.includes("paralayer-ignore")) return [];
105
- return content.split("export class ").slice(1).map((part) => part.split(" ")[0].split("<")[0]);
106
- }
107
- generateLayerContent(layer, layerPaths) {
108
- const layerName = this.getLayerName(layer, "camel");
109
- const LayerName = this.getLayerName(layer, "Pascal");
110
- const allNames = layerPaths.flatMap((path) => this.files[path]?.names ?? []);
111
- const imports = layerPaths.map((path) => {
112
- const file = this.files[path];
113
- if (!file) return "";
114
- if (file.names.length === 0) return "";
115
- const names = file.names;
116
- const types = file.names.map((name) => `type ${name} as ${name}Type`);
117
- const relativePath = relative(this.options.output, path);
118
- return `import { ${[...names, ...types].join(", ")} } from '${relativePath}'`;
119
- }).filter(Boolean);
120
- const assign = [`Object.assign(${layerName}, {`, ...allNames.map((name) => ` ${name},`), `})`];
121
- let extendPascal = "";
122
- let extendCamel = "";
123
- if (this.options.globalLayerName && layer !== this.options.globalLayerName) {
124
- extendPascal = `extends ${this.getLayerName(this.options.globalLayerName, "Pascal")} `;
125
- extendCamel = `extends ${this.getLayerName(this.options.globalLayerName, "camel")} `;
126
- }
127
- const globals = [
128
- `declare global {`,
129
- ` const ${layerName}: ${LayerName}`,
130
- ``,
131
- ` interface ${LayerName} ${extendPascal}{`,
132
- ...allNames.map((name) => ` ${name}: typeof ${name}`),
133
- ` }`,
134
- ``,
135
- ` interface ${layerName} ${extendCamel}{`,
136
- ...allNames.map((name) => ` ${name}: ${name}`),
137
- ` }`,
138
- ``,
139
- ` namespace ${layerName} {`,
140
- ...allNames.map((name) => ` export type ${name} = ${name}Type`),
141
- ` }`,
142
- `}`
143
- ];
144
- return [...imports, "", ...assign, "", ...globals, ""].join("\n");
145
- }
146
- generateIndexContent(topLayer, allLayers) {
147
- const imports = allLayers.filter((layer) => layer.includes(topLayer)).sort((layer1, layer2) => {
148
- if (layer1.length !== layer2.length) return layer2.length - layer1.length;
149
- return layer1.localeCompare(layer2);
150
- }).map((layer) => `import './layer.${layer}.ts'`);
151
- if (this.options.globalLayerName && topLayer !== this.options.globalLayerName) {
152
- imports.unshift(`import './layer.${this.options.globalLayerName}.ts'`);
153
- }
154
- return [...imports].join("\n");
155
- }
156
- generateSetupContent(allLayers) {
157
- const layers = allLayers.toSorted((layer1, layer2) => {
158
- if (layer1.length !== layer2.length) return layer1.length - layer2.length;
159
- return layer1.localeCompare(layer2);
160
- });
161
- const vars = layers.map((layer) => {
162
- const layerName = this.getLayerName(layer, "camel");
163
- if (this.options.globalize) return `globalThis.${layerName} = {}`;
164
- return `const ${layerName} = {}`;
165
- });
166
- return [...vars].join("\n");
167
- }
168
- getLayer(path) {
169
- const name = basename(path);
170
- const layer = name.split(".").slice(1, -1).sort().join(".");
171
- if (layer) return layer;
172
- return this.options.defaultLayerName ?? "";
173
- }
174
- getLayerName(layer, style) {
175
- const LayerName = layer.split(".").map(this.capitalize).join("");
176
- if (style === "camel") return this.decapitalize(LayerName);
177
- if (style === "Pascal") return LayerName;
178
- throw this.never();
179
- }
180
- capitalize(string) {
181
- return string.charAt(0).toUpperCase() + string.slice(1);
182
- }
183
- decapitalize(string) {
184
- return string.charAt(0).toLowerCase() + string.slice(1);
185
- }
186
- isTopLayer(layer) {
187
- return !layer.includes(".");
188
- }
189
- async write(path, content) {
190
- const [prevContent] = await safe(() => readFile(path, "utf-8"));
191
- if (content === prevContent) return;
192
- await writeFile(path, content, "utf-8");
193
- }
194
- };
195
- async function paralayer(options) {
196
- const setupLayersJs = await new Paralayer(options).start();
197
- return setupLayersJs;
198
- }
199
- var paralayer_default = paralayer;
1
+ import {
2
+ Paralayer,
3
+ paralayer,
4
+ paralayer_default
5
+ } from "./chunk-YSWJGCOE.js";
200
6
  export {
201
7
  Paralayer,
202
8
  paralayer_default as default,
203
9
  paralayer
204
10
  };
11
+ //# sourceMappingURL=paralayer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paralayer",
3
- "version": "1.11.0",
3
+ "version": "1.13.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "imkost",
@@ -19,22 +19,14 @@
19
19
  "paralayer": "./src/bin.js"
20
20
  },
21
21
  "exports": {
22
- ".": {
23
- "source": "./src/paralayer.ts",
24
- "import": "./dist/paralayer.js",
25
- "types": "./dist/paralayer.d.ts"
26
- },
27
- "./ts": {
28
- "import": "./src/paralayer.ts"
29
- }
22
+ ".": "./dist/paralayer.js"
30
23
  },
31
24
  "files": [
32
- "src",
33
25
  "dist"
34
26
  ],
35
27
  "dependencies": {
36
28
  "chokidar": "^5.0.0",
37
- "dropcap": "^1.0.0",
29
+ "dropcap": "^1.4.0",
38
30
  "minimist": "^1.2.8"
39
31
  },
40
32
  "devDependencies": {
package/src/bin.ts DELETED
@@ -1,32 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import minimist from 'minimist'
4
- import { paralayer } from './paralayer.ts'
5
-
6
- const argv = minimist(process.argv.slice(2), {
7
- string: ['defaultLayerName'],
8
- boolean: ['watch', 'globalize'],
9
- default: { watch: false, globalize: false, defaultLayerName: null },
10
- })
11
-
12
- const paths = argv._
13
- const input = paths.slice(0, -1)
14
- const output = paths.at(-1)
15
-
16
- if (input.length === 0) {
17
- console.error('[paralayer] Input directory is not provided')
18
- process.exit(1)
19
- }
20
-
21
- if (!output) {
22
- console.error('[paralayer] Output directory is not provided')
23
- process.exit(1)
24
- }
25
-
26
- await paralayer({
27
- input: input,
28
- output: output,
29
- watch: argv.watch,
30
- globalize: argv.globalize,
31
- defaultLayerName: argv.defaultLayerName,
32
- })
package/src/paralayer.ts DELETED
@@ -1,290 +0,0 @@
1
- import { watch, type FSWatcher } from 'chokidar'
2
- import { Queue, Unit, safe } from 'dropcap/utils'
3
- import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
4
- import { basename, extname, join, relative } from 'node:path'
5
-
6
- export type DirPath = string
7
-
8
- export type File = {
9
- content: string
10
- names: string[]
11
- }
12
-
13
- export type Options = {
14
- input: DirPath | DirPath[]
15
- output: DirPath
16
- watch?: boolean
17
- /** Whether the layer variables should be exposed globally. */
18
- globalize?: boolean
19
- /** If provided, other layers will extend this layer. */
20
- globalLayerName?: string | null
21
- /** If a file name does not have any layer tags, default layer name will be used. */
22
- defaultLayerName?: string | null
23
- }
24
-
25
- export class Paralayer extends Unit {
26
- private files: { [path: string]: File | null } = {}
27
- private options: Options
28
- private started = false
29
- private ready = false
30
- private ready$ = Promise.withResolvers<void>()
31
- private queue = new Queue()
32
- private extensions = new Set(['.ts', '.tsx', '.js', '.jsx'])
33
- private previousLayers: string[] = []
34
- private watcher: FSWatcher | null = null
35
-
36
- constructor(options: Options) {
37
- super()
38
- this.options = options
39
- }
40
-
41
- async start() {
42
- if (this.started) return
43
- this.started = true
44
-
45
- await safe(rm(this.options.output, { recursive: true }))
46
-
47
- this.watcher = watch(this.options.input)
48
- this.watcher.on('all', this.onAll)
49
- this.watcher.on('ready', this.onReady)
50
-
51
- await this.ready$.promise
52
- await this.queue.add(() => this.build())
53
- if (!this.options.watch) await this.watcher.close()
54
-
55
- // Return content of setup.js
56
- const setupJsPath = join(this.options.output, 'setup.js')
57
- return await readFile(setupJsPath, 'utf-8')
58
- }
59
-
60
- // ---------------------------------------------------------------------------
61
- // HANDLERS
62
- // ---------------------------------------------------------------------------
63
-
64
- private onAll = async (event: string, path: string) => {
65
- // Process only file events
66
- if (!['add', 'change', 'unlink'].includes(event)) return
67
-
68
- // Not supported file extension? -> Ignore
69
- const ext = extname(path)
70
- if (!this.extensions.has(ext)) return
71
-
72
- // No layer in the file name? -> Ignore
73
- if (!this.getLayer(path)) return
74
-
75
- // Initial scan? -> Just register file
76
- if (!this.ready) {
77
- this.files[path] = null
78
- return
79
- }
80
-
81
- // File removed? -> Remove and rebuild
82
- if (event === 'unlink') {
83
- delete this.files[path]
84
- await this.queue.add(() => this.build())
85
- }
86
-
87
- // File added/changed? -> Reset its content and rebuild
88
- if (event === 'add' || event === 'change') {
89
- this.files[path] = null
90
- await this.queue.add(() => this.build())
91
- }
92
- }
93
-
94
- private onReady = async () => {
95
- this.ready = true
96
- this.ready$.resolve()
97
- }
98
-
99
- // ---------------------------------------------------------------------------
100
- // BUILD
101
- // ---------------------------------------------------------------------------
102
-
103
- private async build() {
104
- // Group file paths by layers
105
- const paths = Object.keys(this.files)
106
- if (paths.length === 0) return
107
- const pathsByLayers = Object.groupBy(paths, path => this.getLayer(path))
108
- const allLayers = Object.keys(pathsByLayers)
109
-
110
- // Ensure output directory exists
111
- await mkdir(this.options.output, { recursive: true })
112
-
113
- // Ensure all files are read
114
- await Promise.all(
115
- paths.map(async path => {
116
- if (this.files[path]) return
117
- const content = await readFile(path, 'utf-8')
118
- const names = this.extractExportedClassNames(content)
119
- this.files[path] = { content, names }
120
- }),
121
- )
122
-
123
- // Create layer files
124
- for (const layer in pathsByLayers) {
125
- // Generate layer.[layer].ts
126
- const layerFile = join(this.options.output, `layer.${layer}.ts`)
127
- const layerPaths = pathsByLayers[layer]!.toSorted()
128
- const layerContent = this.generateLayerContent(layer, layerPaths)
129
- await this.write(layerFile, layerContent)
130
-
131
- // Top layer? -> Generate index.[layer].ts
132
- if (this.isTopLayer(layer)) {
133
- const indexFile = join(this.options.output, `index.${layer}.ts`)
134
- const indexContent = this.generateIndexContent(layer, allLayers)
135
- await this.write(indexFile, indexContent)
136
- }
137
- }
138
-
139
- // Determine removed layers
140
- const removedLayers = this.previousLayers.filter(l => !allLayers.includes(l))
141
- this.previousLayers = allLayers
142
-
143
- // Delete files of removed layers
144
- for (const layer of removedLayers) {
145
- // Delete layer file
146
- const layerFile = join(this.options.output, `layer.${layer}.ts`)
147
- await rm(layerFile)
148
-
149
- // Top layer? -> Remove index file
150
- if (this.isTopLayer(layer)) {
151
- const indexFile = join(this.options.output, `index.${layer}.ts`)
152
- await rm(indexFile)
153
- }
154
- }
155
-
156
- // Generate setup.js file
157
- const setupFile = join(this.options.output, 'setup.js')
158
- const setupContent = this.generateSetupContent(allLayers)
159
- await this.write(setupFile, setupContent)
160
- }
161
-
162
- // ---------------------------------------------------------------------------
163
- // HELPERS
164
- // ---------------------------------------------------------------------------
165
-
166
- private extractExportedClassNames(content: string) {
167
- if (content.includes('paralayer-ignore')) return []
168
- return content
169
- .split('export class ')
170
- .slice(1)
171
- .map(part => part.split(' ')[0].split('<')[0])
172
- }
173
-
174
- private generateLayerContent(layer: string, layerPaths: string[]) {
175
- const layerName = this.getLayerName(layer, 'camel')
176
- const LayerName = this.getLayerName(layer, 'Pascal')
177
- const allNames = layerPaths.flatMap(path => this.files[path]?.names ?? [])
178
-
179
- const imports = layerPaths
180
- .map(path => {
181
- const file = this.files[path]
182
- if (!file) return ''
183
- if (file.names.length === 0) return ''
184
- const names = file.names
185
- const types = file.names.map(name => `type ${name} as ${name}Type`)
186
- const relativePath = relative(this.options.output, path)
187
- return `import { ${[...names, ...types].join(', ')} } from '${relativePath}'`
188
- })
189
- .filter(Boolean)
190
-
191
- const assign = [`Object.assign(${layerName}, {`, ...allNames.map(name => ` ${name},`), `})`]
192
-
193
- let extendPascal = ''
194
- let extendCamel = ''
195
- if (this.options.globalLayerName && layer !== this.options.globalLayerName) {
196
- extendPascal = `extends ${this.getLayerName(this.options.globalLayerName, 'Pascal')} `
197
- extendCamel = `extends ${this.getLayerName(this.options.globalLayerName, 'camel')} `
198
- }
199
-
200
- const globals = [
201
- `declare global {`,
202
- ` const ${layerName}: ${LayerName}`,
203
- ``,
204
- ` interface ${LayerName} ${extendPascal}{`,
205
- ...allNames.map(name => ` ${name}: typeof ${name}`),
206
- ` }`,
207
- ``,
208
- ` interface ${layerName} ${extendCamel}{`,
209
- ...allNames.map(name => ` ${name}: ${name}`),
210
- ` }`,
211
- ``,
212
- ` namespace ${layerName} {`,
213
- ...allNames.map(name => ` export type ${name} = ${name}Type`),
214
- ` }`,
215
- `}`,
216
- ]
217
-
218
- return [...imports, '', ...assign, '', ...globals, ''].join('\n')
219
- }
220
-
221
- private generateIndexContent(topLayer: string, allLayers: string[]) {
222
- const imports = allLayers
223
- .filter(layer => layer.includes(topLayer))
224
- .sort((layer1, layer2) => {
225
- if (layer1.length !== layer2.length) return layer2.length - layer1.length
226
- return layer1.localeCompare(layer2)
227
- })
228
- .map(layer => `import './layer.${layer}.ts'`)
229
-
230
- if (this.options.globalLayerName && topLayer !== this.options.globalLayerName) {
231
- imports.unshift(`import './layer.${this.options.globalLayerName}.ts'`)
232
- }
233
-
234
- return [...imports].join('\n')
235
- }
236
-
237
- private generateSetupContent(allLayers: string[]) {
238
- const layers = allLayers.toSorted((layer1, layer2) => {
239
- if (layer1.length !== layer2.length) return layer1.length - layer2.length
240
- return layer1.localeCompare(layer2)
241
- })
242
-
243
- const vars = layers.map(layer => {
244
- const layerName = this.getLayerName(layer, 'camel')
245
- if (this.options.globalize) return `globalThis.${layerName} = {}`
246
- return `const ${layerName} = {}`
247
- })
248
-
249
- return [...vars].join('\n')
250
- }
251
-
252
- private getLayer(path: string) {
253
- const name = basename(path)
254
- const layer = name.split('.').slice(1, -1).sort().join('.')
255
- if (layer) return layer
256
- return this.options.defaultLayerName ?? ''
257
- }
258
-
259
- private getLayerName(layer: string, style: 'camel' | 'Pascal') {
260
- const LayerName = layer.split('.').map(this.capitalize).join('')
261
- if (style === 'camel') return this.decapitalize(LayerName)
262
- if (style === 'Pascal') return LayerName
263
- throw this.never()
264
- }
265
-
266
- private capitalize(string: string) {
267
- return string.charAt(0).toUpperCase() + string.slice(1)
268
- }
269
-
270
- private decapitalize(string: string) {
271
- return string.charAt(0).toLowerCase() + string.slice(1)
272
- }
273
-
274
- private isTopLayer(layer: string) {
275
- return !layer.includes('.')
276
- }
277
-
278
- private async write(path: string, content: string) {
279
- const [prevContent] = await safe(() => readFile(path, 'utf-8'))
280
- if (content === prevContent) return
281
- await writeFile(path, content, 'utf-8')
282
- }
283
- }
284
-
285
- export async function paralayer(options: Options) {
286
- const setupLayersJs = await new Paralayer(options).start()
287
- return setupLayersJs
288
- }
289
-
290
- export default paralayer