@marsx-dev/launcher 0.0.2 → 0.0.5

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.
Files changed (54) hide show
  1. package/dist/cli/index.d.ts +4 -0
  2. package/dist/cli/index.d.ts.map +1 -0
  3. package/dist/cli/index.js +1 -0
  4. package/dist/cli/init.d.ts +2 -0
  5. package/dist/cli/init.d.ts.map +1 -0
  6. package/dist/cli/init.js +90 -59
  7. package/dist/cli/migrate.d.ts +2 -0
  8. package/dist/cli/migrate.d.ts.map +1 -0
  9. package/dist/cli/migrate.js +3 -2
  10. package/dist/cli/start.d.ts +3 -0
  11. package/dist/cli/start.d.ts.map +1 -0
  12. package/dist/cli/start.js +3 -2
  13. package/dist/configuration.d.ts +30 -0
  14. package/dist/configuration.d.ts.map +1 -0
  15. package/dist/configuration.js +95 -33
  16. package/dist/index.d.ts +7 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +40 -5
  19. package/dist/launcher.d.ts +2 -0
  20. package/dist/launcher.d.ts.map +1 -0
  21. package/dist/launcher.js +6 -4
  22. package/dist/loader.d.ts +3 -0
  23. package/dist/loader.d.ts.map +1 -0
  24. package/dist/loader.js +5 -4
  25. package/dist/utils/compile.d.ts +9 -0
  26. package/dist/utils/compile.d.ts.map +1 -0
  27. package/dist/utils/compile.js +8 -6
  28. package/dist/utils/fileUtils.d.ts +12 -0
  29. package/dist/utils/fileUtils.d.ts.map +1 -0
  30. package/dist/utils/fileUtils.js +42 -0
  31. package/dist/utils/sfc.d.ts +55 -0
  32. package/dist/utils/sfc.d.ts.map +1 -0
  33. package/dist/utils/sfc.js +54 -97
  34. package/dist/utils/textUtils.d.ts +17 -0
  35. package/dist/utils/textUtils.d.ts.map +1 -0
  36. package/dist/utils/textUtils.js +105 -0
  37. package/dist/utils/v3.d.ts +12 -0
  38. package/dist/utils/v3.d.ts.map +1 -0
  39. package/dist/utils/v3.js +5 -5
  40. package/package.json +12 -51
  41. package/src/cli/index.ts +37 -0
  42. package/src/cli/init.ts +142 -0
  43. package/src/cli/migrate.ts +23 -0
  44. package/src/cli/start.ts +34 -0
  45. package/src/configuration.ts +104 -0
  46. package/src/index.ts +6 -0
  47. package/src/launcher.ts +39 -0
  48. package/src/loader.ts +65 -0
  49. package/src/utils/compile.ts +59 -0
  50. package/src/utils/fileUtils.ts +54 -0
  51. package/src/utils/sfc.ts +155 -0
  52. package/src/utils/textUtils.ts +82 -0
  53. package/src/utils/v3.ts +104 -0
  54. package/dist/utils/utils.js +0 -59
@@ -0,0 +1,54 @@
1
+ import { promises as fs } from 'fs';
2
+ import _ from 'lodash';
3
+ import { Abortable } from 'node:events';
4
+ import { Mode, ObjectEncodingOptions, OpenMode } from 'node:fs';
5
+ import { Stream } from 'node:stream';
6
+ import path from 'path';
7
+
8
+ export async function listFilesRecursive(dir: string): Promise<string[]> {
9
+ const entries = await fs.readdir(dir);
10
+
11
+ const files = await Promise.all(
12
+ entries.map(async (entry): Promise<string[]> => {
13
+ const entryPath = path.resolve(dir, entry);
14
+ return (await fs.stat(entryPath)).isDirectory() ? listFilesRecursive(entryPath) : [entryPath];
15
+ }),
16
+ );
17
+ return _.flatten(files);
18
+ }
19
+
20
+ export async function isFile(filePath: string): Promise<boolean> {
21
+ try {
22
+ return (await fs.stat(filePath)).isFile();
23
+ } catch (e) {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ export async function isDirectory(dirPath: string): Promise<boolean> {
29
+ try {
30
+ return (await fs.stat(dirPath)).isDirectory();
31
+ } catch (e) {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ export async function writeFileMakeDir(
37
+ filePath: string,
38
+ data:
39
+ | string
40
+ | NodeJS.ArrayBufferView
41
+ | Iterable<string | NodeJS.ArrayBufferView>
42
+ | AsyncIterable<string | NodeJS.ArrayBufferView>
43
+ | Stream,
44
+ options?:
45
+ | (ObjectEncodingOptions & {
46
+ mode?: Mode | undefined;
47
+ flag?: OpenMode | undefined;
48
+ } & Abortable)
49
+ | BufferEncoding
50
+ | null,
51
+ ): Promise<void> {
52
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
53
+ await fs.writeFile(filePath, data, options);
54
+ }
@@ -0,0 +1,155 @@
1
+ import { CompilerError } from '@vue/compiler-core';
2
+ import JSON5 from 'json5';
3
+ import _ from 'lodash';
4
+ import path from 'path';
5
+ import { escapeCloseTag, escapeHtmlAttr, parseVueLike, stableHtmlAttributes, stablePrettyJson, unescapeCloseTag } from './textUtils';
6
+
7
+ export const METADATA_SECTION_ID = 'metadata';
8
+ export const MARS_SFC_EXT = 'mars';
9
+
10
+ const SAVE_EMPTY_SOURCES = true;
11
+
12
+ export type SfcPath = {
13
+ folder: string;
14
+ name: string;
15
+ blockTypeName: string;
16
+ ext: string;
17
+ filePath: string;
18
+ };
19
+
20
+ export type SfcSource = {
21
+ source: string;
22
+ lang: keyof typeof LANG_TAG_MAP | string | undefined;
23
+ props?: Record<string, string | undefined>;
24
+ lineOffset?: number;
25
+ };
26
+
27
+ export type SfcBlock = { path: SfcPath } & (
28
+ | { metadata: Record<string, unknown>; jsons: Record<string, unknown>; sources: Record<string, SfcSource>; rawContent: null }
29
+ | { metadata: EmptyObject; jsons: EmptyObject; sources: EmptyObject; rawContent: Buffer }
30
+ );
31
+
32
+ export function parseSfcPath(filePath: string): SfcPath {
33
+ // Blog/BlogPost.page.mars => {folder:"Blog", name: "BlogPost", blockTypeName: "page", ext: "mars", fullName: "Blog.BlogPost"}
34
+ const originalParsed = path.parse(filePath);
35
+ const withoutExt = originalParsed.name;
36
+ const ext = originalParsed.ext.slice(1);
37
+
38
+ const parsed = path.parse(withoutExt);
39
+ if (!parsed.name || !parsed.ext) throw new Error(`Invalid block file path: ${filePath}`);
40
+ const blockTypeName = parsed.ext.slice(1);
41
+
42
+ return {
43
+ folder: originalParsed.dir,
44
+ name: parsed.name,
45
+ blockTypeName,
46
+ ext,
47
+ filePath,
48
+ };
49
+ }
50
+
51
+ export function serializeSfcPath(identity: SfcPath): string {
52
+ return path.join(identity.folder, `${identity.name}.${identity.blockTypeName}.${identity.ext}`);
53
+ }
54
+
55
+ type EmptyObject = {
56
+ [K in string]: never;
57
+ };
58
+
59
+ const LANG_TAG_MAP = {
60
+ html: 'html',
61
+ pug: 'template',
62
+ js: 'script',
63
+ jsx: 'script',
64
+ ts: 'script',
65
+ tsx: 'script',
66
+ css: 'style',
67
+ scss: 'style',
68
+ less: 'style',
69
+ sass: 'style',
70
+ stylus: 'style',
71
+ text: 'text',
72
+ };
73
+
74
+ export function parseSFC(filePath: string, content: Buffer): SfcBlock {
75
+ const sfcPath = parseSfcPath(filePath);
76
+ if (sfcPath.ext !== MARS_SFC_EXT) {
77
+ return { path: sfcPath, metadata: {}, jsons: {}, sources: {}, rawContent: content };
78
+ }
79
+
80
+ const block: SfcBlock = {
81
+ path: parseSfcPath(filePath),
82
+ metadata: {},
83
+ jsons: {},
84
+ sources: {},
85
+ rawContent: null,
86
+ };
87
+ const seenIds = new Set<string>();
88
+ const errors: (CompilerError | SyntaxError)[] = [];
89
+
90
+ for (const node of parseVueLike(content.toString('utf-8'), errors)) {
91
+ const { id: sectionId, lang, ...props } = node.props;
92
+
93
+ if (!sectionId) {
94
+ errors.push({ name: `missing-id`, message: `Id property is missing`, code: 0, loc: node.loc });
95
+ continue;
96
+ }
97
+ if (seenIds.has(sectionId)) {
98
+ errors.push({ name: `duplicate-id`, message: `Duplicate section id: ${sectionId}`, code: 0, loc: node.loc });
99
+ continue;
100
+ }
101
+ seenIds.add(sectionId);
102
+
103
+ if (node.tag === 'json') {
104
+ try {
105
+ const jsonData = JSON5.parse(node.content);
106
+ if (sectionId === METADATA_SECTION_ID) {
107
+ block.metadata = jsonData;
108
+ } else {
109
+ block.jsons[sectionId] = jsonData;
110
+ }
111
+ } catch (e) {
112
+ errors.push({ name: 'invalid-json', message: `${e}`, code: 0, loc: node.loc });
113
+ }
114
+ } else {
115
+ const source = unescapeCloseTag(node.content.slice(1, -1), node.tag);
116
+ const lineOffset = node.loc.start.line;
117
+ block.sources[sectionId] = { source, lang, props, lineOffset };
118
+ }
119
+ }
120
+
121
+ if (errors.length) {
122
+ throw new Error(`Parsing SFC ${filePath} failed with ${JSON.stringify(errors)}`);
123
+ }
124
+
125
+ return block;
126
+ }
127
+
128
+ export function serializeSfc(block: SfcBlock): { filePath: string; content: Buffer } {
129
+ const filePath = serializeSfcPath(block.path);
130
+ if (block.path.ext !== MARS_SFC_EXT) {
131
+ if (block.rawContent === null) throw new Error(`SfcBlock must have rawContent if ext!="${MARS_SFC_EXT}"`);
132
+ return { filePath, content: block.rawContent };
133
+ }
134
+
135
+ if (block.rawContent !== null) throw new Error(`SfcBlock cannot have rawContent if ext=="${MARS_SFC_EXT}"`);
136
+
137
+ let content = '';
138
+
139
+ for (const [name, source] of [[METADATA_SECTION_ID, block.metadata] as const, ..._.sortBy(Object.entries(block.jsons), e => e[0])]) {
140
+ const jsonStr = stablePrettyJson(source);
141
+ content += `<json id="${escapeHtmlAttr(name)}">\n${jsonStr}</json>\n\n`;
142
+ }
143
+
144
+ for (const [name, source] of _.sortBy(Object.entries(block.sources), e => e[0])) {
145
+ if (!SAVE_EMPTY_SOURCES && !source.source.trim()) continue;
146
+ const tag = (LANG_TAG_MAP as Record<string, string>)[source.lang || ''] || 'text';
147
+ const id = escapeHtmlAttr(name);
148
+ const lang = escapeHtmlAttr(source.lang || '');
149
+ const props = stableHtmlAttributes(source.props);
150
+ const text = escapeCloseTag(source.source, tag);
151
+ content += `<${tag} id="${id}" lang="${lang}"${props}>\n${text}\n</${tag}>\n\n`;
152
+ }
153
+
154
+ return { filePath, content: Buffer.from(content, 'utf-8') };
155
+ }
@@ -0,0 +1,82 @@
1
+ import { CompilerError, TextModes } from '@vue/compiler-core';
2
+ import * as CompilerDOM from '@vue/compiler-dom';
3
+ import { NodeTypes } from '@vue/compiler-dom';
4
+ import stringify from 'json-stable-stringify';
5
+ import _ from 'lodash';
6
+ import prettier, { Options } from 'prettier';
7
+
8
+ export function assert(value: unknown, message = 'value must be defined'): asserts value {
9
+ if (!value) {
10
+ throw new Error(message);
11
+ }
12
+ }
13
+
14
+ export function escapeCloseTag(str: string, tag: string): string {
15
+ return str.replaceAll(`</${tag}>`, `</ ${tag}>`);
16
+ }
17
+
18
+ export function unescapeCloseTag(str: string, tag: string): string {
19
+ return str.replaceAll(`</ ${tag}>`, `</${tag}>`);
20
+ }
21
+
22
+ export function escapeHtmlAttr(str: string): string {
23
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
24
+ }
25
+
26
+ const PRETTIER_JSON_CONFIG: Options = { parser: 'json5', printWidth: 120, trailingComma: 'all' };
27
+
28
+ export function prettifyJson(json: string): string {
29
+ return prettier.format(`(${json})`, PRETTIER_JSON_CONFIG);
30
+ }
31
+
32
+ export function stableJson(data: unknown): string {
33
+ return stringify(data);
34
+ }
35
+
36
+ export function stablePrettyJson(data: unknown): string {
37
+ return prettifyJson(stableJson(data));
38
+ }
39
+
40
+ export function stableHtmlAttributes(attrs: Record<string, string | undefined | null> | undefined): string {
41
+ let result = '';
42
+ if (attrs) {
43
+ for (const [name, value] of _.sortBy(Object.entries(attrs), e => e[0])) {
44
+ if (value) {
45
+ result += ` ${name}="${escapeHtmlAttr(value)}"`;
46
+ }
47
+ }
48
+ }
49
+ return result;
50
+ }
51
+
52
+ export function parseVueLike(content: string, errors: (CompilerError | SyntaxError)[]) {
53
+ const ast = CompilerDOM.parse(content, {
54
+ // there are no components at SFC parsing level
55
+ isNativeTag: () => true,
56
+ // preserve all whitespaces
57
+ isPreTag: () => true,
58
+ getTextMode: () => TextModes.RAWTEXT,
59
+ onError: e => errors.push(e),
60
+ });
61
+
62
+ const result = [];
63
+
64
+ for (const node of ast.children) {
65
+ if (node.type === NodeTypes.ELEMENT) {
66
+ const child = node.children.length === 1 ? node.children[0] : null;
67
+ const content = child && child.type === NodeTypes.TEXT ? child.content : null;
68
+
69
+ if (content) {
70
+ const props: Record<string, string> = {};
71
+ for (const prop of node.props) {
72
+ if (prop.type === NodeTypes.ATTRIBUTE && prop.value) {
73
+ props[prop.name] = prop.value.content;
74
+ }
75
+ }
76
+ result.push({ tag: node.tag, props, content, loc: node.loc });
77
+ }
78
+ }
79
+ }
80
+
81
+ return result;
82
+ }
@@ -0,0 +1,104 @@
1
+ import { SfcBlock } from './sfc';
2
+
3
+ export type V3MongoBlock = {
4
+ Folder: string;
5
+ Name: string;
6
+ Type: string;
7
+ app?: { name: string };
8
+ } & Record<string, unknown>;
9
+
10
+ const SFC_FIELD_MAP: Record<string, string> = {
11
+ // JSONs
12
+ DataArgs: 'json',
13
+ Page: 'json',
14
+ pages: 'json',
15
+ blocks: 'json',
16
+ Config: 'json',
17
+ langs: 'json',
18
+ // Scripts
19
+ BlockFunction: 'ts',
20
+ Html: 'html',
21
+ HTML: 'html',
22
+ Jsx: 'tsx',
23
+ JsxTranspiled: 'js',
24
+ JsxTranspiledTranspiled: 'js',
25
+ JSX: 'tsx',
26
+ JSXTranspiled: 'js',
27
+ DemoJsx: 'tsx',
28
+ DemoJsxTranspiled: 'js',
29
+ Script: 'ts',
30
+ Css: 'css',
31
+ TestCode: 'ts',
32
+ JestDefinition: 'ts',
33
+ DataForScriptFunction: 'ts',
34
+ // Ignore (part of file path)
35
+ // _id: 'DELETE',
36
+ Name: 'DELETE',
37
+ Type: 'DELETE',
38
+ Folder: 'DELETE',
39
+ // Ignore (will be tracked by git)
40
+ app: 'DELETE',
41
+ Created: 'DELETE',
42
+ LastChanged: 'DELETE',
43
+ createdAt: 'DELETE',
44
+ createdBy: 'DELETE',
45
+ updatedAt: 'DELETE',
46
+ updatedBy: 'DELETE',
47
+ // ... rest of the fields will be saved in metadata
48
+ };
49
+
50
+ export function convertV3ToSfc(block: V3MongoBlock): SfcBlock {
51
+ const sfc: SfcBlock = {
52
+ path: {
53
+ folder: block.Folder,
54
+ name: block.Name,
55
+ blockTypeName: block.Type,
56
+ ext: 'vue',
57
+ filePath: '',
58
+ },
59
+ metadata: {},
60
+ jsons: {},
61
+ sources: {},
62
+ rawContent: null,
63
+ };
64
+ for (const [prop, value] of Object.entries(block)) {
65
+ const sfcLang = SFC_FIELD_MAP[prop] || 'METADATA';
66
+
67
+ switch (sfcLang) {
68
+ case 'json':
69
+ if (typeof value !== 'object' && !Array.isArray(value)) throw new Error(`Object or array is expected for ${prop} property`);
70
+ sfc.jsons[prop] = value;
71
+ break;
72
+ case 'DELETE':
73
+ break;
74
+ case 'METADATA':
75
+ sfc.metadata[prop] = value;
76
+ break;
77
+ default:
78
+ if (typeof value !== 'string') throw new Error(`String is expected for ${prop} property`);
79
+ sfc.sources[prop] = { source: value, lang: sfcLang };
80
+ break;
81
+ }
82
+ }
83
+
84
+ if (block.app) {
85
+ sfc.metadata['app'] = { name: block.app.name };
86
+ }
87
+ return sfc;
88
+ }
89
+
90
+ export function convertSfcToV3(sfc: SfcBlock): V3MongoBlock {
91
+ const block: V3MongoBlock = {
92
+ Name: sfc.path.name,
93
+ Type: sfc.path.blockTypeName,
94
+ Folder: sfc.path.folder,
95
+ ...sfc.metadata,
96
+ ...sfc.jsons,
97
+ };
98
+
99
+ for (const [key, value] of Object.entries(sfc.sources)) {
100
+ block[key] = value.source;
101
+ }
102
+
103
+ return block;
104
+ }
@@ -1,59 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.writeFileMakeDir = exports.isDirectory = exports.isFile = exports.listFilesRecursive = exports.escapeHtmlAttr = exports.unescapeCloseTag = exports.escapeCloseTag = exports.assert = void 0;
7
- const fs_1 = require("fs");
8
- const lodash_1 = __importDefault(require("lodash"));
9
- const path_1 = __importDefault(require("path"));
10
- function assert(value, message = 'value must be defined') {
11
- if (!value) {
12
- throw new Error(message);
13
- }
14
- }
15
- exports.assert = assert;
16
- function escapeCloseTag(str, tag) {
17
- return str.replaceAll(`</${tag}>`, `</ ${tag}>`);
18
- }
19
- exports.escapeCloseTag = escapeCloseTag;
20
- function unescapeCloseTag(str, tag) {
21
- return str.replaceAll(`</ ${tag}>`, `</${tag}>`);
22
- }
23
- exports.unescapeCloseTag = unescapeCloseTag;
24
- function escapeHtmlAttr(str) {
25
- return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
26
- }
27
- exports.escapeHtmlAttr = escapeHtmlAttr;
28
- async function listFilesRecursive(dir) {
29
- const entries = await fs_1.promises.readdir(dir);
30
- const files = await Promise.all(entries.map(async (entry) => {
31
- const entryPath = path_1.default.resolve(dir, entry);
32
- return (await fs_1.promises.stat(entryPath)).isDirectory() ? listFilesRecursive(entryPath) : [entryPath];
33
- }));
34
- return lodash_1.default.flatten(files);
35
- }
36
- exports.listFilesRecursive = listFilesRecursive;
37
- async function isFile(filePath) {
38
- try {
39
- return (await fs_1.promises.stat(filePath)).isFile();
40
- }
41
- catch (e) {
42
- return false;
43
- }
44
- }
45
- exports.isFile = isFile;
46
- async function isDirectory(dirPath) {
47
- try {
48
- return (await fs_1.promises.stat(dirPath)).isDirectory();
49
- }
50
- catch (e) {
51
- return false;
52
- }
53
- }
54
- exports.isDirectory = isDirectory;
55
- async function writeFileMakeDir(filePath, data, options) {
56
- await fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
57
- await fs_1.promises.writeFile(filePath, data, options);
58
- }
59
- exports.writeFileMakeDir = writeFileMakeDir;