@ojiepermana/angular 21.0.0 → 21.0.2
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/collection.json +25 -0
- package/fesm2022/ojiepermana-angular-generator-api.mjs +67 -0
- package/fesm2022/ojiepermana-angular-generator-api.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-layout.mjs +76 -56
- package/fesm2022/ojiepermana-angular-layout.mjs.map +1 -1
- package/fesm2022/ojiepermana-angular.mjs +1 -0
- package/fesm2022/ojiepermana-angular.mjs.map +1 -1
- package/generator/api/README.md +183 -0
- package/generator/api/bin/schematics/init/index.js +88 -0
- package/generator/api/bin/schematics/sdk/index.js +58 -0
- package/generator/api/bin/src/config/loader.js +41 -0
- package/generator/api/bin/src/config/schema.js +56 -0
- package/generator/api/bin/src/emit/client.js +246 -0
- package/generator/api/bin/src/emit/metadata.js +295 -0
- package/generator/api/bin/src/emit/models.js +106 -0
- package/generator/api/bin/src/emit/navigation.js +56 -0
- package/generator/api/bin/src/emit/operations.js +122 -0
- package/generator/api/bin/src/emit/public-api.js +54 -0
- package/generator/api/bin/src/emit/services.js +87 -0
- package/generator/api/bin/src/engine.js +65 -0
- package/generator/api/bin/src/layout/per-domain.js +346 -0
- package/generator/api/bin/src/parser/bundle.js +25 -0
- package/generator/api/bin/src/parser/ir.js +320 -0
- package/generator/api/bin/src/parser/types.js +7 -0
- package/generator/api/bin/src/render/template.js +58 -0
- package/generator/api/bin/src/writer/index.js +69 -0
- package/generator/api/schematics/init/schema.json +19 -0
- package/generator/api/schematics/sdk/schema.json +19 -0
- package/generator/api/sdk.config.example.json +22 -0
- package/generator/guide/README.md +84 -0
- package/generator/guide/bin/schematics/build/index.js +35 -0
- package/generator/guide/bin/schematics/init/index.js +70 -0
- package/generator/guide/bin/src/config/loader.js +50 -0
- package/generator/guide/bin/src/config/schema.js +12 -0
- package/generator/guide/bin/src/engine/component.js +73 -0
- package/generator/guide/bin/src/engine/frontmatter.js +42 -0
- package/generator/guide/bin/src/engine/index.js +42 -0
- package/generator/guide/bin/src/engine/naming.js +39 -0
- package/generator/guide/bin/src/engine/render.js +18 -0
- package/generator/guide/bin/src/engine/routes.js +106 -0
- package/generator/guide/bin/src/engine/walk.js +35 -0
- package/generator/guide/guide.config.example.json +9 -0
- package/generator/guide/schematics/build/schema.json +14 -0
- package/generator/guide/schematics/init/schema.json +19 -0
- package/package.json +10 -3
- package/types/ojiepermana-angular-generator-api.d.ts +85 -0
- package/types/ojiepermana-angular-layout.d.ts +2 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = init;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const FALLBACK_TEMPLATE = {
|
|
7
|
+
$schema: './node_modules/@ojiepermana/angular/generator/guide/schematics/build/schema.json',
|
|
8
|
+
sourceDir: './docs',
|
|
9
|
+
outputDir: './projects/demo/library/src/app/docs',
|
|
10
|
+
routeFile: 'doc.routes.ts',
|
|
11
|
+
componentPrefix: 'Doc',
|
|
12
|
+
componentStyle: 'none',
|
|
13
|
+
routeExportName: 'DOC_ROUTES',
|
|
14
|
+
};
|
|
15
|
+
function init(options = {}) {
|
|
16
|
+
return (tree, context) => {
|
|
17
|
+
const workspaceRoot = process.cwd();
|
|
18
|
+
const destRelative = options.path ?? 'config/guide.config.json';
|
|
19
|
+
const treePath = destRelative.startsWith('/') ? destRelative : `/${destRelative}`;
|
|
20
|
+
if (tree.exists(treePath) && !options.force) {
|
|
21
|
+
throw new Error(`${destRelative} already exists. Re-run with --force to overwrite.`);
|
|
22
|
+
}
|
|
23
|
+
const content = loadTemplate(workspaceRoot, destRelative);
|
|
24
|
+
const buffer = Buffer.from(content, 'utf8');
|
|
25
|
+
if (tree.exists(treePath)) {
|
|
26
|
+
tree.overwrite(treePath, buffer);
|
|
27
|
+
context.logger.info(`[guide:init] overwrote ${destRelative}`);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
tree.create(treePath, buffer);
|
|
31
|
+
context.logger.info(`[guide:init] created ${destRelative}`);
|
|
32
|
+
}
|
|
33
|
+
context.logger.info(`[guide:init] edit sourceDir / outputDir, then run \`${getNextCommand(workspaceRoot)}\`.`);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function loadTemplate(workspaceRoot, destRelative) {
|
|
37
|
+
const candidates = [
|
|
38
|
+
(0, node_path_1.resolve)(workspaceRoot, 'guide.config.example.json'),
|
|
39
|
+
(0, node_path_1.resolve)(__dirname, '../../../guide.config.example.json'),
|
|
40
|
+
(0, node_path_1.resolve)(workspaceRoot, 'projects/angular/generator/guide/guide.config.example.json'),
|
|
41
|
+
];
|
|
42
|
+
for (const candidate of candidates) {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(candidate, 'utf8'));
|
|
45
|
+
parsed.$schema = resolveSchemaPath(workspaceRoot, destRelative);
|
|
46
|
+
return `${JSON.stringify(parsed, null, 2)}\n`;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// try next
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return `${JSON.stringify({ ...FALLBACK_TEMPLATE, $schema: resolveSchemaPath(workspaceRoot, destRelative) }, null, 2)}\n`;
|
|
53
|
+
}
|
|
54
|
+
function resolveSchemaPath(workspaceRoot, destRelative) {
|
|
55
|
+
const prefix = relativePrefix(destRelative);
|
|
56
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(workspaceRoot, 'projects/angular/generator/guide/schematics/build/schema.json'))) {
|
|
57
|
+
return `${prefix}projects/angular/generator/guide/schematics/build/schema.json`;
|
|
58
|
+
}
|
|
59
|
+
return `${prefix}node_modules/@ojiepermana/angular/generator/guide/schematics/build/schema.json`;
|
|
60
|
+
}
|
|
61
|
+
function relativePrefix(destRelative) {
|
|
62
|
+
const depth = destRelative.replace(/^\/+/, '').split('/').length - 1;
|
|
63
|
+
return depth <= 0 ? './' : '../'.repeat(depth);
|
|
64
|
+
}
|
|
65
|
+
function getNextCommand(workspaceRoot) {
|
|
66
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(workspaceRoot, 'projects/angular/collection.json'))) {
|
|
67
|
+
return 'bun run gen:guide';
|
|
68
|
+
}
|
|
69
|
+
return 'ng generate @ojiepermana/angular:guide';
|
|
70
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadConfig = loadConfig;
|
|
4
|
+
exports.resolveConfig = resolveConfig;
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const VALID_STYLES = ['none', 'css', 'scss'];
|
|
8
|
+
function loadConfig(configPath, workspaceRoot) {
|
|
9
|
+
const absolute = (0, node_path_1.isAbsolute)(configPath) ? configPath : (0, node_path_1.resolve)(workspaceRoot, configPath);
|
|
10
|
+
let raw;
|
|
11
|
+
try {
|
|
12
|
+
raw = (0, node_fs_1.readFileSync)(absolute, 'utf8');
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
throw new Error(`[guide] Unable to read config at ${absolute}. Run \`bun run gen:guide:init\` to scaffold one. (${error.message})`);
|
|
16
|
+
}
|
|
17
|
+
let parsed;
|
|
18
|
+
try {
|
|
19
|
+
parsed = JSON.parse(raw);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw new Error(`[guide] Invalid JSON in ${absolute}: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
return resolveConfig(parsed, absolute);
|
|
25
|
+
}
|
|
26
|
+
function resolveConfig(input, configPath) {
|
|
27
|
+
if (!input || typeof input !== 'object') {
|
|
28
|
+
throw new Error(`[guide] Config at ${configPath} must be a JSON object.`);
|
|
29
|
+
}
|
|
30
|
+
if (!input.sourceDir || typeof input.sourceDir !== 'string') {
|
|
31
|
+
throw new Error(`[guide] "sourceDir" is required in ${configPath}.`);
|
|
32
|
+
}
|
|
33
|
+
if (!input.outputDir || typeof input.outputDir !== 'string') {
|
|
34
|
+
throw new Error(`[guide] "outputDir" is required in ${configPath}.`);
|
|
35
|
+
}
|
|
36
|
+
const style = input.componentStyle ?? 'none';
|
|
37
|
+
if (!VALID_STYLES.includes(style)) {
|
|
38
|
+
throw new Error(`[guide] Invalid componentStyle "${style}". Expected one of ${VALID_STYLES.join(', ')}.`);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
sourceDir: input.sourceDir,
|
|
42
|
+
outputDir: input.outputDir,
|
|
43
|
+
routeFile: input.routeFile && input.routeFile.trim() ? input.routeFile : 'doc.routes.ts',
|
|
44
|
+
componentPrefix: input.componentPrefix && input.componentPrefix.trim() ? input.componentPrefix : 'Doc',
|
|
45
|
+
componentStyle: style,
|
|
46
|
+
routeExportName: input.routeExportName && /^[A-Za-z_][A-Za-z0-9_]*$/.test(input.routeExportName)
|
|
47
|
+
? input.routeExportName
|
|
48
|
+
: 'DOC_ROUTES',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Public configuration shape for the `guide` schematic.
|
|
4
|
+
*
|
|
5
|
+
* The schematic reads `guide.config.json` at the workspace root and produces
|
|
6
|
+
* standalone Angular components plus a nested `Routes` file under `outputDir`.
|
|
7
|
+
*
|
|
8
|
+
* Node / Angular CLI utilities stay in the schematic package itself. This file
|
|
9
|
+
* is the only surface re-exported by the secondary entry point and therefore
|
|
10
|
+
* MUST stay free of Node-specific imports.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildComponent = buildComponent;
|
|
4
|
+
const naming_1 = require("./naming");
|
|
5
|
+
const render_1 = require("./render");
|
|
6
|
+
function buildComponent(segments, frontmatter, body, config) {
|
|
7
|
+
const html = (0, render_1.renderMarkdown)(body);
|
|
8
|
+
const escaped = (0, render_1.escapeForTemplateLiteral)(html);
|
|
9
|
+
const className = (0, naming_1.componentClassName)(config.componentPrefix, segments);
|
|
10
|
+
const selector = (0, naming_1.componentSelector)(config.componentPrefix, segments);
|
|
11
|
+
const fileBase = (0, naming_1.componentFileBase)(segments);
|
|
12
|
+
const folderPath = segments.slice(0, -1).map(naming_1.toSlug).join('/');
|
|
13
|
+
const tsPath = joinPath(folderPath, `${fileBase}.component.ts`);
|
|
14
|
+
const { stylePath, styleRef, styleContent } = styleAssets(config.componentStyle, folderPath, fileBase);
|
|
15
|
+
const title = typeof frontmatter.title === 'string' && frontmatter.title.trim()
|
|
16
|
+
? frontmatter.title.trim()
|
|
17
|
+
: humanizeTitle(segments);
|
|
18
|
+
const order = typeof frontmatter.order === 'number' ? frontmatter.order : null;
|
|
19
|
+
const pathOverride = typeof frontmatter.path === 'string' && frontmatter.path.trim() ? frontmatter.path.trim() : null;
|
|
20
|
+
const tsContent = renderComponentTs({
|
|
21
|
+
className,
|
|
22
|
+
selector,
|
|
23
|
+
html: escaped,
|
|
24
|
+
styleRef,
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
className,
|
|
28
|
+
selector,
|
|
29
|
+
tsContent,
|
|
30
|
+
styleContent,
|
|
31
|
+
tsPath,
|
|
32
|
+
stylePath,
|
|
33
|
+
segments,
|
|
34
|
+
title,
|
|
35
|
+
order,
|
|
36
|
+
pathOverride,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function styleAssets(style, folder, fileBase) {
|
|
40
|
+
if (style === 'none') {
|
|
41
|
+
return { stylePath: null, styleRef: null, styleContent: null };
|
|
42
|
+
}
|
|
43
|
+
const ext = style === 'scss' ? 'scss' : 'css';
|
|
44
|
+
const stylePath = joinPath(folder, `${fileBase}.component.${ext}`);
|
|
45
|
+
return {
|
|
46
|
+
stylePath,
|
|
47
|
+
styleRef: `./${fileBase}.component.${ext}`,
|
|
48
|
+
styleContent: `:host { display: block; }\n`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function renderComponentTs(opts) {
|
|
52
|
+
const styleLine = opts.styleRef ? ` styleUrl: '${opts.styleRef}',\n` : '';
|
|
53
|
+
return (`// Auto-generated by @ojiepermana/angular/generator/guide. Do not edit by hand.\n` +
|
|
54
|
+
`import { ChangeDetectionStrategy, Component } from '@angular/core';\n\n` +
|
|
55
|
+
`@Component({\n` +
|
|
56
|
+
` selector: '${opts.selector}',\n` +
|
|
57
|
+
` changeDetection: ChangeDetectionStrategy.OnPush,\n` +
|
|
58
|
+
styleLine +
|
|
59
|
+
` template: \`${opts.html}\`,\n` +
|
|
60
|
+
`})\n` +
|
|
61
|
+
`export class ${opts.className} {}\n`);
|
|
62
|
+
}
|
|
63
|
+
function humanizeTitle(segments) {
|
|
64
|
+
const last = segments[segments.length - 1] ?? 'Untitled';
|
|
65
|
+
return last
|
|
66
|
+
.replace(/[-_]+/g, ' ')
|
|
67
|
+
.replace(/\s+/g, ' ')
|
|
68
|
+
.trim()
|
|
69
|
+
.replace(/(^|\s)\S/g, (c) => c.toUpperCase());
|
|
70
|
+
}
|
|
71
|
+
function joinPath(folder, file) {
|
|
72
|
+
return folder ? `${folder}/${file}` : file;
|
|
73
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseMarkdown = parseMarkdown;
|
|
4
|
+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
5
|
+
function parseMarkdown(source) {
|
|
6
|
+
const match = source.match(FRONTMATTER_RE);
|
|
7
|
+
if (!match) {
|
|
8
|
+
return { frontmatter: {}, body: source };
|
|
9
|
+
}
|
|
10
|
+
const frontmatter = parseSimpleYaml(match[1]);
|
|
11
|
+
const body = source.slice(match[0].length);
|
|
12
|
+
return { frontmatter, body };
|
|
13
|
+
}
|
|
14
|
+
function parseSimpleYaml(yaml) {
|
|
15
|
+
const out = {};
|
|
16
|
+
for (const rawLine of yaml.split(/\r?\n/)) {
|
|
17
|
+
const line = rawLine.trim();
|
|
18
|
+
if (!line || line.startsWith('#'))
|
|
19
|
+
continue;
|
|
20
|
+
const idx = line.indexOf(':');
|
|
21
|
+
if (idx === -1)
|
|
22
|
+
continue;
|
|
23
|
+
const key = line.slice(0, idx).trim();
|
|
24
|
+
let value = line.slice(idx + 1).trim();
|
|
25
|
+
if (!key)
|
|
26
|
+
continue;
|
|
27
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
28
|
+
value = value.slice(1, -1);
|
|
29
|
+
}
|
|
30
|
+
if (value === 'true')
|
|
31
|
+
out[key] = true;
|
|
32
|
+
else if (value === 'false')
|
|
33
|
+
out[key] = false;
|
|
34
|
+
else if (value !== '' && !Number.isNaN(Number(value)) && /^-?\d+(\.\d+)?$/.test(value)) {
|
|
35
|
+
out[key] = Number(value);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
out[key] = value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generate = generate;
|
|
4
|
+
const node_path_1 = require("node:path");
|
|
5
|
+
const component_1 = require("./component");
|
|
6
|
+
const frontmatter_1 = require("./frontmatter");
|
|
7
|
+
const routes_1 = require("./routes");
|
|
8
|
+
const walk_1 = require("./walk");
|
|
9
|
+
function generate(config, workspaceRoot) {
|
|
10
|
+
const sourceAbs = resolveUnderRoot(config.sourceDir, workspaceRoot);
|
|
11
|
+
const outputAbs = resolveUnderRoot(config.outputDir, workspaceRoot);
|
|
12
|
+
const sources = (0, walk_1.collectMarkdown)(sourceAbs);
|
|
13
|
+
const components = sources.map((src) => {
|
|
14
|
+
const { frontmatter, body } = (0, frontmatter_1.parseMarkdown)(src.content);
|
|
15
|
+
return (0, component_1.buildComponent)(src.segments, frontmatter, body, config);
|
|
16
|
+
});
|
|
17
|
+
const files = [];
|
|
18
|
+
for (const comp of components) {
|
|
19
|
+
files.push({ path: joinWorkspace(workspaceRoot, outputAbs, comp.tsPath), content: comp.tsContent });
|
|
20
|
+
if (comp.stylePath && comp.styleContent !== null) {
|
|
21
|
+
files.push({ path: joinWorkspace(workspaceRoot, outputAbs, comp.stylePath), content: comp.styleContent });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
files.push({
|
|
25
|
+
path: joinWorkspace(workspaceRoot, outputAbs, config.routeFile),
|
|
26
|
+
content: (0, routes_1.renderRoutes)(components, config),
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
config,
|
|
30
|
+
outputDir: outputAbs,
|
|
31
|
+
files,
|
|
32
|
+
stats: { markdown: sources.length, components: components.length, files: files.length },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function resolveUnderRoot(p, workspaceRoot) {
|
|
36
|
+
return (0, node_path_1.isAbsolute)(p) ? p : (0, node_path_1.resolve)(workspaceRoot, p);
|
|
37
|
+
}
|
|
38
|
+
function joinWorkspace(workspaceRoot, outputAbs, rel) {
|
|
39
|
+
const abs = (0, node_path_1.resolve)(outputAbs, rel);
|
|
40
|
+
const normalized = abs.startsWith(workspaceRoot) ? abs.slice(workspaceRoot.length + 1) : abs;
|
|
41
|
+
return normalized.split(/[\\/]+/).join('/');
|
|
42
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toSlug = toSlug;
|
|
4
|
+
exports.toPascal = toPascal;
|
|
5
|
+
exports.componentClassName = componentClassName;
|
|
6
|
+
exports.componentFileBase = componentFileBase;
|
|
7
|
+
exports.componentSelector = componentSelector;
|
|
8
|
+
function toSlug(segment) {
|
|
9
|
+
return segment
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.trim()
|
|
12
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
13
|
+
.replace(/^-+|-+$/g, '');
|
|
14
|
+
}
|
|
15
|
+
function toPascal(segment) {
|
|
16
|
+
return segment
|
|
17
|
+
.replace(/[^A-Za-z0-9]+/g, ' ')
|
|
18
|
+
.trim()
|
|
19
|
+
.split(/\s+/)
|
|
20
|
+
.map((w) => (w.length === 0 ? w : w[0].toUpperCase() + w.slice(1).toLowerCase()))
|
|
21
|
+
.join('');
|
|
22
|
+
}
|
|
23
|
+
function componentClassName(prefix, segments) {
|
|
24
|
+
const base = segments.map(toPascal).join('');
|
|
25
|
+
return `${prefix}${base}Component`;
|
|
26
|
+
}
|
|
27
|
+
function componentFileBase(segments) {
|
|
28
|
+
const last = segments[segments.length - 1] ?? 'index';
|
|
29
|
+
return toSlug(last) || 'index';
|
|
30
|
+
}
|
|
31
|
+
function componentSelector(prefix, segments) {
|
|
32
|
+
const kebabPrefix = prefix
|
|
33
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
34
|
+
.replace(/[^A-Za-z0-9]+/g, '-')
|
|
35
|
+
.replace(/^-+|-+$/g, '')
|
|
36
|
+
.toLowerCase() || 'doc';
|
|
37
|
+
const rest = segments.map(toSlug).filter(Boolean).join('-') || 'index';
|
|
38
|
+
return `${kebabPrefix}-${rest}`;
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderMarkdown = renderMarkdown;
|
|
4
|
+
exports.escapeForTemplateLiteral = escapeForTemplateLiteral;
|
|
5
|
+
const marked_1 = require("marked");
|
|
6
|
+
/** Render Markdown (GFM) to HTML synchronously. */
|
|
7
|
+
function renderMarkdown(body) {
|
|
8
|
+
marked_1.marked.setOptions({ gfm: true, breaks: false });
|
|
9
|
+
const html = marked_1.marked.parse(body, { async: false });
|
|
10
|
+
if (typeof html !== 'string') {
|
|
11
|
+
throw new Error('[guide] marked returned a non-string result; expected synchronous parse.');
|
|
12
|
+
}
|
|
13
|
+
return html.trim();
|
|
14
|
+
}
|
|
15
|
+
/** Escape backticks and `${` so HTML can be safely embedded in a TS template literal. */
|
|
16
|
+
function escapeForTemplateLiteral(html) {
|
|
17
|
+
return html.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
18
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderRoutes = renderRoutes;
|
|
4
|
+
const naming_1 = require("./naming");
|
|
5
|
+
function renderRoutes(components, config) {
|
|
6
|
+
const root = { segment: '', children: new Map() };
|
|
7
|
+
for (const comp of components) {
|
|
8
|
+
insert(root, comp);
|
|
9
|
+
}
|
|
10
|
+
const body = serializeChildren(root, ' ');
|
|
11
|
+
const exportName = config.routeExportName;
|
|
12
|
+
return (`// Auto-generated by @ojiepermana/angular/generator/guide. Do not edit by hand.\n` +
|
|
13
|
+
`import type { Routes } from '@angular/router';\n\n` +
|
|
14
|
+
`export const ${exportName}: Routes = [\n` +
|
|
15
|
+
body +
|
|
16
|
+
`];\n`);
|
|
17
|
+
}
|
|
18
|
+
function insert(root, comp) {
|
|
19
|
+
const segments = routeSegments(comp);
|
|
20
|
+
let node = root;
|
|
21
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
22
|
+
const seg = segments[i];
|
|
23
|
+
let next = node.children.get(seg);
|
|
24
|
+
if (!next) {
|
|
25
|
+
next = { segment: seg, children: new Map() };
|
|
26
|
+
node.children.set(seg, next);
|
|
27
|
+
}
|
|
28
|
+
node = next;
|
|
29
|
+
}
|
|
30
|
+
const leafSeg = segments[segments.length - 1];
|
|
31
|
+
let leaf = node.children.get(leafSeg);
|
|
32
|
+
if (!leaf) {
|
|
33
|
+
leaf = { segment: leafSeg, children: new Map() };
|
|
34
|
+
node.children.set(leafSeg, leaf);
|
|
35
|
+
}
|
|
36
|
+
if (leaf.leaf) {
|
|
37
|
+
throw new Error(`[guide] Route conflict: two markdown files resolve to the same path "${segments.join('/') || '<root>'}".`);
|
|
38
|
+
}
|
|
39
|
+
leaf.leaf = comp;
|
|
40
|
+
}
|
|
41
|
+
function routeSegments(comp) {
|
|
42
|
+
// file segments without extension; handle index.md as empty path at parent level
|
|
43
|
+
const segs = comp.segments.slice();
|
|
44
|
+
const last = segs[segs.length - 1] ?? 'index';
|
|
45
|
+
if (comp.pathOverride !== null) {
|
|
46
|
+
// Override replaces the leaf segment path (keeps parent folders).
|
|
47
|
+
segs[segs.length - 1] = comp.pathOverride === '' ? '' : comp.pathOverride;
|
|
48
|
+
}
|
|
49
|
+
else if (/^index$/i.test(last)) {
|
|
50
|
+
segs[segs.length - 1] = '';
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
segs[segs.length - 1] = (0, naming_1.toSlug)(last);
|
|
54
|
+
}
|
|
55
|
+
// slugify folder segments
|
|
56
|
+
for (let i = 0; i < segs.length - 1; i++) {
|
|
57
|
+
segs[i] = (0, naming_1.toSlug)(segs[i]);
|
|
58
|
+
}
|
|
59
|
+
return segs;
|
|
60
|
+
}
|
|
61
|
+
function serializeChildren(node, indent) {
|
|
62
|
+
const children = [...node.children.values()].sort(compareNodes);
|
|
63
|
+
return children.map((c) => serializeNode(c, indent)).join('');
|
|
64
|
+
}
|
|
65
|
+
function compareNodes(a, b) {
|
|
66
|
+
const orderA = a.leaf?.order ?? Number.POSITIVE_INFINITY;
|
|
67
|
+
const orderB = b.leaf?.order ?? Number.POSITIVE_INFINITY;
|
|
68
|
+
if (orderA !== orderB)
|
|
69
|
+
return orderA - orderB;
|
|
70
|
+
return a.segment.localeCompare(b.segment);
|
|
71
|
+
}
|
|
72
|
+
function serializeNode(node, indent) {
|
|
73
|
+
const next = indent + ' ';
|
|
74
|
+
const lines = [];
|
|
75
|
+
lines.push(`${indent}{`);
|
|
76
|
+
lines.push(`${next}path: ${JSON.stringify(node.segment)},`);
|
|
77
|
+
if (node.leaf) {
|
|
78
|
+
const comp = node.leaf;
|
|
79
|
+
const importPath = importSpecifier(comp.tsPath);
|
|
80
|
+
lines.push(`${next}loadComponent: () => import(${JSON.stringify(importPath)}).then((m) => m.${comp.className}),`);
|
|
81
|
+
const data = routeData(comp);
|
|
82
|
+
if (data)
|
|
83
|
+
lines.push(`${next}data: ${data},`);
|
|
84
|
+
}
|
|
85
|
+
if (node.children.size > 0) {
|
|
86
|
+
lines.push(`${next}children: [`);
|
|
87
|
+
lines.push(serializeChildren(node, next + ' ').replace(/\n+$/, ''));
|
|
88
|
+
lines.push(`${next}],`);
|
|
89
|
+
}
|
|
90
|
+
lines.push(`${indent}},`);
|
|
91
|
+
return lines.join('\n') + '\n';
|
|
92
|
+
}
|
|
93
|
+
function routeData(comp) {
|
|
94
|
+
const parts = [];
|
|
95
|
+
if (comp.title)
|
|
96
|
+
parts.push(`title: ${JSON.stringify(comp.title)}`);
|
|
97
|
+
if (comp.order !== null)
|
|
98
|
+
parts.push(`order: ${comp.order}`);
|
|
99
|
+
if (parts.length === 0)
|
|
100
|
+
return null;
|
|
101
|
+
return `{ ${parts.join(', ')} }`;
|
|
102
|
+
}
|
|
103
|
+
function importSpecifier(tsPath) {
|
|
104
|
+
const noExt = tsPath.replace(/\.ts$/i, '');
|
|
105
|
+
return `./${noExt}`;
|
|
106
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectMarkdown = collectMarkdown;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
function collectMarkdown(sourceDir) {
|
|
7
|
+
const out = [];
|
|
8
|
+
walk(sourceDir, sourceDir, out);
|
|
9
|
+
return out.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
10
|
+
}
|
|
11
|
+
function walk(root, dir, out) {
|
|
12
|
+
const entries = (0, node_fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
const abs = (0, node_path_1.join)(dir, entry.name);
|
|
15
|
+
if (entry.isDirectory()) {
|
|
16
|
+
walk(root, abs, out);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (!entry.isFile() || !entry.name.toLowerCase().endsWith('.md'))
|
|
20
|
+
continue;
|
|
21
|
+
const st = (0, node_fs_1.statSync)(abs);
|
|
22
|
+
if (!st.isFile())
|
|
23
|
+
continue;
|
|
24
|
+
const rel = (0, node_path_1.relative)(root, abs)
|
|
25
|
+
.split(/[\\/]+/)
|
|
26
|
+
.join('/');
|
|
27
|
+
const segments = rel.replace(/\.md$/i, '').split('/');
|
|
28
|
+
out.push({
|
|
29
|
+
absolutePath: abs,
|
|
30
|
+
relativePath: rel,
|
|
31
|
+
segments,
|
|
32
|
+
content: (0, node_fs_1.readFileSync)(abs, 'utf8'),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/@ojiepermana/angular/generator/guide/schematics/build/schema.json",
|
|
3
|
+
"sourceDir": "./docs",
|
|
4
|
+
"outputDir": "./projects/demo/library/src/app/docs",
|
|
5
|
+
"routeFile": "doc.routes.ts",
|
|
6
|
+
"componentPrefix": "Doc",
|
|
7
|
+
"componentStyle": "none",
|
|
8
|
+
"routeExportName": "DOC_ROUTES"
|
|
9
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema",
|
|
3
|
+
"$id": "GuideBuildSchema",
|
|
4
|
+
"title": "Guide · build",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"properties": {
|
|
8
|
+
"config": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Path to the guide config file, relative to the workspace root.",
|
|
11
|
+
"default": "config/guide.config.json"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema",
|
|
3
|
+
"$id": "GuideInitSchema",
|
|
4
|
+
"title": "Guide · init",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"properties": {
|
|
8
|
+
"path": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Destination path for the generated guide.config.json, relative to the workspace root.",
|
|
11
|
+
"default": "config/guide.config.json"
|
|
12
|
+
},
|
|
13
|
+
"force": {
|
|
14
|
+
"type": "boolean",
|
|
15
|
+
"description": "Overwrite an existing guide.config.json.",
|
|
16
|
+
"default": false
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ojiepermana/angular",
|
|
3
|
-
"version": "21.0.
|
|
3
|
+
"version": "21.0.2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/ojiepermana/angular.git"
|
|
@@ -38,14 +38,17 @@
|
|
|
38
38
|
"d3-array": "^3.2.4",
|
|
39
39
|
"d3-scale": "^4.0.2",
|
|
40
40
|
"d3-shape": "^3.2.0",
|
|
41
|
+
"marked": "^16.0.0",
|
|
41
42
|
"tailwind-merge": "^3.5.0",
|
|
42
|
-
"tslib": "^2.8.1"
|
|
43
|
+
"tslib": "^2.8.1",
|
|
44
|
+
"yaml": "^2.8.3"
|
|
43
45
|
},
|
|
44
46
|
"publishConfig": {
|
|
45
47
|
"access": "public",
|
|
46
48
|
"registry": "https://registry.npmjs.org/"
|
|
47
49
|
},
|
|
48
50
|
"sideEffects": false,
|
|
51
|
+
"schematics": "./collection.json",
|
|
49
52
|
"exports": {
|
|
50
53
|
"./theme/styles": "./theme/styles/index.css",
|
|
51
54
|
"./theme/styles/*": "./theme/styles/*",
|
|
@@ -65,6 +68,10 @@
|
|
|
65
68
|
"types": "./types/ojiepermana-angular-component.d.ts",
|
|
66
69
|
"default": "./fesm2022/ojiepermana-angular-component.mjs"
|
|
67
70
|
},
|
|
71
|
+
"./generator/api": {
|
|
72
|
+
"types": "./types/ojiepermana-angular-generator-api.d.ts",
|
|
73
|
+
"default": "./fesm2022/ojiepermana-angular-generator-api.mjs"
|
|
74
|
+
},
|
|
68
75
|
"./layout": {
|
|
69
76
|
"types": "./types/ojiepermana-angular-layout.d.ts",
|
|
70
77
|
"default": "./fesm2022/ojiepermana-angular-layout.mjs"
|
|
@@ -81,4 +88,4 @@
|
|
|
81
88
|
"module": "fesm2022/ojiepermana-angular.mjs",
|
|
82
89
|
"typings": "types/ojiepermana-angular.d.ts",
|
|
83
90
|
"type": "module"
|
|
84
|
-
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public config shape for the SDK generator.
|
|
3
|
+
*
|
|
4
|
+
* Users place an `sdk.config.json` at the workspace root describing one or more
|
|
5
|
+
* generation targets. All paths are resolved relative to the workspace root
|
|
6
|
+
* (i.e. the directory containing the config file).
|
|
7
|
+
*/
|
|
8
|
+
type SdkOutputMode = 'standalone' | 'library' | 'secondary-entrypoint';
|
|
9
|
+
interface SdkFeatureFlags {
|
|
10
|
+
/** Emit `models/*.ts` (type-only DTO interfaces). Default `true`. */
|
|
11
|
+
models?: boolean;
|
|
12
|
+
/** Emit tree-shakeable operation functions in `fn/<tag>/*.ts`. Default `true`. */
|
|
13
|
+
operations?: boolean;
|
|
14
|
+
/** Emit grouped Injectable service classes per tag. Default `true`. */
|
|
15
|
+
services?: boolean;
|
|
16
|
+
/** Emit the runtime client primitives (ApiConfiguration, BaseService, etc). Default `true`. */
|
|
17
|
+
client?: boolean;
|
|
18
|
+
/** Emit operation rules + field validators + permission helpers. Default `true`. */
|
|
19
|
+
metadata?: boolean;
|
|
20
|
+
/** Emit navigation tree derived from tags (parent, x-icon). Default `false`. */
|
|
21
|
+
navigation?: boolean;
|
|
22
|
+
}
|
|
23
|
+
interface SdkTargetConfig {
|
|
24
|
+
/** Path to OpenAPI 3.x spec (YAML or JSON). Required. */
|
|
25
|
+
input: string;
|
|
26
|
+
/** Output directory (relative to workspace root). Required. */
|
|
27
|
+
output: string;
|
|
28
|
+
/** Output mode. Defaults to `"standalone"`. */
|
|
29
|
+
mode?: SdkOutputMode;
|
|
30
|
+
/** Optional human-readable name used for logs & the generated `Api` class. */
|
|
31
|
+
clientName?: string;
|
|
32
|
+
/** Package name used when `mode === "library"`. */
|
|
33
|
+
packageName?: string;
|
|
34
|
+
/** Package version used when `mode === "library"`. Defaults to `0.0.1`. */
|
|
35
|
+
packageVersion?: string;
|
|
36
|
+
/** Default rootUrl baked into ApiConfiguration. Defaults to the first server in the spec. */
|
|
37
|
+
rootUrl?: string;
|
|
38
|
+
/** Feature toggles. Every feature defaults to `true` except `navigation` (default `true` too). */
|
|
39
|
+
features?: SdkFeatureFlags;
|
|
40
|
+
/**
|
|
41
|
+
* When `true`, reorganize emitted files into one folder per domain (derived
|
|
42
|
+
* from OpenAPI tags). Models shared by multiple domains live in `shared/`,
|
|
43
|
+
* domain-owned models live inside each domain. Default `false` (flat layout).
|
|
44
|
+
*/
|
|
45
|
+
splitByDomain?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Granularity of `splitByDomain`. Only meaningful when `splitByDomain` is
|
|
48
|
+
* `true`. `'service'` (default) groups every tag under its root parent — one
|
|
49
|
+
* folder per backend service (e.g. all Role/Permission/... tags collapse into
|
|
50
|
+
* `access/`). `'tag'` emits one folder per leaf tag, nested under the parent
|
|
51
|
+
* chain (e.g. `storage/gcs/`, `storage/s3/`, `access/role/`).
|
|
52
|
+
*/
|
|
53
|
+
splitDepth?: 'service' | 'tag';
|
|
54
|
+
/** Custom file banner. Defaults to a short auto-generated notice. */
|
|
55
|
+
banner?: string;
|
|
56
|
+
}
|
|
57
|
+
interface SdkConfig {
|
|
58
|
+
targets: SdkTargetConfig[];
|
|
59
|
+
}
|
|
60
|
+
interface ResolvedFeatureFlags {
|
|
61
|
+
models: boolean;
|
|
62
|
+
operations: boolean;
|
|
63
|
+
services: boolean;
|
|
64
|
+
client: boolean;
|
|
65
|
+
metadata: boolean;
|
|
66
|
+
navigation: boolean;
|
|
67
|
+
}
|
|
68
|
+
interface ResolvedSdkTarget {
|
|
69
|
+
input: string;
|
|
70
|
+
output: string;
|
|
71
|
+
mode: SdkOutputMode;
|
|
72
|
+
clientName: string;
|
|
73
|
+
packageName: string;
|
|
74
|
+
packageVersion: string;
|
|
75
|
+
rootUrl: string | undefined;
|
|
76
|
+
features: ResolvedFeatureFlags;
|
|
77
|
+
splitByDomain: boolean;
|
|
78
|
+
splitDepth: 'service' | 'tag';
|
|
79
|
+
banner: string;
|
|
80
|
+
}
|
|
81
|
+
declare function resolveTarget(raw: SdkTargetConfig): ResolvedSdkTarget;
|
|
82
|
+
declare function resolveConfig(raw: unknown): ResolvedSdkTarget[];
|
|
83
|
+
|
|
84
|
+
export { resolveConfig, resolveTarget };
|
|
85
|
+
export type { ResolvedFeatureFlags, ResolvedSdkTarget, SdkConfig, SdkFeatureFlags, SdkOutputMode, SdkTargetConfig };
|