@marsx-dev/launcher 0.0.1-4.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/LICENSE +201 -0
- package/README.md +5 -0
- package/dist/cli/fix-folders.d.ts +2 -0
- package/dist/cli/fix-folders.d.ts.map +1 -0
- package/dist/cli/fix-folders.js +51 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +49 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +132 -0
- package/dist/cli/migrate.d.ts +2 -0
- package/dist/cli/migrate.d.ts.map +1 -0
- package/dist/cli/migrate.js +35 -0
- package/dist/cli/start.d.ts +3 -0
- package/dist/cli/start.d.ts.map +1 -0
- package/dist/cli/start.js +40 -0
- package/dist/configuration.d.ts +30 -0
- package/dist/configuration.d.ts.map +1 -0
- package/dist/configuration.js +115 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/launcher.d.ts +2 -0
- package/dist/launcher.d.ts.map +1 -0
- package/dist/launcher.js +40 -0
- package/dist/loader.d.ts +4 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +65 -0
- package/dist/utils/compile.d.ts +13 -0
- package/dist/utils/compile.d.ts.map +1 -0
- package/dist/utils/compile.js +55 -0
- package/dist/utils/fileUtils.d.ts +12 -0
- package/dist/utils/fileUtils.d.ts.map +1 -0
- package/dist/utils/fileUtils.js +42 -0
- package/dist/utils/sfc.d.ts +55 -0
- package/dist/utils/sfc.d.ts.map +1 -0
- package/dist/utils/sfc.js +128 -0
- package/dist/utils/textUtils.d.ts +17 -0
- package/dist/utils/textUtils.d.ts.map +1 -0
- package/dist/utils/textUtils.js +101 -0
- package/dist/utils/v3.d.ts +12 -0
- package/dist/utils/v3.d.ts.map +1 -0
- package/dist/utils/v3.js +98 -0
- package/jsx-runtime/jsx-runtime.js +16 -0
- package/jsx-runtime/package.json +5 -0
- package/package.json +80 -0
- package/src/cli/fix-folders.ts +57 -0
- package/src/cli/index.ts +46 -0
- package/src/cli/init.ts +146 -0
- package/src/cli/migrate.ts +30 -0
- package/src/cli/start.ts +34 -0
- package/src/configuration.ts +124 -0
- package/src/index.ts +6 -0
- package/src/launcher.ts +39 -0
- package/src/loader.ts +68 -0
- package/src/utils/compile.ts +68 -0
- package/src/utils/fileUtils.ts +54 -0
- package/src/utils/sfc.ts +155 -0
- package/src/utils/textUtils.ts +82 -0
- package/src/utils/v3.ts +104 -0
package/src/utils/sfc.ts
ADDED
|
@@ -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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
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
|
+
}
|
package/src/utils/v3.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { MARS_SFC_EXT, 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: MARS_SFC_EXT,
|
|
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
|
+
}
|