paralayer 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/bin.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ paralayer
4
+ } from "./chunk-BES3GT56.js";
5
+
6
+ // src/bin.ts
7
+ import minimist from "minimist";
8
+ var argv = minimist(process.argv.slice(2), {
9
+ string: ["defaultLayerName"],
10
+ boolean: ["watch", "globalize"],
11
+ default: { watch: false, globalize: false, defaultLayerName: null }
12
+ });
13
+ var paths = argv._;
14
+ var input = paths.slice(0, -1);
15
+ var output = paths.at(-1);
16
+ if (input.length === 0) {
17
+ console.error("[paralayer] Input directory is not provided");
18
+ process.exit(1);
19
+ }
20
+ if (!output) {
21
+ console.error("[paralayer] Output directory is not provided");
22
+ process.exit(1);
23
+ }
24
+ await paralayer({
25
+ input,
26
+ output,
27
+ watch: argv.watch,
28
+ globalize: argv.globalize,
29
+ defaultLayerName: argv.defaultLayerName
30
+ });
@@ -0,0 +1,195 @@
1
+ // src/paralayer.ts
2
+ import { Queue, safe, Unit } from "@eposlabs/utils";
3
+ import { watch } from "chokidar";
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.run(() => this.build());
29
+ if (!this.options.watch) await this.watcher.close();
30
+ }
31
+ async readSetupJs() {
32
+ const setupJsPath = join(this.options.output, "setup.js");
33
+ return await readFile(setupJsPath, "utf-8");
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // HANDLERS
37
+ // ---------------------------------------------------------------------------
38
+ onAll = async (event, path) => {
39
+ if (!["add", "change", "unlink"].includes(event)) return;
40
+ const ext = extname(path);
41
+ if (!this.extensions.has(ext)) return;
42
+ if (!this.getLayer(path)) return;
43
+ if (!this.ready) {
44
+ this.files[path] = null;
45
+ return;
46
+ }
47
+ if (event === "unlink") {
48
+ delete this.files[path];
49
+ await this.queue.run(() => this.build());
50
+ }
51
+ if (event === "add" || event === "change") {
52
+ this.files[path] = null;
53
+ await this.queue.run(() => this.build());
54
+ }
55
+ };
56
+ onReady = async () => {
57
+ this.ready = true;
58
+ this.ready$.resolve();
59
+ };
60
+ // ---------------------------------------------------------------------------
61
+ // BUILD
62
+ // ---------------------------------------------------------------------------
63
+ async build() {
64
+ const paths = Object.keys(this.files);
65
+ if (paths.length === 0) return;
66
+ const pathsByLayers = Object.groupBy(paths, (path) => this.getLayer(path));
67
+ const allLayers = Object.keys(pathsByLayers);
68
+ await mkdir(this.options.output, { recursive: true });
69
+ await Promise.all(
70
+ paths.map(async (path) => {
71
+ if (this.files[path]) return;
72
+ const content = await readFile(path, "utf-8");
73
+ const names = this.extractExportedClassNames(content);
74
+ this.files[path] = { content, names };
75
+ })
76
+ );
77
+ for (const layer in pathsByLayers) {
78
+ const layerFile = join(this.options.output, `layer.${layer}.ts`);
79
+ const layerPaths = pathsByLayers[layer].toSorted();
80
+ const layerContent = this.generateLayerContent(layer, layerPaths);
81
+ await this.write(layerFile, layerContent);
82
+ if (this.isTopLayer(layer)) {
83
+ const indexFile = join(this.options.output, `index.${layer}.ts`);
84
+ const indexContent = this.generateIndexContent(layer, allLayers);
85
+ await this.write(indexFile, indexContent);
86
+ }
87
+ }
88
+ const removedLayers = this.previousLayers.filter((l) => !allLayers.includes(l));
89
+ this.previousLayers = allLayers;
90
+ for (const layer of removedLayers) {
91
+ const layerFile = join(this.options.output, `layer.${layer}.ts`);
92
+ await rm(layerFile);
93
+ if (this.isTopLayer(layer)) {
94
+ const indexFile = join(this.options.output, `index.${layer}.ts`);
95
+ await rm(indexFile);
96
+ }
97
+ }
98
+ const setupFile = join(this.options.output, "setup.js");
99
+ const setupContent = this.generateSetupContent(allLayers);
100
+ await this.write(setupFile, setupContent);
101
+ }
102
+ // ---------------------------------------------------------------------------
103
+ // HELPERS
104
+ // ---------------------------------------------------------------------------
105
+ extractExportedClassNames(content) {
106
+ if (content.includes("paralayer-ignore")) return [];
107
+ return content.split("export class ").slice(1).map((part) => part.split(" ")[0].split("<")[0]);
108
+ }
109
+ generateLayerContent(layer, layerPaths) {
110
+ const $LayerName = this.getLayerName(layer, "$Pascal");
111
+ const $layerName = this.getLayerName(layer, "$camel");
112
+ const allNames = layerPaths.flatMap((path) => this.files[path]?.names ?? []);
113
+ const imports = layerPaths.map((path) => {
114
+ const file = this.files[path];
115
+ if (!file) return "";
116
+ if (file.names.length === 0) return "";
117
+ const names = file.names;
118
+ const types = file.names.map((name) => `type ${name} as ${name}Type`);
119
+ const relativePath = relative(this.options.output, path);
120
+ return `import { ${[...names, ...types].join(", ")} } from '${relativePath}'`;
121
+ }).filter(Boolean);
122
+ const assign = [`Object.assign(${$layerName}, {`, ...allNames.map((name) => ` ${name},`), `})`];
123
+ const globals = [
124
+ `declare global {`,
125
+ ` var ${$layerName}: ${$LayerName}`,
126
+ ``,
127
+ ` interface ${$LayerName} {`,
128
+ ...allNames.map((name) => ` ${name}: typeof ${name}`),
129
+ ` }`,
130
+ ``,
131
+ ` namespace ${$layerName} {`,
132
+ ...allNames.map((name) => ` export type ${name} = ${name}Type`),
133
+ ` }`,
134
+ `}`
135
+ ];
136
+ return [...imports, "", ...assign, "", ...globals, ""].join("\n");
137
+ }
138
+ generateIndexContent(topLayer, allLayers) {
139
+ const imports = allLayers.filter((layer) => layer.includes(topLayer)).sort((layer1, layer2) => {
140
+ if (layer1.length !== layer2.length) return layer2.length - layer1.length;
141
+ return layer1.localeCompare(layer2);
142
+ }).map((layer) => `import './layer.${layer}.ts'`);
143
+ return [...imports].join("\n");
144
+ }
145
+ generateSetupContent(allLayers) {
146
+ const layers = allLayers.toSorted((layer1, layer2) => {
147
+ if (layer1.length !== layer2.length) return layer1.length - layer2.length;
148
+ return layer1.localeCompare(layer2);
149
+ });
150
+ const vars = layers.map((layer) => {
151
+ const $layerName = this.getLayerName(layer, "$camel");
152
+ if (this.options.globalize) return `globalThis.${$layerName} = {}`;
153
+ return `const ${$layerName} = {}`;
154
+ });
155
+ return [...vars].join("\n");
156
+ }
157
+ getLayer(path) {
158
+ const name = basename(path);
159
+ const layer = name.split(".").slice(1, -1).sort().join(".");
160
+ if (layer) return layer;
161
+ return this.options.defaultLayerName ?? "";
162
+ }
163
+ getLayerName(layer, style) {
164
+ const LayerName = layer.split(".").map(this.capitalize).join("");
165
+ if (style === "$camel") return `$${this.decapitalize(LayerName)}`;
166
+ if (style === "$Pascal") return `$${LayerName}`;
167
+ throw this.never;
168
+ }
169
+ capitalize(string) {
170
+ return string.charAt(0).toUpperCase() + string.slice(1);
171
+ }
172
+ decapitalize(string) {
173
+ return string.charAt(0).toLowerCase() + string.slice(1);
174
+ }
175
+ isTopLayer(layer) {
176
+ return !layer.includes(".");
177
+ }
178
+ async write(path, content) {
179
+ const [prevContent] = await safe(() => readFile(path, "utf-8"));
180
+ if (content === prevContent) return;
181
+ await writeFile(path, content, "utf-8");
182
+ }
183
+ };
184
+ async function paralayer(options) {
185
+ const pl = new Paralayer(options);
186
+ await pl.start();
187
+ return await pl.readSetupJs();
188
+ }
189
+ var paralayer_default = paralayer;
190
+
191
+ export {
192
+ Paralayer,
193
+ paralayer,
194
+ paralayer_default
195
+ };
@@ -0,0 +1,46 @@
1
+ import { Unit } from '@eposlabs/utils';
2
+
3
+ type DirPath = string;
4
+ type File = {
5
+ content: string;
6
+ names: string[];
7
+ };
8
+ type Options = {
9
+ input: DirPath | DirPath[];
10
+ output: DirPath;
11
+ watch?: boolean;
12
+ /** Whether the layer variables should be exposed globally. */
13
+ globalize?: boolean;
14
+ /** If a file name does not have layer tags, default layer name will be used. */
15
+ defaultLayerName?: string | null;
16
+ };
17
+ declare class Paralayer extends Unit {
18
+ private files;
19
+ private options;
20
+ private started;
21
+ private ready;
22
+ private ready$;
23
+ private queue;
24
+ private extensions;
25
+ private previousLayers;
26
+ private watcher;
27
+ constructor(options: Options);
28
+ start(): Promise<void>;
29
+ readSetupJs(): Promise<string>;
30
+ private onAll;
31
+ private onReady;
32
+ private build;
33
+ private extractExportedClassNames;
34
+ private generateLayerContent;
35
+ private generateIndexContent;
36
+ private generateSetupContent;
37
+ private getLayer;
38
+ private getLayerName;
39
+ private capitalize;
40
+ private decapitalize;
41
+ private isTopLayer;
42
+ private write;
43
+ }
44
+ declare function paralayer(options: Options): Promise<string>;
45
+
46
+ export { type DirPath, type File, type Options, Paralayer, paralayer as default, paralayer };
@@ -0,0 +1,10 @@
1
+ import {
2
+ Paralayer,
3
+ paralayer,
4
+ paralayer_default
5
+ } from "./chunk-BES3GT56.js";
6
+ export {
7
+ Paralayer,
8
+ paralayer_default as default,
9
+ paralayer
10
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paralayer",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "imkost",
@@ -10,8 +10,7 @@
10
10
  "vite-plugin"
11
11
  ],
12
12
  "scripts": {
13
- "dev": "rm -rf dist && tsc --watch",
14
- "build": "rm -rf dist && tsc",
13
+ "build": "tsup src --format esm --dts --clean",
15
14
  "lint": "tsc --noEmit",
16
15
  "release": "sh -c 'npm version ${1:-patch} && npm run build && npm publish' --"
17
16
  },
@@ -20,14 +19,18 @@
20
19
  },
21
20
  "exports": {
22
21
  ".": {
23
- "import": "./src/index.ts"
22
+ "import": "./dist/paralayer.js"
23
+ },
24
+ "./ts": {
25
+ "import": "./src/paralayer.ts"
24
26
  }
25
27
  },
26
28
  "files": [
27
- "src"
29
+ "src",
30
+ "dist"
28
31
  ],
29
32
  "dependencies": {
30
- "@eposlabs/utils": "^1.0.13",
33
+ "@eposlabs/utils": "^1.1.1",
31
34
  "chokidar": "^4.0.3",
32
35
  "minimist": "^1.2.8"
33
36
  },
package/src/bin.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import minimist from 'minimist'
4
- import { paralayer } from './index.ts'
4
+ import { paralayer } from './paralayer.ts'
5
5
 
6
6
  const argv = minimist(process.argv.slice(2), {
7
- string: ['default'],
7
+ string: ['defaultLayerName'],
8
8
  boolean: ['watch', 'globalize'],
9
- default: { watch: false, default: null, globalize: false },
9
+ default: { watch: false, globalize: false, defaultLayerName: null },
10
10
  })
11
11
 
12
12
  const paths = argv._
@@ -27,6 +27,6 @@ await paralayer({
27
27
  input: input,
28
28
  output: output,
29
29
  watch: argv.watch,
30
- default: argv.default,
31
30
  globalize: argv.globalize,
31
+ defaultLayerName: argv.defaultLayerName,
32
32
  })
package/src/paralayer.ts CHANGED
@@ -15,10 +15,10 @@ export type Options = {
15
15
  input: DirPath | DirPath[]
16
16
  output: DirPath
17
17
  watch?: boolean
18
- /** Default layer name. If a file name does not have layer tags, default name will be used. */
19
- default?: string | null
20
18
  /** Whether the layer variables should be exposed globally. */
21
19
  globalize?: boolean
20
+ /** If a file name does not have layer tags, default layer name will be used. */
21
+ defaultLayerName?: string | null
22
22
  }
23
23
 
24
24
  export class Paralayer extends Unit {
@@ -37,7 +37,7 @@ export class Paralayer extends Unit {
37
37
  this.options = options
38
38
  }
39
39
 
40
- start = async () => {
40
+ async start() {
41
41
  if (this.started) return
42
42
  this.started = true
43
43
 
@@ -238,7 +238,7 @@ export class Paralayer extends Unit {
238
238
  const name = basename(path)
239
239
  const layer = name.split('.').slice(1, -1).sort().join('.')
240
240
  if (layer) return layer
241
- return this.options.default ?? ''
241
+ return this.options.defaultLayerName ?? ''
242
242
  }
243
243
 
244
244
  private getLayerName(layer: string, style: '$camel' | '$Pascal') {
@@ -266,3 +266,11 @@ export class Paralayer extends Unit {
266
266
  await writeFile(path, content, 'utf-8')
267
267
  }
268
268
  }
269
+
270
+ export async function paralayer(options: Options) {
271
+ const pl = new Paralayer(options)
272
+ await pl.start()
273
+ return await pl.readSetupJs()
274
+ }
275
+
276
+ export default paralayer
package/src/index.ts DELETED
@@ -1,9 +0,0 @@
1
- import { Paralayer, type Options } from './paralayer.ts'
2
-
3
- export async function paralayer(options: Options) {
4
- const pl = new Paralayer(options)
5
- await pl.start()
6
- return await pl.readSetupJs()
7
- }
8
-
9
- export default paralayer