@orbytes/astrolab 0.3.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/LICENSE +37 -0
- package/README.md +410 -0
- package/bin/lab-cull.mjs +401 -0
- package/bin/pin-gallery.mjs +121 -0
- package/defaults.mjs +120 -0
- package/dist/core/astro-integration.js +130 -0
- package/dist/core/index.js +6 -0
- package/dist/core/lib-paths.js +29 -0
- package/dist/core/options.js +173 -0
- package/dist/core/utils/get-exports.js +52 -0
- package/dist/core/utils/invariant.js +12 -0
- package/dist/core/utils/kebab-case.js +15 -0
- package/dist/core/utils/path-builder.js +22 -0
- package/dist/core/utils/path.js +53 -0
- package/dist/core/virtual-module/get-story-modules.js +60 -0
- package/dist/core/virtual-module/story-modules.js +8 -0
- package/dist/core/virtual-module/virtual-module-ids.js +22 -0
- package/dist/core/virtual-module/virtual-routes.js +83 -0
- package/dist/core/virtual-module/vite-plugin.js +98 -0
- package/docs/PIN-CONTRACT.md +263 -0
- package/docs/PIN.md +429 -0
- package/index.d.ts +279 -0
- package/index.mjs +347 -0
- package/package.json +93 -0
- package/src/Empty.astro +4 -0
- package/src/Home.astro +298 -0
- package/src/LabHead.astro +1102 -0
- package/src/core/LICENSE-astrobook +166 -0
- package/src/core/astro-integration.ts +166 -0
- package/src/core/client.ts +89 -0
- package/src/core/index.ts +7 -0
- package/src/core/lib/components/empty.astro +1 -0
- package/src/core/lib/components/head.astro +1 -0
- package/src/core/lib/components/home.astro +8 -0
- package/src/core/lib/components/with-decorators.astro +22 -0
- package/src/core/lib/pages/app.astro +19 -0
- package/src/core/lib/pages/preview.astro +17 -0
- package/src/core/lib/pages/story.astro +16 -0
- package/src/core/lib-paths.ts +72 -0
- package/src/core/options.ts +262 -0
- package/src/core/utils/get-exports.ts +59 -0
- package/src/core/utils/invariant.ts +13 -0
- package/src/core/utils/kebab-case.ts +30 -0
- package/src/core/utils/path-builder.ts +45 -0
- package/src/core/utils/path.ts +80 -0
- package/src/core/virtual-module/get-story-modules.ts +110 -0
- package/src/core/virtual-module/story-modules.ts +9 -0
- package/src/core/virtual-module/virtual-module-ids.ts +17 -0
- package/src/core/virtual-module/virtual-routes.ts +130 -0
- package/src/core/virtual-module/vite-plugin.ts +125 -0
- package/src/pin/board.mjs +1521 -0
- package/src/pin/index.mjs +666 -0
- package/src/pin/shot.mjs +427 -0
- package/src/pin/source-stamp.mjs +159 -0
- package/src/pin/tickets.mjs +697 -0
- package/src/pin/toolbar.js +3181 -0
- package/src/shell/Browse.astro +371 -0
- package/src/shell/CardGrid.astro +297 -0
- package/src/shell/Viewport.astro +1330 -0
- package/src/shell/index.json.ts +12 -0
- package/src/shell/lab-index.ts +344 -0
- package/src/shell/lab-params.ts +245 -0
- package/src/shell/live-files.mjs +164 -0
- package/src/shell/marks.mjs +136 -0
- package/src/types/index.ts +6 -0
- package/src/types/types.ts +239 -0
- package/src/types/virtual.d.ts +29 -0
- package/src/ui/components/app.astro +13 -0
- package/src/ui/components/build-path.ts +13 -0
- package/src/ui/components/build-tree.ts +108 -0
- package/src/ui/components/collapse-duration.ts +28 -0
- package/src/ui/components/compress-terms.ts +10 -0
- package/src/ui/components/dashboard-layout.astro +39 -0
- package/src/ui/components/home.astro +65 -0
- package/src/ui/components/layout.astro +110 -0
- package/src/ui/components/preview-layout.astro +109 -0
- package/src/ui/components/sidebar-button-fullscreen.astro +38 -0
- package/src/ui/components/sidebar-button-search.astro +23 -0
- package/src/ui/components/sidebar-button-theme.astro +9 -0
- package/src/ui/components/sidebar-button.astro +24 -0
- package/src/ui/components/sidebar-resize-handle.astro +74 -0
- package/src/ui/components/sidebar-search-panel.astro +41 -0
- package/src/ui/components/sidebar-search-script.ts +103 -0
- package/src/ui/components/sidebar-title.astro +17 -0
- package/src/ui/components/sidebar-tree-node.astro +143 -0
- package/src/ui/components/sidebar-tree.astro +84 -0
- package/src/ui/components/sidebar.astro +29 -0
- package/src/ui/components/theme-message.ts +26 -0
- package/src/ui/components/theme-script.astro +71 -0
- package/src/ui/components/theme-toggle.astro +63 -0
- package/src/ui/components/theme.ts +32 -0
- package/src/ui/index.ts +4 -0
- package/src/ui/lab.css +549 -0
- package/virtual.d.ts +42 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import colors from "yoctocolors";
|
|
5
|
+
import { APP_PAGE } from "./lib-paths.js";
|
|
6
|
+
import { resolveOptions } from "./options.js";
|
|
7
|
+
import { createPathBuilder } from "./utils/path-builder.js";
|
|
8
|
+
import {
|
|
9
|
+
createVirtualRouteComponent,
|
|
10
|
+
getVirtualRoutes
|
|
11
|
+
} from "./virtual-module/virtual-routes.js";
|
|
12
|
+
import { createVirtualFilesPlugin } from "./virtual-module/vite-plugin.js";
|
|
13
|
+
function createAstrobookIntegration(userOptions) {
|
|
14
|
+
let astrobookBaseForLogging;
|
|
15
|
+
const resolvedOptions = resolveOptions(userOptions);
|
|
16
|
+
return {
|
|
17
|
+
name: "astrobook",
|
|
18
|
+
hooks: {
|
|
19
|
+
"astro:config:setup": async ({
|
|
20
|
+
updateConfig,
|
|
21
|
+
injectRoute,
|
|
22
|
+
createCodegenDir,
|
|
23
|
+
config,
|
|
24
|
+
logger,
|
|
25
|
+
command
|
|
26
|
+
}) => {
|
|
27
|
+
const rootDir = path.resolve(
|
|
28
|
+
fileURLToPath(config.root),
|
|
29
|
+
resolvedOptions.directory
|
|
30
|
+
);
|
|
31
|
+
const astroBase = config.base || "";
|
|
32
|
+
const astrobookSubpath = resolvedOptions.subpath;
|
|
33
|
+
const buildPath = createPathBuilder({
|
|
34
|
+
trailingSlash: config.trailingSlash
|
|
35
|
+
});
|
|
36
|
+
const dashboardSubpath = buildPath(
|
|
37
|
+
astrobookSubpath,
|
|
38
|
+
resolvedOptions.dashboardSubpath
|
|
39
|
+
);
|
|
40
|
+
const previewSubpath = buildPath(
|
|
41
|
+
astrobookSubpath,
|
|
42
|
+
resolvedOptions.previewSubpath
|
|
43
|
+
);
|
|
44
|
+
if (dashboardSubpath === previewSubpath) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
"[Astrobook] The dashboard and preview subpaths cannot be the same. Please set different values for `dashboardSubpath` and `previewSubpath` options."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const astrobookBase = buildPath(astroBase, astrobookSubpath);
|
|
50
|
+
const dashboardBase = buildPath(astroBase, dashboardSubpath);
|
|
51
|
+
const storyBase = buildPath(astroBase, previewSubpath);
|
|
52
|
+
if (astrobookSubpath && command === "dev") {
|
|
53
|
+
astrobookBaseForLogging = astrobookBase;
|
|
54
|
+
} else {
|
|
55
|
+
astrobookBaseForLogging = void 0;
|
|
56
|
+
}
|
|
57
|
+
logger.debug(`Creating dedicated folder`);
|
|
58
|
+
const codegenDir = fileURLToPath(createCodegenDir());
|
|
59
|
+
logger.debug(`Created dedicated folder at ${codegenDir}`);
|
|
60
|
+
logger.debug(`Scanning for stories in ${rootDir}`);
|
|
61
|
+
const routes = await getVirtualRoutes(
|
|
62
|
+
rootDir,
|
|
63
|
+
codegenDir,
|
|
64
|
+
logger,
|
|
65
|
+
dashboardSubpath,
|
|
66
|
+
previewSubpath,
|
|
67
|
+
buildPath
|
|
68
|
+
);
|
|
69
|
+
logger.debug(`Writing files to ${codegenDir}`);
|
|
70
|
+
await Promise.all(
|
|
71
|
+
Array.from(routes.values(), async (route) => {
|
|
72
|
+
const filePath = route.entrypoint;
|
|
73
|
+
const fileContent = createVirtualRouteComponent(route);
|
|
74
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
75
|
+
await fs.writeFile(filePath, fileContent, { encoding: "utf-8" });
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
updateConfig({
|
|
79
|
+
vite: {
|
|
80
|
+
plugins: [
|
|
81
|
+
createVirtualFilesPlugin(
|
|
82
|
+
rootDir,
|
|
83
|
+
{
|
|
84
|
+
astrobookBase,
|
|
85
|
+
dashboardBase,
|
|
86
|
+
storyBase,
|
|
87
|
+
head: resolvedOptions.head,
|
|
88
|
+
home: resolvedOptions.home,
|
|
89
|
+
homeContent: resolvedOptions.homeContent,
|
|
90
|
+
css: resolvedOptions.css,
|
|
91
|
+
title: resolvedOptions.title,
|
|
92
|
+
trailingSlash: config.trailingSlash
|
|
93
|
+
},
|
|
94
|
+
config,
|
|
95
|
+
logger
|
|
96
|
+
)
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
logger.debug(`Injecting routes`);
|
|
101
|
+
for (const route of routes.values()) {
|
|
102
|
+
injectRoute({
|
|
103
|
+
pattern: route.pattern,
|
|
104
|
+
entrypoint: route.entrypoint,
|
|
105
|
+
prerender: true
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
injectRoute({
|
|
109
|
+
pattern: astrobookSubpath,
|
|
110
|
+
entrypoint: APP_PAGE,
|
|
111
|
+
prerender: true
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
"astro:server:start": ({ logger, address }) => {
|
|
115
|
+
if (astrobookBaseForLogging) {
|
|
116
|
+
const url = new URL(
|
|
117
|
+
astrobookBaseForLogging,
|
|
118
|
+
`http://localhost:${address.port}`
|
|
119
|
+
);
|
|
120
|
+
const message = colors.bgGreen(colors.white(colors.bold(" astrobook "))) + " is available at " + colors.cyan(url.href);
|
|
121
|
+
const plainLogger = logger.fork("SKIP_FORMAT");
|
|
122
|
+
plainLogger.info(message);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
export {
|
|
129
|
+
createAstrobookIntegration
|
|
130
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import slash from "slash";
|
|
3
|
+
const LIB_BASE = new URL(
|
|
4
|
+
import.meta.url.endsWith("/dist/core/lib-paths.js") ? "../../src/core/" : "./",
|
|
5
|
+
import.meta.url
|
|
6
|
+
);
|
|
7
|
+
function libFile(relative) {
|
|
8
|
+
return slash(fileURLToPath(new URL(relative, LIB_BASE)));
|
|
9
|
+
}
|
|
10
|
+
const CLIENT_MODULE = libFile("./client.ts");
|
|
11
|
+
const STORY_PAGE = libFile("./lib/pages/story.astro");
|
|
12
|
+
const PREVIEW_PAGE = libFile("./lib/pages/preview.astro");
|
|
13
|
+
const APP_PAGE = libFile("./lib/pages/app.astro");
|
|
14
|
+
const WITH_DECORATORS_COMPONENT = libFile(
|
|
15
|
+
"./lib/components/with-decorators.astro"
|
|
16
|
+
);
|
|
17
|
+
const HEAD_COMPONENT = libFile("./lib/components/head.astro");
|
|
18
|
+
const HOME_COMPONENT = libFile("./lib/components/home.astro");
|
|
19
|
+
const EMPTY_COMPONENT = libFile("./lib/components/empty.astro");
|
|
20
|
+
export {
|
|
21
|
+
APP_PAGE,
|
|
22
|
+
CLIENT_MODULE,
|
|
23
|
+
EMPTY_COMPONENT,
|
|
24
|
+
HEAD_COMPONENT,
|
|
25
|
+
HOME_COMPONENT,
|
|
26
|
+
PREVIEW_PAGE,
|
|
27
|
+
STORY_PAGE,
|
|
28
|
+
WITH_DECORATORS_COMPONENT
|
|
29
|
+
};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EMPTY_COMPONENT,
|
|
3
|
+
HEAD_COMPONENT,
|
|
4
|
+
HOME_COMPONENT
|
|
5
|
+
} from "./lib-paths.js";
|
|
6
|
+
const ASTROBOOK_VERSION = "0.13.3";
|
|
7
|
+
const DEFAULT_VERSION_HREF = "https://github.com/ocavue/astrobook/blob/master/packages/astrobook/CHANGELOG.md";
|
|
8
|
+
const DEFAULT_REPO_HREF = "https://github.com/ocavue/astrobook";
|
|
9
|
+
function stringify(value) {
|
|
10
|
+
switch (typeof value) {
|
|
11
|
+
case "string":
|
|
12
|
+
return `"${value}"`;
|
|
13
|
+
case "number":
|
|
14
|
+
case "bigint":
|
|
15
|
+
case "boolean":
|
|
16
|
+
return String(value);
|
|
17
|
+
case "symbol":
|
|
18
|
+
case "function":
|
|
19
|
+
return value.toString();
|
|
20
|
+
case "object":
|
|
21
|
+
return value === null ? "null" : Array.isArray(value) ? "Array" : "Object";
|
|
22
|
+
default:
|
|
23
|
+
return "undefined";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function typeIssue(expected, value, path) {
|
|
27
|
+
return {
|
|
28
|
+
message: `Invalid type: Expected ${expected} but received ${stringify(value)}`,
|
|
29
|
+
path
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function readString(value, fallback, path, issues) {
|
|
33
|
+
if (value === void 0) return fallback;
|
|
34
|
+
if (typeof value !== "string") {
|
|
35
|
+
issues.push(typeIssue("string", value, path));
|
|
36
|
+
return fallback;
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
function readStringArray(value, path, issues) {
|
|
41
|
+
if (value === void 0) return [];
|
|
42
|
+
if (!Array.isArray(value)) {
|
|
43
|
+
issues.push(typeIssue("Array", value, path));
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const result = [];
|
|
47
|
+
value.forEach((item, index) => {
|
|
48
|
+
if (typeof item !== "string") {
|
|
49
|
+
issues.push(typeIssue("string", item, `${path}.${index}`));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
result.push(item);
|
|
53
|
+
});
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
function readStringOrFalse(value, fallback, path, issues) {
|
|
57
|
+
if (value === void 0) return fallback;
|
|
58
|
+
if (value === false) return false;
|
|
59
|
+
if (typeof value !== "string") {
|
|
60
|
+
issues.push(typeIssue("(string | false)", value, path));
|
|
61
|
+
return fallback;
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
function readBadge(value, defaults, path, issues) {
|
|
66
|
+
if (value === void 0) return { ...defaults };
|
|
67
|
+
if (value === false) return false;
|
|
68
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
69
|
+
issues.push(typeIssue("(Object | false)", value, path));
|
|
70
|
+
return { ...defaults };
|
|
71
|
+
}
|
|
72
|
+
const badge = value;
|
|
73
|
+
return {
|
|
74
|
+
href: readString(badge["href"], defaults.href, `${path}.href`, issues),
|
|
75
|
+
label: readString(badge["label"], defaults.label, `${path}.label`, issues)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function readHomeContent(value, issues) {
|
|
79
|
+
let content = {};
|
|
80
|
+
if (value !== void 0) {
|
|
81
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
82
|
+
issues.push(typeIssue("Object", value, "homeContent"));
|
|
83
|
+
} else {
|
|
84
|
+
content = value;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
title: readStringOrFalse(
|
|
89
|
+
content["title"],
|
|
90
|
+
"Astrobook",
|
|
91
|
+
"homeContent.title",
|
|
92
|
+
issues
|
|
93
|
+
),
|
|
94
|
+
subtitle: readStringOrFalse(
|
|
95
|
+
content["subtitle"],
|
|
96
|
+
"The minimal UI component playground",
|
|
97
|
+
"homeContent.subtitle",
|
|
98
|
+
issues
|
|
99
|
+
),
|
|
100
|
+
version: readBadge(
|
|
101
|
+
content["version"],
|
|
102
|
+
{ href: DEFAULT_VERSION_HREF, label: `v${ASTROBOOK_VERSION}` },
|
|
103
|
+
"homeContent.version",
|
|
104
|
+
issues
|
|
105
|
+
),
|
|
106
|
+
repo: readBadge(
|
|
107
|
+
content["repo"],
|
|
108
|
+
{ href: DEFAULT_REPO_HREF, label: "Star on GitHub" },
|
|
109
|
+
"homeContent.repo",
|
|
110
|
+
issues
|
|
111
|
+
)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function readHome(value, issues) {
|
|
115
|
+
if (value === void 0) return HOME_COMPONENT;
|
|
116
|
+
if (value === false) return EMPTY_COMPONENT;
|
|
117
|
+
if (typeof value !== "string") {
|
|
118
|
+
issues.push(typeIssue("(string | false)", value, "home"));
|
|
119
|
+
return HOME_COMPONENT;
|
|
120
|
+
}
|
|
121
|
+
if (value.length === 0) {
|
|
122
|
+
issues.push({
|
|
123
|
+
message: "Invalid length: Expected >=1 but received 0",
|
|
124
|
+
path: "home"
|
|
125
|
+
});
|
|
126
|
+
return HOME_COMPONENT;
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
function resolveOptions(options) {
|
|
131
|
+
const issues = [];
|
|
132
|
+
let input = {};
|
|
133
|
+
if (options !== void 0) {
|
|
134
|
+
if (typeof options !== "object" || options === null) {
|
|
135
|
+
issues.push(typeIssue("Object", options, ""));
|
|
136
|
+
} else {
|
|
137
|
+
input = options;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const resolved = {
|
|
141
|
+
directory: readString(input["directory"], ".", "directory", issues),
|
|
142
|
+
subpath: readString(input["subpath"], "", "subpath", issues),
|
|
143
|
+
dashboardSubpath: readString(
|
|
144
|
+
input["dashboardSubpath"],
|
|
145
|
+
"dashboard",
|
|
146
|
+
"dashboardSubpath",
|
|
147
|
+
issues
|
|
148
|
+
),
|
|
149
|
+
previewSubpath: readString(
|
|
150
|
+
input["previewSubpath"],
|
|
151
|
+
"stories",
|
|
152
|
+
"previewSubpath",
|
|
153
|
+
issues
|
|
154
|
+
),
|
|
155
|
+
title: readString(input["title"], "Astrobook", "title", issues),
|
|
156
|
+
css: readStringArray(input["css"], "css", issues),
|
|
157
|
+
head: readString(input["head"], HEAD_COMPONENT, "head", issues),
|
|
158
|
+
home: readHome(input["home"], issues),
|
|
159
|
+
homeContent: readHomeContent(input["homeContent"], issues)
|
|
160
|
+
};
|
|
161
|
+
if (issues.length > 0) {
|
|
162
|
+
const summary = issues.map(
|
|
163
|
+
(issue) => issue.path ? `\xD7 ${issue.message}
|
|
164
|
+
\u2192 at ${issue.path}` : `\xD7 ${issue.message}`
|
|
165
|
+
).join("\n");
|
|
166
|
+
throw new Error(`Invalid Astrobook options:
|
|
167
|
+
${summary}`);
|
|
168
|
+
}
|
|
169
|
+
return resolved;
|
|
170
|
+
}
|
|
171
|
+
export {
|
|
172
|
+
resolveOptions
|
|
173
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { tsPlugin } from "@sveltejs/acorn-typescript";
|
|
2
|
+
import { Parser } from "acorn";
|
|
3
|
+
function getExports(code) {
|
|
4
|
+
const parser = Parser.extend(
|
|
5
|
+
tsPlugin({
|
|
6
|
+
dts: false,
|
|
7
|
+
jsx: {
|
|
8
|
+
allowNamespaces: true,
|
|
9
|
+
allowNamespacedObjects: true
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
);
|
|
13
|
+
const ast = parser.parse(code, {
|
|
14
|
+
sourceType: "module",
|
|
15
|
+
ecmaVersion: "latest",
|
|
16
|
+
allowImportExportEverywhere: true,
|
|
17
|
+
locations: true
|
|
18
|
+
});
|
|
19
|
+
const exports = /* @__PURE__ */ new Set();
|
|
20
|
+
for (const node of ast.body) {
|
|
21
|
+
if (node.type === "ExportNamedDeclaration") {
|
|
22
|
+
for (const specifier of node.specifiers) {
|
|
23
|
+
const { exported } = specifier;
|
|
24
|
+
if (exported.type === "Identifier") {
|
|
25
|
+
exports.add(exported.name);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const { declaration } = node;
|
|
29
|
+
if (declaration?.type === "VariableDeclaration") {
|
|
30
|
+
const { declarations } = declaration;
|
|
31
|
+
for (const declaration2 of declarations) {
|
|
32
|
+
const id = declaration2.id;
|
|
33
|
+
if (id.type === "Identifier") {
|
|
34
|
+
exports.add(id.name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (declaration?.type === "FunctionDeclaration") {
|
|
39
|
+
exports.add(declaration.id.name);
|
|
40
|
+
}
|
|
41
|
+
if (declaration?.type === "ClassDeclaration") {
|
|
42
|
+
exports.add(declaration.id.name);
|
|
43
|
+
}
|
|
44
|
+
} else if (node.type === "ExportDefaultDeclaration") {
|
|
45
|
+
exports.add("default");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return Array.from(exports).sort();
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
getExports
|
|
52
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
function invariant(condition, message = "Unexpected invariant violation") {
|
|
3
|
+
if (!condition) {
|
|
4
|
+
assert(
|
|
5
|
+
condition,
|
|
6
|
+
`[astrobook] Unexpected internal error. Please open an issue at https://github.com/ocavue/astrobook/issues. ${message}`
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
invariant
|
|
12
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const wordSeparators = /[\s\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?@\[\]^_`{|}~]+/;
|
|
2
|
+
const capital_plus_lower = /[A-ZÀ-Ý\u00C0-\u00D6\u00D9-\u00DD][a-zà-ÿ]/g;
|
|
3
|
+
const capitals = /[A-ZÀ-Ý\u00C0-\u00D6\u00D9-\u00DD]+/g;
|
|
4
|
+
function kebabCase(str) {
|
|
5
|
+
str = str.replace(capital_plus_lower, function(match) {
|
|
6
|
+
return " " + (match[0].toLowerCase() || match[0]) + match[1];
|
|
7
|
+
});
|
|
8
|
+
str = str.replace(capitals, function(match) {
|
|
9
|
+
return " " + match.toLowerCase();
|
|
10
|
+
});
|
|
11
|
+
return str.trim().split(wordSeparators).join("-").replace(/^-/, "").replace(/-\s*$/, "");
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
kebabCase as default
|
|
15
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureLeadingSlash,
|
|
3
|
+
ensureTrailingSlash,
|
|
4
|
+
stripLeadingAndTrailingSlashes,
|
|
5
|
+
stripTrailingSlash
|
|
6
|
+
} from "./path.js";
|
|
7
|
+
function createPathBuilder({
|
|
8
|
+
trailingSlash = "ignore"
|
|
9
|
+
}) {
|
|
10
|
+
return function buildPath(...parts) {
|
|
11
|
+
let result = parts.map(stripLeadingAndTrailingSlashes).filter(Boolean).join("/");
|
|
12
|
+
if (trailingSlash === "always") {
|
|
13
|
+
result = ensureTrailingSlash(result);
|
|
14
|
+
} else if (trailingSlash === "never") {
|
|
15
|
+
result = stripTrailingSlash(result);
|
|
16
|
+
}
|
|
17
|
+
return ensureLeadingSlash(result);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
createPathBuilder
|
|
22
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
function ensureLeadingSlash(href) {
|
|
2
|
+
if (href[0] !== "/") href = "/" + href;
|
|
3
|
+
return href;
|
|
4
|
+
}
|
|
5
|
+
function ensureTrailingSlash(href) {
|
|
6
|
+
if (href[href.length - 1] !== "/") href += "/";
|
|
7
|
+
return href;
|
|
8
|
+
}
|
|
9
|
+
function ensureLeadingAndTrailingSlashes(href) {
|
|
10
|
+
href = ensureLeadingSlash(href);
|
|
11
|
+
href = ensureTrailingSlash(href);
|
|
12
|
+
return href;
|
|
13
|
+
}
|
|
14
|
+
function stripLeadingSlash(href) {
|
|
15
|
+
while (href[0] === "/") href = href.slice(1);
|
|
16
|
+
return href;
|
|
17
|
+
}
|
|
18
|
+
function stripTrailingSlash(href) {
|
|
19
|
+
while (href[href.length - 1] === "/") href = href.slice(0, -1);
|
|
20
|
+
return href;
|
|
21
|
+
}
|
|
22
|
+
function stripLeadingAndTrailingSlashes(href) {
|
|
23
|
+
href = stripLeadingSlash(href);
|
|
24
|
+
href = stripTrailingSlash(href);
|
|
25
|
+
return href;
|
|
26
|
+
}
|
|
27
|
+
function stripHtmlExtension(path) {
|
|
28
|
+
const pathWithoutTrailingSlash = stripTrailingSlash(path);
|
|
29
|
+
return pathWithoutTrailingSlash.endsWith(".html") ? pathWithoutTrailingSlash.slice(0, -5) : path;
|
|
30
|
+
}
|
|
31
|
+
function ensureHtmlExtension(path) {
|
|
32
|
+
path = stripLeadingAndTrailingSlashes(path);
|
|
33
|
+
if (!path.endsWith(".html")) {
|
|
34
|
+
path = path ? path + ".html" : "/index.html";
|
|
35
|
+
}
|
|
36
|
+
return ensureLeadingSlash(path);
|
|
37
|
+
}
|
|
38
|
+
function stripExtension(path) {
|
|
39
|
+
const periodIndex = path.lastIndexOf(".");
|
|
40
|
+
return path.slice(0, periodIndex > -1 ? periodIndex : void 0);
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
ensureHtmlExtension,
|
|
44
|
+
ensureLeadingAndTrailingSlashes,
|
|
45
|
+
ensureLeadingSlash,
|
|
46
|
+
ensureTrailingSlash,
|
|
47
|
+
stripExtension,
|
|
48
|
+
stripHtmlExtension,
|
|
49
|
+
stripLeadingAndTrailingSlashes,
|
|
50
|
+
stripLeadingSlash,
|
|
51
|
+
stripLeadingAndTrailingSlashes as stripSlashes,
|
|
52
|
+
stripTrailingSlash
|
|
53
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fdir } from "fdir";
|
|
4
|
+
import slash from "slash";
|
|
5
|
+
import { getExports } from "../utils/get-exports.js";
|
|
6
|
+
import { invariant } from "../utils/invariant.js";
|
|
7
|
+
import kebabCase from "../utils/kebab-case.js";
|
|
8
|
+
async function listStoryFiles(rootDir) {
|
|
9
|
+
invariant(
|
|
10
|
+
path.isAbsolute(rootDir),
|
|
11
|
+
`Root directory must be absolute, but got '${rootDir}'`
|
|
12
|
+
);
|
|
13
|
+
const filePaths = await new fdir().withSymlinks().withFullPaths().normalize().glob("./**/*.stories.{ts,tsx,js,jsx,mts,mtsx,mjs,mjsx}").exclude((dirname) => dirname === "node_modules").crawl(rootDir).withPromise();
|
|
14
|
+
return filePaths.sort();
|
|
15
|
+
}
|
|
16
|
+
async function parseStoryFiles(filePath) {
|
|
17
|
+
const code = await fs.readFile(filePath, "utf-8");
|
|
18
|
+
const exports = getExports(code);
|
|
19
|
+
const defaultExport = exports.includes("default");
|
|
20
|
+
const namedExports = exports.filter((name) => name !== "default");
|
|
21
|
+
return { filePath, defaultExport, namedExports };
|
|
22
|
+
}
|
|
23
|
+
function convertStoryFileToModule(rootDir, file) {
|
|
24
|
+
if (file.namedExports.length === 0) {
|
|
25
|
+
console.warn(`[astrobook] File ${file.filePath} has no named exports`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!file.defaultExport) {
|
|
29
|
+
console.warn(`[astrobook] File ${file.filePath} has no default export`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const relativePath = path.normalize(path.relative(rootDir, file.filePath));
|
|
33
|
+
const relativePathWithoutStories = relativePath.replace(
|
|
34
|
+
/\.stories\.\w+$/i,
|
|
35
|
+
""
|
|
36
|
+
);
|
|
37
|
+
const parts = relativePathWithoutStories.split(path.sep);
|
|
38
|
+
const directory = parts.slice(0, -1).join("/");
|
|
39
|
+
const name = parts.pop();
|
|
40
|
+
invariant(name, `Unexpected file path: ${file.filePath}`);
|
|
41
|
+
const moduleId = directory ? `${directory}/${kebabCase(name)}` : kebabCase(name);
|
|
42
|
+
return {
|
|
43
|
+
id: moduleId,
|
|
44
|
+
name,
|
|
45
|
+
directory,
|
|
46
|
+
importPath: slash(file.filePath),
|
|
47
|
+
stories: file.namedExports.map((name2) => {
|
|
48
|
+
return { id: `${moduleId}/${kebabCase(name2)}`, name: name2 };
|
|
49
|
+
})
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function getStoryModules(rootDir) {
|
|
53
|
+
const storyFilePaths = await listStoryFiles(rootDir);
|
|
54
|
+
const storyFiles = await Promise.all(storyFilePaths.map(parseStoryFiles));
|
|
55
|
+
return storyFiles.map((storyFile) => convertStoryFileToModule(rootDir, storyFile)).filter((module) => !!module);
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
convertStoryFileToModule,
|
|
59
|
+
getStoryModules
|
|
60
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const STORY_MODULES_ID = "virtual:astrobook/story-modules.mjs";
|
|
2
|
+
const STORY_MODULES_RESOLVED_ID = "__virtual_astrobook_story_modules__.mjs";
|
|
3
|
+
const GLOBAL_CONFIG_ID = "virtual:astrobook/global-config.mjs";
|
|
4
|
+
const GLOBAL_CONFIG_RESOLVED_ID = "__virtual_astrobook_user_config__.mjs";
|
|
5
|
+
const COMPONENT_HEAD_ID = "virtual:astrobook/components/head.mjs";
|
|
6
|
+
const COMPONENT_HEAD_RESOLVED_ID = "__virtual_astrobook_COMPONENT_HEAD__.mjs";
|
|
7
|
+
const COMPONENT_HOME_ID = "virtual:astrobook/components/home.mjs";
|
|
8
|
+
const COMPONENT_HOME_RESOLVED_ID = "__virtual_astrobook_COMPONENT_HOME__.mjs";
|
|
9
|
+
const USER_CSS_ID = "virtual:astrobook/user-css.mjs";
|
|
10
|
+
const USER_CSS_RESOLVED_ID = "__virtual_astrobook_user_css__.mjs";
|
|
11
|
+
export {
|
|
12
|
+
COMPONENT_HEAD_ID,
|
|
13
|
+
COMPONENT_HEAD_RESOLVED_ID,
|
|
14
|
+
COMPONENT_HOME_ID,
|
|
15
|
+
COMPONENT_HOME_RESOLVED_ID,
|
|
16
|
+
GLOBAL_CONFIG_ID,
|
|
17
|
+
GLOBAL_CONFIG_RESOLVED_ID,
|
|
18
|
+
STORY_MODULES_ID,
|
|
19
|
+
STORY_MODULES_RESOLVED_ID,
|
|
20
|
+
USER_CSS_ID,
|
|
21
|
+
USER_CSS_RESOLVED_ID
|
|
22
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import slash from "slash";
|
|
3
|
+
import {
|
|
4
|
+
CLIENT_MODULE,
|
|
5
|
+
PREVIEW_PAGE,
|
|
6
|
+
STORY_PAGE,
|
|
7
|
+
WITH_DECORATORS_COMPONENT
|
|
8
|
+
} from "../lib-paths.js";
|
|
9
|
+
import { invariant } from "../utils/invariant.js";
|
|
10
|
+
import { getStoryModules } from "./get-story-modules.js";
|
|
11
|
+
async function getVirtualRoutes(rootDir, codegenDir, logger, dashboardSubpath, previewSubpath, buildPath) {
|
|
12
|
+
const routes = [];
|
|
13
|
+
const storyModules = await getStoryModules(rootDir);
|
|
14
|
+
for (const storyModule of storyModules) {
|
|
15
|
+
logger.debug(
|
|
16
|
+
`Found ${storyModule.stories.length} stories in ${storyModule.importPath}`
|
|
17
|
+
);
|
|
18
|
+
for (const story of storyModule.stories) {
|
|
19
|
+
invariant(
|
|
20
|
+
!story.id.startsWith(".."),
|
|
21
|
+
`Story ID cannot start with '..', but got '${story.id}'`
|
|
22
|
+
);
|
|
23
|
+
invariant(
|
|
24
|
+
!story.id.startsWith("/"),
|
|
25
|
+
`Story ID cannot start with '/', but got '${story.id}'`
|
|
26
|
+
);
|
|
27
|
+
routes.push(
|
|
28
|
+
{
|
|
29
|
+
pattern: buildPath(dashboardSubpath, story.id),
|
|
30
|
+
entrypoint: slash(
|
|
31
|
+
path.resolve(codegenDir, "dashboard", story.id + ".astro")
|
|
32
|
+
),
|
|
33
|
+
storyModule,
|
|
34
|
+
story,
|
|
35
|
+
props: { hasSidebar: true, story: story.id }
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
pattern: buildPath(previewSubpath, story.id),
|
|
39
|
+
entrypoint: slash(
|
|
40
|
+
path.resolve(codegenDir, "stories", story.id + ".astro")
|
|
41
|
+
),
|
|
42
|
+
storyModule,
|
|
43
|
+
story,
|
|
44
|
+
props: { hasSidebar: false, story: story.id }
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
logger.info(`Found ${routes.length} stories in ${storyModules.length} files`);
|
|
50
|
+
return new Map(routes.map((route) => [route.pattern, route]));
|
|
51
|
+
}
|
|
52
|
+
function createVirtualRouteComponent(route) {
|
|
53
|
+
const modName = `astrobook_module_${route.storyModule.id}`.replaceAll(/\W+/g, "_").replaceAll(/_+/g, "_").replace(/_+$/, "");
|
|
54
|
+
const page = route.props.hasSidebar ? STORY_PAGE : PREVIEW_PAGE;
|
|
55
|
+
return `
|
|
56
|
+
---
|
|
57
|
+
// Automatically generated by Astrobook
|
|
58
|
+
|
|
59
|
+
import StoryPage from '${page}';
|
|
60
|
+
import WithDecorators from '${WITH_DECORATORS_COMPONENT}';
|
|
61
|
+
import { parseStoryDefaultExport, parseStoryNamedExport } from '${CLIENT_MODULE}';
|
|
62
|
+
|
|
63
|
+
import * as ${modName} from '${route.storyModule.importPath}';
|
|
64
|
+
|
|
65
|
+
const { isAstroComponent } = parseStoryDefaultExport(${modName}, '${route.storyModule.importPath}');
|
|
66
|
+
const { decorators, args } = parseStoryNamedExport(${modName}, '${route.storyModule.importPath}', '${route.story.name}');
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
<StoryPage story={'${route.props.story}'} hasSidebar={${route.props.hasSidebar}}>
|
|
70
|
+
<WithDecorators decorators={decorators}>
|
|
71
|
+
{isAstroComponent ? (
|
|
72
|
+
<${modName}.default.component {...args} />
|
|
73
|
+
) : (
|
|
74
|
+
<${modName}.default.component {...args} client:load />
|
|
75
|
+
)}
|
|
76
|
+
</WithDecorators>
|
|
77
|
+
</StoryPage>
|
|
78
|
+
`.trim();
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
createVirtualRouteComponent,
|
|
82
|
+
getVirtualRoutes
|
|
83
|
+
};
|