@cratis/create-cast 0.0.1 → 0.1.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/README.md +7 -1
- package/dist/PublicProfile.js +3 -0
- package/dist/ScaffoldInput.js +3 -0
- package/dist/castYaml.js +131 -0
- package/dist/fetchProfile.js +31 -0
- package/dist/generateFiles.js +133 -0
- package/dist/index.js +78 -0
- package/dist/parseArguments.js +33 -0
- package/package.json +44 -8
package/README.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
# @cratis/create-cast
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Creates a [Cratis Cast](https://cast.cratis.io) site from your public GitHub profile.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm create @cratis/cast@latest my-site -- --github your-login
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Options: `[directory]`, `--github <login>`, `--domain <domain>`, `--yes`, `--help`. See [getting started](https://github.com/Cratis/Cast/blob/main/Documentation/getting-started.md).
|
package/dist/castYaml.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
/** A YAML double-quoted scalar. JSON strings are valid YAML. */
|
|
4
|
+
const quote = (value) => JSON.stringify(value);
|
|
5
|
+
/** The public URL the site will have: the custom domain, or the user's GitHub Pages address. */
|
|
6
|
+
export const siteUrl = (input) => input.domain ? `https://${input.domain}` : `https://${input.profile.login.toLowerCase()}.github.io`;
|
|
7
|
+
/** cast.yaml: prefilled from the public profile, with a commented example for every section. */
|
|
8
|
+
export function castYaml(input) {
|
|
9
|
+
const { profile } = input;
|
|
10
|
+
const name = profile.name ?? profile.login;
|
|
11
|
+
const firstLine = profile.bio?.split(/(?<=[.!?])\s/)[0];
|
|
12
|
+
const lines = [
|
|
13
|
+
'# yaml-language-server: $schema=https://unpkg.com/@cratis/cast/cast.schema.json',
|
|
14
|
+
'#',
|
|
15
|
+
'# Your Cast site. Every page is built from this file; edit it and the site follows.',
|
|
16
|
+
'# Text fields can use *emphasis* (the italic serif accent), **strong**, `code` and [links](https://example.com).',
|
|
17
|
+
'# Reference: https://github.com/Cratis/Cast/blob/main/Documentation/cast-yaml.md',
|
|
18
|
+
'',
|
|
19
|
+
'site:',
|
|
20
|
+
` url: ${quote(siteUrl(input))}`,
|
|
21
|
+
' # description: One sentence for search engines; defaults to your tagline and intro.',
|
|
22
|
+
' # source: https://github.com/you/your-site',
|
|
23
|
+
'',
|
|
24
|
+
'person:',
|
|
25
|
+
` name: ${quote(name)}`,
|
|
26
|
+
` handle: ${quote(profile.login)}`,
|
|
27
|
+
` title: ${quote(firstLine ?? 'Software engineer')} # the role line under your name`,
|
|
28
|
+
...(profile.location ? [` location: ${quote(profile.location)}`] : [' # location: Oslo, Norway']),
|
|
29
|
+
' # email: you@example.com',
|
|
30
|
+
...(profile.avatarUrl ? [` photo: ${quote(profile.avatarUrl)} # or a local file such as ./avatar.jpeg`] : [' # photo: ./avatar.jpeg']),
|
|
31
|
+
' links:',
|
|
32
|
+
` github: ${quote(`https://github.com/${profile.login}`)}`,
|
|
33
|
+
...(profile.blog && /linkedin\.com/i.test(profile.blog) ? [` linkedin: ${quote(profile.blog)}`] : []),
|
|
34
|
+
...(profile.blog && !/linkedin\.com/i.test(profile.blog) ? [` website: ${quote(profile.blog)}`, ' # linkedin: https://www.linkedin.com/in/you/'] : []),
|
|
35
|
+
...(!profile.blog ? [' # linkedin: https://www.linkedin.com/in/you/'] : []),
|
|
36
|
+
' tagline: "I build software that *remembers.*" # the hero headline; *asterisks* mark the accent',
|
|
37
|
+
` intro: ${quote(profile.bio ?? 'One paragraph about what you build and why. Keep it specific.')}`,
|
|
38
|
+
' # about:',
|
|
39
|
+
' # - A paragraph for the "about" column and the CV summary.',
|
|
40
|
+
' # interests: [Event Modeling, Event Sourcing, Developer experience]',
|
|
41
|
+
' # languages: [English (fluent)]',
|
|
42
|
+
'',
|
|
43
|
+
'# The "now" read model: ordered rows. YYYY-MM values show as months.',
|
|
44
|
+
'# now:',
|
|
45
|
+
'# role: Senior Engineer',
|
|
46
|
+
'# company: Example Co',
|
|
47
|
+
'# since: 2025-06',
|
|
48
|
+
'# building: [Something with Cratis]',
|
|
49
|
+
'',
|
|
50
|
+
'# Big numbers. `live` replaces the value with a build-time figure from GitHub.',
|
|
51
|
+
'# stats:',
|
|
52
|
+
'# - { value: 1000, suffix: "+", label: contributions on GitHub in the last year, live: contributionsLastYear }',
|
|
53
|
+
'# - { value: 10, suffix: "+", label: merged PRs in the Cratis org, live: mergedPullRequests }',
|
|
54
|
+
'',
|
|
55
|
+
'# Your career as an event stream. Types are past-tense PascalCase. `role` sets the current role;',
|
|
56
|
+
'# `endsRole` closes the open role at that organization. The page replays these in order.',
|
|
57
|
+
'# career:',
|
|
58
|
+
'# events:',
|
|
59
|
+
'# - date: 2018-07',
|
|
60
|
+
'# type: EmploymentStarted',
|
|
61
|
+
'# title: Software Engineer',
|
|
62
|
+
'# org: Example Co',
|
|
63
|
+
'# orgUrl: https://example.com',
|
|
64
|
+
'# role: Software Engineer',
|
|
65
|
+
'# summary: What you built and what changed because of it.',
|
|
66
|
+
'# skills: [C#, Event Sourcing]',
|
|
67
|
+
'# - date: 2024-06',
|
|
68
|
+
'# type: TalkGiven',
|
|
69
|
+
'# title: "Talk: Event Modeling in practice"',
|
|
70
|
+
'# summary: Where and what about.',
|
|
71
|
+
'',
|
|
72
|
+
'# What you do at Cratis. The section only renders when this key is present.',
|
|
73
|
+
'# cratis:',
|
|
74
|
+
'# role: I maintain the Chronicle Go client and help teams adopt event modeling.',
|
|
75
|
+
'# repos:',
|
|
76
|
+
'# - { name: Chronicle.Go, url: https://github.com/Cratis/Chronicle.Go, kind: Client · Go, group: client, desc: Idiomatic Go client for Chronicle. }',
|
|
77
|
+
'# highlights: [A change you landed that you are proud of]',
|
|
78
|
+
'',
|
|
79
|
+
'# projects:',
|
|
80
|
+
'# - name: my-tool',
|
|
81
|
+
'# url: https://github.com/you/my-tool',
|
|
82
|
+
'# category: cli',
|
|
83
|
+
'# pitch: What it does, in one sentence.',
|
|
84
|
+
'# features: [One, Two, Three]',
|
|
85
|
+
'# stack: [Rust]',
|
|
86
|
+
'# install: brew install you/tap/my-tool',
|
|
87
|
+
'# projectCategories:',
|
|
88
|
+
'# - { id: cli, label: CLI }',
|
|
89
|
+
'',
|
|
90
|
+
'# talks:',
|
|
91
|
+
'# - { title: Event sourcing in practice, host: Example Meetup, date: 2025-11-12, url: https://example.com/talk, summary: What it covered. }',
|
|
92
|
+
'# community:',
|
|
93
|
+
'# - { title: Event Modeling Conference, detail: Attended, date: 2025-10 }',
|
|
94
|
+
'',
|
|
95
|
+
'# Posts from RSS/Atom feeds or a Starlight Blog author page (such as blog.cratis.io).',
|
|
96
|
+
'# writing:',
|
|
97
|
+
'# sources:',
|
|
98
|
+
'# - rss: https://example.com/rss.xml',
|
|
99
|
+
'# - starlightBlogAuthor: https://blog.cratis.io/authors/your-name/',
|
|
100
|
+
'# themes:',
|
|
101
|
+
'# - { id: event-sourcing, label: Event sourcing, tags: [event-sourcing, cqrs] }',
|
|
102
|
+
'',
|
|
103
|
+
'# agentic:',
|
|
104
|
+
'# pitch: How you work with AI coding agents.',
|
|
105
|
+
'# items:',
|
|
106
|
+
'# - { name: my-setup, url: https://github.com/you/my-setup, kind: Agent configuration, text: What it is. }',
|
|
107
|
+
'',
|
|
108
|
+
'# skills:',
|
|
109
|
+
'# Languages: [C#, TypeScript]',
|
|
110
|
+
'# Architecture: [Event Modeling, Event Sourcing, CQRS]',
|
|
111
|
+
'# education:',
|
|
112
|
+
'# - { degree: Bachelor of Computer Science, school: Example University, from: "2015", to: "2018" }',
|
|
113
|
+
'# certifications:',
|
|
114
|
+
'# - { title: Example certificate, issuer: Example, year: "2023" }',
|
|
115
|
+
'',
|
|
116
|
+
'# Public GitHub data captured at build time (a token is read from GITHUB_TOKEN or GH_TOKEN).',
|
|
117
|
+
'github:',
|
|
118
|
+
' orgs: [Cratis] # organizations whose merged public pull requests you want counted',
|
|
119
|
+
' # releases: [you/my-tool] # latest release versions your projects',
|
|
120
|
+
' # stars: [Cratis/Chronicle]',
|
|
121
|
+
'',
|
|
122
|
+
'theme:',
|
|
123
|
+
' accent: "#8b5cf6" # your accent; Cratis violet by default',
|
|
124
|
+
' mode: dark # dark, light or system for first-time visitors',
|
|
125
|
+
'',
|
|
126
|
+
'# Section order. Leave it out to show every section that has data.',
|
|
127
|
+
'# sections: [hero, now, stats, career, cratis, activity, projects, writing, talks, skills, contact]',
|
|
128
|
+
'',
|
|
129
|
+
];
|
|
130
|
+
return lines.join('\n');
|
|
131
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
const clean = (value) => (value && value.trim() ? value.trim() : undefined);
|
|
4
|
+
/** Reads a public GitHub profile. Without network or for an unknown login it returns just the login. */
|
|
5
|
+
export async function fetchProfile(login, fetchImplementation = fetch) {
|
|
6
|
+
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
7
|
+
try {
|
|
8
|
+
const response = await fetchImplementation(`https://api.github.com/users/${encodeURIComponent(login)}`, {
|
|
9
|
+
headers: { accept: 'application/vnd.github+json', 'user-agent': 'cratis-create-cast', ...(token ? { authorization: `bearer ${token}` } : {}) },
|
|
10
|
+
signal: AbortSignal.timeout(8000),
|
|
11
|
+
});
|
|
12
|
+
if (!response.ok)
|
|
13
|
+
return { profile: { login }, found: false };
|
|
14
|
+
const user = await response.json();
|
|
15
|
+
const blog = clean(user.blog);
|
|
16
|
+
return {
|
|
17
|
+
found: true,
|
|
18
|
+
profile: {
|
|
19
|
+
login: clean(user.login) ?? login,
|
|
20
|
+
...(clean(user.name) ? { name: clean(user.name) } : {}),
|
|
21
|
+
...(clean(user.bio) ? { bio: clean(user.bio) } : {}),
|
|
22
|
+
...(clean(user.location) ? { location: clean(user.location) } : {}),
|
|
23
|
+
...(blog ? { blog: /^https?:\/\//.test(blog) ? blog : `https://${blog}` } : {}),
|
|
24
|
+
...(clean(user.avatar_url) ? { avatarUrl: clean(user.avatar_url) } : {}),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return { profile: { login }, found: false };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
import { castYaml, siteUrl } from './castYaml.js';
|
|
4
|
+
/** A package name npm accepts, derived from the directory name. */
|
|
5
|
+
export const packageName = (directory) => directory.split(/[\\/]/).filter(Boolean).pop().toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^[._-]+|[._-]+$/g, '') || 'cast-site';
|
|
6
|
+
const packageJson = (input) => `${JSON.stringify({
|
|
7
|
+
name: packageName(input.directory),
|
|
8
|
+
version: '0.0.0',
|
|
9
|
+
private: true,
|
|
10
|
+
type: 'module',
|
|
11
|
+
description: `${input.profile.name ?? input.profile.login}'s Cast site.`,
|
|
12
|
+
scripts: {
|
|
13
|
+
dev: 'astro dev',
|
|
14
|
+
build: 'astro build',
|
|
15
|
+
preview: 'astro preview',
|
|
16
|
+
},
|
|
17
|
+
engines: { node: '>=23.0.0' },
|
|
18
|
+
dependencies: {
|
|
19
|
+
'@cratis/cast': input.castVersion,
|
|
20
|
+
astro: '^7.2.0',
|
|
21
|
+
},
|
|
22
|
+
}, null, 2)}\n`;
|
|
23
|
+
const astroConfig = `import cast from '@cratis/cast';
|
|
24
|
+
import { defineConfig } from 'astro/config';
|
|
25
|
+
|
|
26
|
+
// cast() reads ./cast.yaml, captures public GitHub data at build time and adds every page.
|
|
27
|
+
export default defineConfig({
|
|
28
|
+
integrations: [cast()],
|
|
29
|
+
});
|
|
30
|
+
`;
|
|
31
|
+
const workflow = `name: Build and deploy Cast site
|
|
32
|
+
|
|
33
|
+
on:
|
|
34
|
+
push:
|
|
35
|
+
branches: [main]
|
|
36
|
+
pull_request:
|
|
37
|
+
# Hourly rebuild keeps the public GitHub numbers, the feed and the Cast badge current.
|
|
38
|
+
schedule:
|
|
39
|
+
- cron: '23 * * * *'
|
|
40
|
+
workflow_dispatch:
|
|
41
|
+
|
|
42
|
+
# Least privilege by default; only the deploy job can write to Pages.
|
|
43
|
+
permissions:
|
|
44
|
+
contents: read
|
|
45
|
+
|
|
46
|
+
concurrency:
|
|
47
|
+
group: \${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'pages' }}
|
|
48
|
+
cancel-in-progress: \${{ github.event_name == 'pull_request' }}
|
|
49
|
+
|
|
50
|
+
jobs:
|
|
51
|
+
build:
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
steps:
|
|
54
|
+
- name: Checkout
|
|
55
|
+
uses: actions/checkout@v4
|
|
56
|
+
- name: Setup Node
|
|
57
|
+
uses: actions/setup-node@v4
|
|
58
|
+
with:
|
|
59
|
+
node-version: 23.x
|
|
60
|
+
- name: Install
|
|
61
|
+
run: if [ -f package-lock.json ]; then npm ci; else npm install --no-audit --no-fund; fi
|
|
62
|
+
- name: Build
|
|
63
|
+
run: npm run build
|
|
64
|
+
env:
|
|
65
|
+
# Read-only: Cast captures public GitHub data at build time and never fails the build on it.
|
|
66
|
+
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
67
|
+
- name: Upload artifact
|
|
68
|
+
if: github.event_name != 'pull_request'
|
|
69
|
+
uses: actions/upload-pages-artifact@v3
|
|
70
|
+
with:
|
|
71
|
+
path: ./dist
|
|
72
|
+
|
|
73
|
+
deploy:
|
|
74
|
+
if: github.event_name != 'pull_request'
|
|
75
|
+
needs: build
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
environment:
|
|
78
|
+
name: github-pages
|
|
79
|
+
url: \${{ steps.deployment.outputs.page_url }}
|
|
80
|
+
permissions:
|
|
81
|
+
pages: write
|
|
82
|
+
id-token: write
|
|
83
|
+
steps:
|
|
84
|
+
- name: Deploy to GitHub Pages
|
|
85
|
+
id: deployment
|
|
86
|
+
uses: actions/deploy-pages@v4
|
|
87
|
+
`;
|
|
88
|
+
const gitignore = `node_modules/
|
|
89
|
+
dist/
|
|
90
|
+
.astro/
|
|
91
|
+
.DS_Store
|
|
92
|
+
# .cast/snapshot.json is the last good GitHub data. Commit it so builds without a token still show numbers.
|
|
93
|
+
`;
|
|
94
|
+
const readme = (input) => `# ${input.profile.name ?? input.profile.login}
|
|
95
|
+
|
|
96
|
+
My [Cratis Cast](https://cast.cratis.io) site: one [\`cast.yaml\`](cast.yaml), built with [@cratis/cast](https://github.com/Cratis/Cast).
|
|
97
|
+
|
|
98
|
+
## Next steps
|
|
99
|
+
|
|
100
|
+
1. Install and run it:
|
|
101
|
+
|
|
102
|
+
\`\`\`sh
|
|
103
|
+
npm install
|
|
104
|
+
npm run dev # http://localhost:4321
|
|
105
|
+
\`\`\`
|
|
106
|
+
|
|
107
|
+
2. Fill in [\`cast.yaml\`](cast.yaml). Every section has a commented example; uncomment what you want. Your editor completes fields from the schema.
|
|
108
|
+
3. Build with live GitHub numbers (optional locally, automatic in CI):
|
|
109
|
+
|
|
110
|
+
\`\`\`sh
|
|
111
|
+
GITHUB_TOKEN=$(gh auth token) npm run build
|
|
112
|
+
\`\`\`
|
|
113
|
+
|
|
114
|
+
4. Push to GitHub and turn on Pages: **Settings → Pages → Source: GitHub Actions**. The workflow in [\`.github/workflows/publish.yaml\`](.github/workflows/publish.yaml) builds on every push and every hour.
|
|
115
|
+
${input.domain ? `5. Point the DNS for \`${input.domain}\` at GitHub Pages; [\`public/CNAME\`](public/CNAME) already names it.\n` : ''}
|
|
116
|
+
The site will be at ${siteUrl(input)}.
|
|
117
|
+
|
|
118
|
+
## Your badge
|
|
119
|
+
|
|
120
|
+
Your site shows the Cast tier its build resolved, as a claim: Core and Partner from the [Cast registry](https://github.com/Cratis/Cast/blob/main/Registry/README.md), Contributor from merged public pull requests in the Cratis organization. The directory, built by Cratis, is the source of truth; link to your verification page: https://cast.cratis.io/${input.profile.login.toLowerCase()}
|
|
121
|
+
`;
|
|
122
|
+
/** Every file of a new Cast site, by relative path. */
|
|
123
|
+
export function generateFiles(input) {
|
|
124
|
+
return {
|
|
125
|
+
'cast.yaml': castYaml(input),
|
|
126
|
+
'package.json': packageJson(input),
|
|
127
|
+
'astro.config.mjs': astroConfig,
|
|
128
|
+
'.github/workflows/publish.yaml': workflow,
|
|
129
|
+
'.gitignore': gitignore,
|
|
130
|
+
'README.md': readme(input),
|
|
131
|
+
...(input.domain ? { 'public/CNAME': `${input.domain}\n` } : {}),
|
|
132
|
+
};
|
|
133
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
3
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
4
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { createInterface } from 'node:readline/promises';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { fetchProfile } from './fetchProfile.js';
|
|
10
|
+
import { generateFiles } from './generateFiles.js';
|
|
11
|
+
import { parseArguments } from './parseArguments.js';
|
|
12
|
+
const usage = `Create a Cratis Cast site.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
npm create @cratis/cast@latest [directory] -- [--github <login>] [--domain <domain>] [--yes]
|
|
16
|
+
yarn create @cratis/cast [directory] [--github <login>] [--domain <domain>] [--yes]
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--github <login> Your GitHub login. Prefills cast.yaml from your public profile.
|
|
20
|
+
--domain <domain> Custom domain, for example www.example.com. Writes public/CNAME.
|
|
21
|
+
--yes, -y Do not ask; use the options given and defaults for the rest.
|
|
22
|
+
--help, -h Show this help.
|
|
23
|
+
`;
|
|
24
|
+
const ownVersion = () => {
|
|
25
|
+
const packageFile = fileURLToPath(new URL('../package.json', import.meta.url));
|
|
26
|
+
return JSON.parse(readFileSync(packageFile, 'utf8')).version;
|
|
27
|
+
};
|
|
28
|
+
const isEmptyDirectory = (directory) => !existsSync(directory) || readdirSync(directory).filter(entry => entry !== '.git').length === 0;
|
|
29
|
+
async function main() {
|
|
30
|
+
const options = parseArguments(process.argv.slice(2));
|
|
31
|
+
if (options.help) {
|
|
32
|
+
process.stdout.write(usage);
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
const interactive = !options.yes && process.stdin.isTTY === true;
|
|
36
|
+
const prompt = interactive ? createInterface({ input: process.stdin, output: process.stdout }) : undefined;
|
|
37
|
+
const ask = async (question, fallback) => {
|
|
38
|
+
if (!prompt)
|
|
39
|
+
return fallback;
|
|
40
|
+
const answer = (await prompt.question(`${question}${fallback ? ` (${fallback})` : ''}: `)).trim();
|
|
41
|
+
return answer || fallback;
|
|
42
|
+
};
|
|
43
|
+
try {
|
|
44
|
+
const github = options.github ?? await ask('Your GitHub login', '');
|
|
45
|
+
if (!github || !/^[A-Za-z0-9-]+$/.test(github)) {
|
|
46
|
+
process.stderr.write('A GitHub login is needed: pass --github <login>.\n');
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
const directory = options.directory ?? await ask('Directory for the site', `${github.toLowerCase()}-cast`);
|
|
50
|
+
const domain = options.domain ?? (await ask('Custom domain (leave empty for GitHub Pages)', '') || undefined);
|
|
51
|
+
const target = path.resolve(directory);
|
|
52
|
+
if (!isEmptyDirectory(target)) {
|
|
53
|
+
process.stderr.write(`${target} is not empty. Choose a new or empty directory.\n`);
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
process.stdout.write(`Reading the public GitHub profile of ${github}...\n`);
|
|
57
|
+
const { profile, found } = await fetchProfile(github);
|
|
58
|
+
if (!found)
|
|
59
|
+
process.stdout.write('Could not read the profile (offline or unknown login); cast.yaml gets placeholders instead.\n');
|
|
60
|
+
const files = generateFiles({ directory, profile, domain, castVersion: `^${ownVersion()}` });
|
|
61
|
+
for (const [relative, content] of Object.entries(files)) {
|
|
62
|
+
const file = path.join(target, relative);
|
|
63
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
64
|
+
await writeFile(file, content);
|
|
65
|
+
}
|
|
66
|
+
const relative = path.relative(process.cwd(), target);
|
|
67
|
+
const shown = relative === '' ? '.' : relative.startsWith('..') ? target : relative;
|
|
68
|
+
process.stdout.write(`\nYour Cast site is in ${shown}.\n\nNext:\n cd ${shown}\n npm install\n npm run dev\n\nThen fill in cast.yaml. The README lists how to publish it.\n`);
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
prompt?.close();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
main().then(code => process.exit(code), error => {
|
|
76
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
/** Parses `[dir] [--github <login>] [--domain <domain>] [--yes] [--help]`; `--flag=value` works too. */
|
|
4
|
+
export function parseArguments(argv) {
|
|
5
|
+
const result = { yes: false, help: false };
|
|
6
|
+
for (let index = 0; index < argv.length; index++) {
|
|
7
|
+
const argument = argv[index];
|
|
8
|
+
const [flag, inline] = argument.startsWith('--') ? argument.split(/=(.*)/s, 2) : [argument, undefined];
|
|
9
|
+
const value = () => inline ?? argv[++index];
|
|
10
|
+
switch (flag) {
|
|
11
|
+
case '--github':
|
|
12
|
+
result.github = value();
|
|
13
|
+
break;
|
|
14
|
+
case '--domain':
|
|
15
|
+
result.domain = value();
|
|
16
|
+
break;
|
|
17
|
+
case '--yes':
|
|
18
|
+
case '-y':
|
|
19
|
+
result.yes = true;
|
|
20
|
+
break;
|
|
21
|
+
case '--help':
|
|
22
|
+
case '-h':
|
|
23
|
+
result.help = true;
|
|
24
|
+
break;
|
|
25
|
+
default:
|
|
26
|
+
if (!argument.startsWith('-') && result.directory === undefined)
|
|
27
|
+
result.directory = argument;
|
|
28
|
+
else
|
|
29
|
+
throw new Error(`Unknown option '${argument}'. Run with --help for usage.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,45 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
"name": "@cratis/create-cast",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "npm create @cratis/cast: scaffold a Cratis Cast profile site from your GitHub profile.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cratis",
|
|
7
|
+
"cast",
|
|
8
|
+
"create",
|
|
9
|
+
"astro",
|
|
10
|
+
"portfolio",
|
|
11
|
+
"cv"
|
|
12
|
+
],
|
|
13
|
+
"author": "Cratis",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"homepage": "https://cast.cratis.io/join",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/Cratis/Cast.git",
|
|
19
|
+
"directory": "Source/CreateCast"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=23.0.0"
|
|
27
|
+
},
|
|
28
|
+
"bin": "dist/index.js",
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"!dist/**/for_*/**",
|
|
32
|
+
"!dist/**/*.map"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"clean": "yarn g:clean",
|
|
36
|
+
"build": "yarn g:tsc -p tsconfig.build.json && chmod +x dist/index.js",
|
|
37
|
+
"lint": "yarn g:lint",
|
|
38
|
+
"lint:ci": "yarn g:lint:ci",
|
|
39
|
+
"test": "yarn g:test",
|
|
40
|
+
"ci": "yarn g:ci"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@cratis/cast": "0.1.0"
|
|
44
|
+
}
|
|
45
|
+
}
|