create-shapio 0.2.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 +201 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +311 -0
- package/dist/index.js.map +1 -0
- package/package.json +37 -0
- package/templates/astro/.env.example +10 -0
- package/templates/astro/README.md +31 -0
- package/templates/astro/astro.config.mjs +50 -0
- package/templates/astro/gitignore +4 -0
- package/templates/astro/package.json +31 -0
- package/templates/astro/public/favicon.svg +9 -0
- package/templates/astro/public/robots.txt +3 -0
- package/templates/astro/scripts/incrementalBuild.ts +142 -0
- package/templates/astro/scripts/lib/admin.ts +76 -0
- package/templates/astro/scripts/lib/callback.ts +46 -0
- package/templates/astro/scripts/lib/content.ts +462 -0
- package/templates/astro/scripts/lib/png.ts +47 -0
- package/templates/astro/scripts/reportStatus.ts +40 -0
- package/templates/astro/scripts/seed.ts +396 -0
- package/templates/astro/scripts/smoke.ts +127 -0
- package/templates/astro/scripts/smokeHttp.ts +162 -0
- package/templates/astro/shapio/components/callToAction.json +85 -0
- package/templates/astro/shapio/components/feature.json +46 -0
- package/templates/astro/shapio/components/featureGrid.json +51 -0
- package/templates/astro/shapio/components/gallery.json +52 -0
- package/templates/astro/shapio/components/hero.json +108 -0
- package/templates/astro/shapio/models/article.json +150 -0
- package/templates/astro/shapio/models/author.json +73 -0
- package/templates/astro/shapio/models/page.json +93 -0
- package/templates/astro/shapio/models/siteSettings.json +87 -0
- package/templates/astro/src/layouts/Base.astro +52 -0
- package/templates/astro/src/lib/config.ts +42 -0
- package/templates/astro/src/lib/preview.ts +57 -0
- package/templates/astro/src/lib/render.ts +146 -0
- package/templates/astro/src/lib/shapio.ts +104 -0
- package/templates/astro/src/lib/site.ts +55 -0
- package/templates/astro/src/lib/types.ts +96 -0
- package/templates/astro/src/pages/404.astro +8 -0
- package/templates/astro/src/pages/[locale]/[...slug].astro +28 -0
- package/templates/astro/src/pages/[locale]/articles/[slug].astro +25 -0
- package/templates/astro/src/pages/[locale]/articles/index.astro +22 -0
- package/templates/astro/src/pages/[locale]/colophon.astro +19 -0
- package/templates/astro/src/pages/build.json.ts +9 -0
- package/templates/astro/src/pages/index.astro +18 -0
- package/templates/astro/src/pages/preview.astro +21 -0
- package/templates/astro/src/scripts/preview.ts +47 -0
- package/templates/astro/src/styles/site.css +199 -0
- package/templates/astro/tsconfig.json +17 -0
- package/templates/next/.env.example +15 -0
- package/templates/next/README.md +69 -0
- package/templates/next/gitignore +5 -0
- package/templates/next/next.config.ts +69 -0
- package/templates/next/package.json +34 -0
- package/templates/next/public/favicon.svg +9 -0
- package/templates/next/scripts/lib/admin.ts +76 -0
- package/templates/next/scripts/lib/content.ts +462 -0
- package/templates/next/scripts/lib/png.ts +47 -0
- package/templates/next/scripts/seed.ts +396 -0
- package/templates/next/scripts/smokeHttp.ts +162 -0
- package/templates/next/shapio/components/callToAction.json +85 -0
- package/templates/next/shapio/components/feature.json +46 -0
- package/templates/next/shapio/components/featureGrid.json +51 -0
- package/templates/next/shapio/components/gallery.json +52 -0
- package/templates/next/shapio/components/hero.json +108 -0
- package/templates/next/shapio/models/article.json +150 -0
- package/templates/next/shapio/models/author.json +73 -0
- package/templates/next/shapio/models/page.json +93 -0
- package/templates/next/shapio/models/siteSettings.json +87 -0
- package/templates/next/src/app/[locale]/[[...slug]]/page.tsx +40 -0
- package/templates/next/src/app/[locale]/articles/[slug]/page.tsx +37 -0
- package/templates/next/src/app/[locale]/articles/page.tsx +28 -0
- package/templates/next/src/app/[locale]/colophon/page.tsx +25 -0
- package/templates/next/src/app/[locale]/layout.tsx +77 -0
- package/templates/next/src/app/api/revalidate/route.ts +31 -0
- package/templates/next/src/app/preview/layout.tsx +20 -0
- package/templates/next/src/app/preview/page.tsx +6 -0
- package/templates/next/src/app/site.css +199 -0
- package/templates/next/src/components/ArticleCard/index.tsx +19 -0
- package/templates/next/src/components/ArticleView/index.tsx +40 -0
- package/templates/next/src/components/Byline/index.tsx +15 -0
- package/templates/next/src/components/LocaleSwitch/index.tsx +16 -0
- package/templates/next/src/components/Preview/hooks/usePreviewDraft.ts +53 -0
- package/templates/next/src/components/Preview/index.tsx +33 -0
- package/templates/next/src/components/ResponsiveImage/index.tsx +36 -0
- package/templates/next/src/components/RichText/index.tsx +13 -0
- package/templates/next/src/components/Sections/CallToAction/index.tsx +14 -0
- package/templates/next/src/components/Sections/FeatureGrid/index.tsx +17 -0
- package/templates/next/src/components/Sections/Gallery/index.tsx +20 -0
- package/templates/next/src/components/Sections/Hero/index.tsx +31 -0
- package/templates/next/src/components/Sections/SectionView/index.tsx +30 -0
- package/templates/next/src/components/Sections/index.tsx +19 -0
- package/templates/next/src/lib/articles.ts +5 -0
- package/templates/next/src/lib/config.ts +57 -0
- package/templates/next/src/lib/liveSnapshot.ts +74 -0
- package/templates/next/src/lib/log.ts +4 -0
- package/templates/next/src/lib/preview.ts +56 -0
- package/templates/next/src/lib/revalidation.ts +91 -0
- package/templates/next/src/lib/revalidationTargets.test.ts +67 -0
- package/templates/next/src/lib/revalidationTargets.ts +66 -0
- package/templates/next/src/lib/shapio.ts +75 -0
- package/templates/next/src/lib/site.ts +66 -0
- package/templates/next/src/lib/types.ts +96 -0
- package/templates/next/src/lib/webhookEvent.test.ts +61 -0
- package/templates/next/src/lib/webhookEvent.ts +47 -0
- package/templates/next/src/styles.d.ts +3 -0
- package/templates/next/tsconfig.json +23 -0
- package/templates/sveltekit/.env.example +10 -0
- package/templates/sveltekit/README.md +38 -0
- package/templates/sveltekit/gitignore +4 -0
- package/templates/sveltekit/package.json +38 -0
- package/templates/sveltekit/scripts/lib/admin.ts +76 -0
- package/templates/sveltekit/scripts/lib/content.ts +462 -0
- package/templates/sveltekit/scripts/lib/png.ts +47 -0
- package/templates/sveltekit/scripts/seed.ts +396 -0
- package/templates/sveltekit/scripts/smokeHttp.ts +162 -0
- package/templates/sveltekit/shapio/components/callToAction.json +85 -0
- package/templates/sveltekit/shapio/components/feature.json +46 -0
- package/templates/sveltekit/shapio/components/featureGrid.json +51 -0
- package/templates/sveltekit/shapio/components/gallery.json +52 -0
- package/templates/sveltekit/shapio/components/hero.json +108 -0
- package/templates/sveltekit/shapio/models/article.json +150 -0
- package/templates/sveltekit/shapio/models/author.json +73 -0
- package/templates/sveltekit/shapio/models/page.json +93 -0
- package/templates/sveltekit/shapio/models/siteSettings.json +87 -0
- package/templates/sveltekit/src/app.html +12 -0
- package/templates/sveltekit/src/env.ts +16 -0
- package/templates/sveltekit/src/hooks.server.ts +9 -0
- package/templates/sveltekit/src/lib/articles.ts +5 -0
- package/templates/sveltekit/src/lib/components/ArticleCard.svelte +21 -0
- package/templates/sveltekit/src/lib/components/ArticleView.svelte +43 -0
- package/templates/sveltekit/src/lib/components/Byline.svelte +14 -0
- package/templates/sveltekit/src/lib/components/CallToAction.svelte +15 -0
- package/templates/sveltekit/src/lib/components/FeatureGrid.svelte +22 -0
- package/templates/sveltekit/src/lib/components/Gallery.svelte +25 -0
- package/templates/sveltekit/src/lib/components/Hero.svelte +24 -0
- package/templates/sveltekit/src/lib/components/ResponsiveImage.svelte +30 -0
- package/templates/sveltekit/src/lib/components/RichText.svelte +13 -0
- package/templates/sveltekit/src/lib/components/Sections.svelte +30 -0
- package/templates/sveltekit/src/lib/preview.ts +56 -0
- package/templates/sveltekit/src/lib/server/config.ts +46 -0
- package/templates/sveltekit/src/lib/server/shapio.ts +93 -0
- package/templates/sveltekit/src/lib/site.css +199 -0
- package/templates/sveltekit/src/lib/site.ts +66 -0
- package/templates/sveltekit/src/lib/types.ts +96 -0
- package/templates/sveltekit/src/routes/+layout.ts +3 -0
- package/templates/sveltekit/src/routes/+page.ts +7 -0
- package/templates/sveltekit/src/routes/[locale]/+layout.server.ts +16 -0
- package/templates/sveltekit/src/routes/[locale]/+layout.svelte +32 -0
- package/templates/sveltekit/src/routes/[locale]/[...slug]/+page.server.ts +28 -0
- package/templates/sveltekit/src/routes/[locale]/[...slug]/+page.svelte +15 -0
- package/templates/sveltekit/src/routes/[locale]/articles/+page.server.ts +9 -0
- package/templates/sveltekit/src/routes/[locale]/articles/+page.svelte +17 -0
- package/templates/sveltekit/src/routes/[locale]/articles/[slug]/+page.server.ts +25 -0
- package/templates/sveltekit/src/routes/[locale]/articles/[slug]/+page.svelte +15 -0
- package/templates/sveltekit/src/routes/[locale]/colophon/+page.svelte +17 -0
- package/templates/sveltekit/src/routes/[locale]/colophon/+page.ts +5 -0
- package/templates/sveltekit/src/routes/preview/+page.server.ts +5 -0
- package/templates/sveltekit/src/routes/preview/+page.svelte +67 -0
- package/templates/sveltekit/static/favicon.svg +9 -0
- package/templates/sveltekit/tsconfig.json +14 -0
- package/templates/sveltekit/vite.config.ts +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
import { randomBytes } from "node:crypto";
|
|
7
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
8
|
+
//#region src/templates.ts
|
|
9
|
+
const packageJson = ({ projectName, shapioSpec }) => `${JSON.stringify({
|
|
10
|
+
name: projectName,
|
|
11
|
+
private: true,
|
|
12
|
+
type: "module",
|
|
13
|
+
engines: { node: ">=24" },
|
|
14
|
+
scripts: {
|
|
15
|
+
start: "shapio start",
|
|
16
|
+
migrate: "shapio migrate",
|
|
17
|
+
worker: "shapio worker",
|
|
18
|
+
status: "shapio status"
|
|
19
|
+
},
|
|
20
|
+
dependencies: { shapio: shapioSpec }
|
|
21
|
+
}, null, 2)}\n`;
|
|
22
|
+
const env = ({ databaseUrl, sessionSecret }) => [
|
|
23
|
+
"# Shapio configuration. Every setting is an environment variable; `shapio` loads this file.",
|
|
24
|
+
"# Real environment variables override values here.",
|
|
25
|
+
"NODE_ENV=production",
|
|
26
|
+
"HOST=127.0.0.1",
|
|
27
|
+
"PORT=4300",
|
|
28
|
+
"LOG_LEVEL=info",
|
|
29
|
+
"",
|
|
30
|
+
"# The origin people reach Shapio at; every absolute URL is built from it. Change it when you deploy.",
|
|
31
|
+
"PUBLIC_URL=http://localhost:4300",
|
|
32
|
+
"# BASE_PATH=/cms",
|
|
33
|
+
"",
|
|
34
|
+
"# HTTPS without a reverse proxy: set PUBLIC_URL=https://your.domain and PORT=443, and point these at your",
|
|
35
|
+
"# certificate and key (PEM). Renewed files are picked up without a restart. HTTP_PORT=80 redirects to HTTPS.",
|
|
36
|
+
"# TLS_CERT_FILE=/etc/shapio/tls/fullchain.pem",
|
|
37
|
+
"# TLS_KEY_FILE=/etc/shapio/tls/privkey.pem",
|
|
38
|
+
"# HTTP_PORT=80",
|
|
39
|
+
"",
|
|
40
|
+
"# PostgreSQL >= 16, or sqlite:<path> for a single-process install",
|
|
41
|
+
`DATABASE_URL=${databaseUrl}`,
|
|
42
|
+
"MIGRATE_ON_START=true",
|
|
43
|
+
"",
|
|
44
|
+
"# Signs preview links, private-media URLs and app-user tokens, and encrypts stored secrets.",
|
|
45
|
+
"# Generated for this project; keep it secret, and back it up with the database.",
|
|
46
|
+
`SESSION_SECRET=${sessionSecret}`,
|
|
47
|
+
"",
|
|
48
|
+
"# SETUP_REQUIRE_TOKEN=true # require the logged one-time token for first-run setup (for installs exposed before setup)",
|
|
49
|
+
"",
|
|
50
|
+
"# inline: jobs run inside the server process. dedicated: run `npm run worker` as a second process.",
|
|
51
|
+
"WORKER_MODE=inline",
|
|
52
|
+
"",
|
|
53
|
+
"MEDIA_PATH=./media",
|
|
54
|
+
""
|
|
55
|
+
].join("\n");
|
|
56
|
+
const shapioConfig = () => `// Project configuration for Shapio: code extensions (hooks, custom routes, services, jobs) and custom
|
|
57
|
+
// field editors. Content models are NOT configured here; they are created live in the admin UI and synced
|
|
58
|
+
// with \`shapio schema pull\` / \`shapio schema apply\`. This file is code: changing it, or anything in
|
|
59
|
+
// ./extensions, needs a restart (never a rebuild). Check it with \`npx shapio extensions check\`.
|
|
60
|
+
// TypeScript works as is (erasable syntax only: no enums or namespaces). Guide: documentation/extensions.md in Shapio's repository.
|
|
61
|
+
import { defineConfig } from 'shapio/config';
|
|
62
|
+
|
|
63
|
+
export const config = defineConfig({
|
|
64
|
+
// Lifecycle hooks by model API ID ('*' = every model). before* hooks run inside the write's transaction
|
|
65
|
+
// and may reject it (422 HOOK_REJECTED); after* hooks run after commit, as retried jobs.
|
|
66
|
+
hooks: {
|
|
67
|
+
// article: {
|
|
68
|
+
// beforePublish: ({ data, reject }) => {
|
|
69
|
+
// if (!data?.cover) reject('An article needs a cover image');
|
|
70
|
+
// },
|
|
71
|
+
// },
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// Custom React field editors: file names of ES modules built into ./extensions/editors/, e.g.
|
|
75
|
+
// ['star-rating.js']. The admin loads them at runtime; installing one is a file copy plus a restart.
|
|
76
|
+
editors: [],
|
|
77
|
+
|
|
78
|
+
// Custom Fastify routes, mounted at /api/ext/<prefix>. Mutating routes must declare config.audit.
|
|
79
|
+
// routes: [{ prefix: 'acme', plugin: async (app) => { app.get('/ping', async () => ({ ok: true })); } }],
|
|
80
|
+
|
|
81
|
+
// Services constructed once at startup and shared by hooks, routes and jobs.
|
|
82
|
+
// services: { stats: ({ services }) => ({ articles: () => services.content.count('article') }) },
|
|
83
|
+
|
|
84
|
+
// Background jobs, registered as ext.<name>; enqueue with services.jobs.enqueue('<name>', payload).
|
|
85
|
+
// jobs: { nightlyReport: async ({ services, logger }) => { logger.info('report'); } },
|
|
86
|
+
});
|
|
87
|
+
`;
|
|
88
|
+
const gitignore = () => `node_modules/
|
|
89
|
+
.env
|
|
90
|
+
media/*
|
|
91
|
+
!media/.gitkeep
|
|
92
|
+
`;
|
|
93
|
+
const pm2Ecosystem = ({ projectName }) => `// PM2 process file: pm2 start ecosystem.config.cjs
|
|
94
|
+
// One instance in fork mode, the simplest setup. Several instances on one database are supported too: use the
|
|
95
|
+
// cluster variant below.
|
|
96
|
+
module.exports = {
|
|
97
|
+
apps: [
|
|
98
|
+
{
|
|
99
|
+
name: ${JSON.stringify(projectName)},
|
|
100
|
+
script: 'node_modules/shapio/dist/cli.js',
|
|
101
|
+
args: 'start',
|
|
102
|
+
exec_mode: 'fork',
|
|
103
|
+
instances: 1,
|
|
104
|
+
kill_timeout: 20000,
|
|
105
|
+
env: { NODE_ENV: 'production' },
|
|
106
|
+
},
|
|
107
|
+
// Cluster variant (replace the app above): two processes sharing the port, each with its inline worker.
|
|
108
|
+
// { name: ${JSON.stringify(projectName)}, script: 'node_modules/shapio/dist/cli.js', args: 'start', exec_mode: 'cluster', instances: 2, kill_timeout: 20000, env: { NODE_ENV: 'production' } },
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
`;
|
|
112
|
+
const readme = ({ projectName }) => `# ${projectName}
|
|
113
|
+
|
|
114
|
+
A [Shapio](https://github.com/mybrokengnome/shapio) project.
|
|
115
|
+
|
|
116
|
+
1. Set \`DATABASE_URL\` in \`.env\` to a PostgreSQL (>= 16) database, or to \`sqlite:<path>\` for a
|
|
117
|
+
single-process install.
|
|
118
|
+
2. \`npm run start\`. Migrations run on start. The first boot logs where to create the owner account: open
|
|
119
|
+
\`/admin/\`; the first person to complete Setup becomes the owner.
|
|
120
|
+
3. Under PM2: \`pm2 start ecosystem.config.cjs\`.
|
|
121
|
+
|
|
122
|
+
Content models are edited live in the admin; no restart is needed for modelling. Changing \`shapio.config.ts\`
|
|
123
|
+
or \`extensions/\` (code) needs a restart; \`npx shapio extensions check\` validates them.
|
|
124
|
+
`;
|
|
125
|
+
/** Files to write, relative to the project directory. */
|
|
126
|
+
const renderTemplates = (input) => [
|
|
127
|
+
["package.json", packageJson(input)],
|
|
128
|
+
[".env", env(input)],
|
|
129
|
+
[".env.example", env({
|
|
130
|
+
...input,
|
|
131
|
+
databaseUrl: "postgres://user:password@localhost:5432/shapio",
|
|
132
|
+
sessionSecret: ""
|
|
133
|
+
})],
|
|
134
|
+
["shapio.config.ts", shapioConfig()],
|
|
135
|
+
[".gitignore", gitignore()],
|
|
136
|
+
["ecosystem.config.cjs", pm2Ecosystem(input)],
|
|
137
|
+
["README.md", readme(input)],
|
|
138
|
+
["extensions/.gitkeep", ""],
|
|
139
|
+
["extensions/editors/.gitkeep", ""],
|
|
140
|
+
["media/.gitkeep", ""]
|
|
141
|
+
];
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/scaffold.ts
|
|
144
|
+
var ScaffoldError = class extends Error {
|
|
145
|
+
constructor(message) {
|
|
146
|
+
super(message);
|
|
147
|
+
this.name = "ScaffoldError";
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
/** npm package names: lowercase, URL-safe. Derived from the directory name. */
|
|
151
|
+
const toPackageName = (directory) => basename(resolve(directory)).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[-.]+$/g, "") || "shapio-project";
|
|
152
|
+
/** Writes the project files. Refuses to touch a non-empty directory. Returns the absolute project path. */
|
|
153
|
+
const scaffoldProject = ({ directory, shapioSpec, databaseUrl }) => {
|
|
154
|
+
const root = resolve(directory);
|
|
155
|
+
if (existsSync(root) && readdirSync(root).length > 0) throw new ScaffoldError(`${root} exists and is not empty`);
|
|
156
|
+
const projectName = toPackageName(root);
|
|
157
|
+
const files = renderTemplates({
|
|
158
|
+
projectName,
|
|
159
|
+
shapioSpec,
|
|
160
|
+
databaseUrl: databaseUrl ?? `postgres://postgres:postgres@localhost:5432/${projectName.replace(/[^a-z0-9_]/g, "_")}`,
|
|
161
|
+
sessionSecret: randomBytes(32).toString("base64url")
|
|
162
|
+
});
|
|
163
|
+
for (const [path, content] of files) {
|
|
164
|
+
const target = join(root, path);
|
|
165
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
166
|
+
writeFileSync(target, content, { flag: "wx" });
|
|
167
|
+
}
|
|
168
|
+
return root;
|
|
169
|
+
};
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/starters.ts
|
|
172
|
+
/** The site starters `create-shapio --site` scaffolds (examples/<name> in the repository). */
|
|
173
|
+
const SITE_STARTERS = [
|
|
174
|
+
"astro",
|
|
175
|
+
"next",
|
|
176
|
+
"sveltekit"
|
|
177
|
+
];
|
|
178
|
+
const isSiteStarter = (value) => SITE_STARTERS.includes(value);
|
|
179
|
+
/** npm leaves `.gitignore` out of published packages, so templates carry it under this name. */
|
|
180
|
+
const PACKED_GITIGNORE = "gitignore";
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region src/site.ts
|
|
183
|
+
/**
|
|
184
|
+
* Writes a standalone site project from a starter template: the framework app, the blog's model files in
|
|
185
|
+
* shapio/, and the seed and smoke scripts. Refuses to touch a non-empty directory. Returns the project path.
|
|
186
|
+
*/
|
|
187
|
+
const scaffoldSite = ({ directory, site, templatesDir }) => {
|
|
188
|
+
const root = resolve(directory);
|
|
189
|
+
if (existsSync(root) && readdirSync(root).length > 0) throw new ScaffoldError(`${root} exists and is not empty`);
|
|
190
|
+
const template = join(templatesDir, site);
|
|
191
|
+
if (!existsSync(join(template, "package.json"))) throw new ScaffoldError(`The ${site} template is missing from ${templatesDir}; build create-shapio first`);
|
|
192
|
+
cpSync(template, root, { recursive: true });
|
|
193
|
+
if (existsSync(join(root, "gitignore"))) renameSync(join(root, PACKED_GITIGNORE), join(root, ".gitignore"));
|
|
194
|
+
const packagePath = join(root, "package.json");
|
|
195
|
+
const packageJson = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
196
|
+
writeFileSync(packagePath, `${JSON.stringify({
|
|
197
|
+
...packageJson,
|
|
198
|
+
name: toPackageName(root),
|
|
199
|
+
version: "0.1.0"
|
|
200
|
+
}, null, 2)}\n`);
|
|
201
|
+
return root;
|
|
202
|
+
};
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/main.ts
|
|
205
|
+
const USAGE = `Usage: npx create-shapio <directory> [options]
|
|
206
|
+
|
|
207
|
+
Without --site: a Shapio project (the CMS server). With --site: a website that reads content from Shapio.
|
|
208
|
+
|
|
209
|
+
Options:
|
|
210
|
+
--site <${SITE_STARTERS.join("|")}> Create a site starter instead (a blog: pages, articles, authors)
|
|
211
|
+
--database-url <url> Database URL to put in .env (PostgreSQL, or sqlite:<path> for a single-process
|
|
212
|
+
install; CMS project only)
|
|
213
|
+
--shapio-spec <spec> Dependency spec for shapio (default: ^<this version>; a tarball path works too)
|
|
214
|
+
--no-install Skip npm install
|
|
215
|
+
-h, --help Show this help
|
|
216
|
+
`;
|
|
217
|
+
/** Packed from the repository's examples/ when create-shapio is built (templates/ beside dist/). */
|
|
218
|
+
const TEMPLATES_DIR = fileURLToPath(new URL("../templates/", import.meta.url));
|
|
219
|
+
const ownVersion = () => {
|
|
220
|
+
return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
221
|
+
};
|
|
222
|
+
/** Returns the exit code. */
|
|
223
|
+
const main = (argv, { out, err }) => {
|
|
224
|
+
const { values, positionals } = parseArgs({
|
|
225
|
+
args: [...argv],
|
|
226
|
+
allowPositionals: true,
|
|
227
|
+
allowNegative: true,
|
|
228
|
+
options: {
|
|
229
|
+
"database-url": { type: "string" },
|
|
230
|
+
"shapio-spec": { type: "string" },
|
|
231
|
+
site: { type: "string" },
|
|
232
|
+
install: {
|
|
233
|
+
type: "boolean",
|
|
234
|
+
default: true
|
|
235
|
+
},
|
|
236
|
+
help: {
|
|
237
|
+
type: "boolean",
|
|
238
|
+
short: "h"
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
const [directory] = positionals;
|
|
243
|
+
if (values.help || !directory) {
|
|
244
|
+
(directory ? out : err)(USAGE);
|
|
245
|
+
return values.help ? 0 : 1;
|
|
246
|
+
}
|
|
247
|
+
const site = values.site;
|
|
248
|
+
if (site !== void 0 && !isSiteStarter(site)) {
|
|
249
|
+
err(`create-shapio: --site must be one of ${SITE_STARTERS.join(", ")}\n`);
|
|
250
|
+
return 1;
|
|
251
|
+
}
|
|
252
|
+
if (site !== void 0 && (values["database-url"] || values["shapio-spec"])) {
|
|
253
|
+
err("create-shapio: --database-url and --shapio-spec are for a CMS project, not --site\n");
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
let root;
|
|
257
|
+
try {
|
|
258
|
+
root = site ? scaffoldSite({
|
|
259
|
+
directory,
|
|
260
|
+
site,
|
|
261
|
+
templatesDir: TEMPLATES_DIR
|
|
262
|
+
}) : scaffoldProject({
|
|
263
|
+
directory,
|
|
264
|
+
shapioSpec: values["shapio-spec"] ?? `^${ownVersion()}`,
|
|
265
|
+
...values["database-url"] ? { databaseUrl: values["database-url"] } : {}
|
|
266
|
+
});
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (error instanceof ScaffoldError) {
|
|
269
|
+
err(`create-shapio: ${error.message}\n`);
|
|
270
|
+
return 1;
|
|
271
|
+
}
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
out(`Created a ${site ? `${site} site` : "Shapio project"} in ${root}\n`);
|
|
275
|
+
if (values.install) {
|
|
276
|
+
out("Installing dependencies with npm...\n");
|
|
277
|
+
if (spawnSync("npm", ["install"], {
|
|
278
|
+
cwd: root,
|
|
279
|
+
stdio: "inherit"
|
|
280
|
+
}).status !== 0) {
|
|
281
|
+
err("create-shapio: npm install failed; run it yourself in the project directory\n");
|
|
282
|
+
return 1;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
out(site ? siteNextSteps(directory, site) : `\nNext:\n cd ${directory}\n edit .env (DATABASE_URL)\n npm run start\n`);
|
|
286
|
+
return 0;
|
|
287
|
+
};
|
|
288
|
+
const SERVE_COMMAND = {
|
|
289
|
+
astro: "npm run preview",
|
|
290
|
+
next: "npm run start",
|
|
291
|
+
sveltekit: "npm run preview"
|
|
292
|
+
};
|
|
293
|
+
const siteNextSteps = (directory, site) => [
|
|
294
|
+
"",
|
|
295
|
+
"Next (with a running Shapio; README.md has the details):",
|
|
296
|
+
` cd ${directory}`,
|
|
297
|
+
" SHAPIO_URL=http://localhost:4300 SHAPIO_ADMIN_EMAIL=you@example.com SHAPIO_ADMIN_PASSWORD=... npm run seed",
|
|
298
|
+
" npm run build",
|
|
299
|
+
` ${SERVE_COMMAND[site]}`,
|
|
300
|
+
""
|
|
301
|
+
].join("\n");
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/index.ts
|
|
304
|
+
process.exitCode = main(process.argv.slice(2), {
|
|
305
|
+
out: (text) => process.stdout.write(text),
|
|
306
|
+
err: (text) => process.stderr.write(text)
|
|
307
|
+
});
|
|
308
|
+
//#endregion
|
|
309
|
+
export {};
|
|
310
|
+
|
|
311
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/templates.ts","../src/scaffold.ts","../src/starters.ts","../src/site.ts","../src/main.ts","../src/index.ts"],"sourcesContent":["export type TemplateInput = {\n projectName: string;\n shapioSpec: string;\n databaseUrl: string;\n sessionSecret: string;\n};\n\nconst packageJson = ({ projectName, shapioSpec }: TemplateInput) =>\n `${JSON.stringify(\n {\n name: projectName,\n private: true,\n type: 'module',\n engines: { node: '>=24' },\n scripts: {\n start: 'shapio start',\n migrate: 'shapio migrate',\n worker: 'shapio worker',\n status: 'shapio status',\n },\n dependencies: { shapio: shapioSpec },\n },\n null,\n 2,\n )}\\n`;\n\nconst env = ({ databaseUrl, sessionSecret }: TemplateInput) =>\n [\n '# Shapio configuration. Every setting is an environment variable; `shapio` loads this file.',\n '# Real environment variables override values here.',\n 'NODE_ENV=production',\n 'HOST=127.0.0.1',\n 'PORT=4300',\n 'LOG_LEVEL=info',\n '',\n '# The origin people reach Shapio at; every absolute URL is built from it. Change it when you deploy.',\n 'PUBLIC_URL=http://localhost:4300',\n '# BASE_PATH=/cms',\n '',\n '# HTTPS without a reverse proxy: set PUBLIC_URL=https://your.domain and PORT=443, and point these at your',\n '# certificate and key (PEM). Renewed files are picked up without a restart. HTTP_PORT=80 redirects to HTTPS.',\n '# TLS_CERT_FILE=/etc/shapio/tls/fullchain.pem',\n '# TLS_KEY_FILE=/etc/shapio/tls/privkey.pem',\n '# HTTP_PORT=80',\n '',\n '# PostgreSQL >= 16, or sqlite:<path> for a single-process install',\n `DATABASE_URL=${databaseUrl}`,\n 'MIGRATE_ON_START=true',\n '',\n '# Signs preview links, private-media URLs and app-user tokens, and encrypts stored secrets.',\n '# Generated for this project; keep it secret, and back it up with the database.',\n `SESSION_SECRET=${sessionSecret}`,\n '',\n '# SETUP_REQUIRE_TOKEN=true # require the logged one-time token for first-run setup (for installs exposed before setup)',\n '',\n '# inline: jobs run inside the server process. dedicated: run `npm run worker` as a second process.',\n 'WORKER_MODE=inline',\n '',\n 'MEDIA_PATH=./media',\n '',\n ].join('\\n');\n\nconst shapioConfig =\n () => `// Project configuration for Shapio: code extensions (hooks, custom routes, services, jobs) and custom\n// field editors. Content models are NOT configured here; they are created live in the admin UI and synced\n// with \\`shapio schema pull\\` / \\`shapio schema apply\\`. This file is code: changing it, or anything in\n// ./extensions, needs a restart (never a rebuild). Check it with \\`npx shapio extensions check\\`.\n// TypeScript works as is (erasable syntax only: no enums or namespaces). Guide: documentation/extensions.md in Shapio's repository.\nimport { defineConfig } from 'shapio/config';\n\nexport const config = defineConfig({\n // Lifecycle hooks by model API ID ('*' = every model). before* hooks run inside the write's transaction\n // and may reject it (422 HOOK_REJECTED); after* hooks run after commit, as retried jobs.\n hooks: {\n // article: {\n // beforePublish: ({ data, reject }) => {\n // if (!data?.cover) reject('An article needs a cover image');\n // },\n // },\n },\n\n // Custom React field editors: file names of ES modules built into ./extensions/editors/, e.g.\n // ['star-rating.js']. The admin loads them at runtime; installing one is a file copy plus a restart.\n editors: [],\n\n // Custom Fastify routes, mounted at /api/ext/<prefix>. Mutating routes must declare config.audit.\n // routes: [{ prefix: 'acme', plugin: async (app) => { app.get('/ping', async () => ({ ok: true })); } }],\n\n // Services constructed once at startup and shared by hooks, routes and jobs.\n // services: { stats: ({ services }) => ({ articles: () => services.content.count('article') }) },\n\n // Background jobs, registered as ext.<name>; enqueue with services.jobs.enqueue('<name>', payload).\n // jobs: { nightlyReport: async ({ services, logger }) => { logger.info('report'); } },\n});\n`;\n\nconst gitignore = () => `node_modules/\n.env\nmedia/*\n!media/.gitkeep\n`;\n\nconst pm2Ecosystem = ({ projectName }: TemplateInput) => `// PM2 process file: pm2 start ecosystem.config.cjs\n// One instance in fork mode, the simplest setup. Several instances on one database are supported too: use the\n// cluster variant below.\nmodule.exports = {\n apps: [\n {\n name: ${JSON.stringify(projectName)},\n script: 'node_modules/shapio/dist/cli.js',\n args: 'start',\n exec_mode: 'fork',\n instances: 1,\n kill_timeout: 20000,\n env: { NODE_ENV: 'production' },\n },\n // Cluster variant (replace the app above): two processes sharing the port, each with its inline worker.\n // { name: ${JSON.stringify(projectName)}, script: 'node_modules/shapio/dist/cli.js', args: 'start', exec_mode: 'cluster', instances: 2, kill_timeout: 20000, env: { NODE_ENV: 'production' } },\n ],\n};\n`;\n\nconst readme = ({ projectName }: TemplateInput) =>\n `# ${projectName}\n\nA [Shapio](https://github.com/mybrokengnome/shapio) project.\n\n1. Set \\`DATABASE_URL\\` in \\`.env\\` to a PostgreSQL (>= 16) database, or to \\`sqlite:<path>\\` for a\n single-process install.\n2. \\`npm run start\\`. Migrations run on start. The first boot logs where to create the owner account: open\n \\`/admin/\\`; the first person to complete Setup becomes the owner.\n3. Under PM2: \\`pm2 start ecosystem.config.cjs\\`.\n\nContent models are edited live in the admin; no restart is needed for modelling. Changing \\`shapio.config.ts\\`\nor \\`extensions/\\` (code) needs a restart; \\`npx shapio extensions check\\` validates them.\n`;\n\n/** Files to write, relative to the project directory. */\nexport const renderTemplates = (input: TemplateInput): ReadonlyArray<[path: string, content: string]> => [\n ['package.json', packageJson(input)],\n ['.env', env(input)],\n [\n '.env.example',\n env({ ...input, databaseUrl: 'postgres://user:password@localhost:5432/shapio', sessionSecret: '' }),\n ],\n ['shapio.config.ts', shapioConfig()],\n ['.gitignore', gitignore()],\n ['ecosystem.config.cjs', pm2Ecosystem(input)],\n ['README.md', readme(input)],\n ['extensions/.gitkeep', ''],\n ['extensions/editors/.gitkeep', ''],\n ['media/.gitkeep', ''],\n];\n","import { randomBytes } from 'node:crypto';\nimport { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { renderTemplates } from './templates.js';\n\nexport type ScaffoldOptions = {\n directory: string;\n shapioSpec: string;\n databaseUrl?: string;\n};\n\nexport class ScaffoldError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ScaffoldError';\n }\n}\n\n/** npm package names: lowercase, URL-safe. Derived from the directory name. */\nexport const toPackageName = (directory: string): string =>\n basename(resolve(directory))\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, '-')\n .replace(/^[._-]+|[-.]+$/g, '') || 'shapio-project';\n\n/** Writes the project files. Refuses to touch a non-empty directory. Returns the absolute project path. */\nexport const scaffoldProject = ({ directory, shapioSpec, databaseUrl }: ScaffoldOptions): string => {\n const root = resolve(directory);\n if (existsSync(root) && readdirSync(root).length > 0) {\n throw new ScaffoldError(`${root} exists and is not empty`);\n }\n const projectName = toPackageName(root);\n const files = renderTemplates({\n projectName,\n shapioSpec,\n databaseUrl:\n databaseUrl ?? `postgres://postgres:postgres@localhost:5432/${projectName.replace(/[^a-z0-9_]/g, '_')}`,\n sessionSecret: randomBytes(32).toString('base64url'),\n });\n for (const [path, content] of files) {\n const target = join(root, path);\n mkdirSync(dirname(target), { recursive: true });\n writeFileSync(target, content, { flag: 'wx' });\n }\n return root;\n};\n","/** The site starters `create-shapio --site` scaffolds (examples/<name> in the repository). */\nexport const SITE_STARTERS = ['astro', 'next', 'sveltekit'] as const;\n\nexport type SiteStarter = (typeof SITE_STARTERS)[number];\n\nexport const isSiteStarter = (value: string): value is SiteStarter =>\n (SITE_STARTERS as readonly string[]).includes(value);\n\n/** npm leaves `.gitignore` out of published packages, so templates carry it under this name. */\nexport const PACKED_GITIGNORE = 'gitignore';\n","import { cpSync, existsSync, readdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { ScaffoldError, toPackageName } from './scaffold.js';\nimport { PACKED_GITIGNORE, type SiteStarter } from './starters.js';\n\nexport type ScaffoldSiteOptions = {\n directory: string;\n site: SiteStarter;\n /** The packed templates (templates/ next to dist/ in the published package). */\n templatesDir: string;\n};\n\n/**\n * Writes a standalone site project from a starter template: the framework app, the blog's model files in\n * shapio/, and the seed and smoke scripts. Refuses to touch a non-empty directory. Returns the project path.\n */\nexport const scaffoldSite = ({ directory, site, templatesDir }: ScaffoldSiteOptions): string => {\n const root = resolve(directory);\n if (existsSync(root) && readdirSync(root).length > 0) {\n throw new ScaffoldError(`${root} exists and is not empty`);\n }\n const template = join(templatesDir, site);\n if (!existsSync(join(template, 'package.json'))) {\n throw new ScaffoldError(\n `The ${site} template is missing from ${templatesDir}; build create-shapio first`,\n );\n }\n cpSync(template, root, { recursive: true });\n if (existsSync(join(root, PACKED_GITIGNORE))) {\n renameSync(join(root, PACKED_GITIGNORE), join(root, '.gitignore'));\n }\n const packagePath = join(root, 'package.json');\n const packageJson = JSON.parse(readFileSync(packagePath, 'utf8')) as Record<string, unknown>;\n writeFileSync(\n packagePath,\n `${JSON.stringify({ ...packageJson, name: toPackageName(root), version: '0.1.0' }, null, 2)}\\n`,\n );\n return root;\n};\n","import { spawnSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { parseArgs } from 'node:util';\nimport { scaffoldProject, ScaffoldError } from './scaffold.js';\nimport { scaffoldSite } from './site.js';\nimport { isSiteStarter, SITE_STARTERS, type SiteStarter } from './starters.js';\n\nconst USAGE = `Usage: npx create-shapio <directory> [options]\n\nWithout --site: a Shapio project (the CMS server). With --site: a website that reads content from Shapio.\n\nOptions:\n --site <${SITE_STARTERS.join('|')}> Create a site starter instead (a blog: pages, articles, authors)\n --database-url <url> Database URL to put in .env (PostgreSQL, or sqlite:<path> for a single-process\n install; CMS project only)\n --shapio-spec <spec> Dependency spec for shapio (default: ^<this version>; a tarball path works too)\n --no-install Skip npm install\n -h, --help Show this help\n`;\n\n/** Packed from the repository's examples/ when create-shapio is built (templates/ beside dist/). */\nconst TEMPLATES_DIR = fileURLToPath(new URL('../templates/', import.meta.url));\n\nconst ownVersion = (): string => {\n const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as {\n version: string;\n };\n return packageJson.version;\n};\n\ntype Output = { out: (text: string) => void; err: (text: string) => void };\n\n/** Returns the exit code. */\nexport const main = (argv: readonly string[], { out, err }: Output): number => {\n const { values, positionals } = parseArgs({\n args: [...argv],\n allowPositionals: true,\n allowNegative: true,\n options: {\n 'database-url': { type: 'string' },\n 'shapio-spec': { type: 'string' },\n site: { type: 'string' },\n install: { type: 'boolean', default: true },\n help: { type: 'boolean', short: 'h' },\n },\n });\n const [directory] = positionals;\n if (values.help || !directory) {\n (directory ? out : err)(USAGE);\n return values.help ? 0 : 1;\n }\n const site = values.site;\n if (site !== undefined && !isSiteStarter(site)) {\n err(`create-shapio: --site must be one of ${SITE_STARTERS.join(', ')}\\n`);\n return 1;\n }\n if (site !== undefined && (values['database-url'] || values['shapio-spec'])) {\n err('create-shapio: --database-url and --shapio-spec are for a CMS project, not --site\\n');\n return 1;\n }\n let root: string;\n try {\n root = site\n ? scaffoldSite({ directory, site, templatesDir: TEMPLATES_DIR })\n : scaffoldProject({\n directory,\n shapioSpec: values['shapio-spec'] ?? `^${ownVersion()}`,\n ...(values['database-url'] ? { databaseUrl: values['database-url'] } : {}),\n });\n } catch (error) {\n if (error instanceof ScaffoldError) {\n err(`create-shapio: ${error.message}\\n`);\n return 1;\n }\n throw error;\n }\n out(`Created a ${site ? `${site} site` : 'Shapio project'} in ${root}\\n`);\n if (values.install) {\n out('Installing dependencies with npm...\\n');\n const result = spawnSync('npm', ['install'], { cwd: root, stdio: 'inherit' });\n if (result.status !== 0) {\n err('create-shapio: npm install failed; run it yourself in the project directory\\n');\n return 1;\n }\n }\n out(\n site\n ? siteNextSteps(directory, site)\n : `\\nNext:\\n cd ${directory}\\n edit .env (DATABASE_URL)\\n npm run start\\n`,\n );\n return 0;\n};\n\nconst SERVE_COMMAND: Record<SiteStarter, string> = {\n astro: 'npm run preview',\n next: 'npm run start',\n sveltekit: 'npm run preview',\n};\n\nconst siteNextSteps = (directory: string, site: SiteStarter) =>\n [\n '',\n 'Next (with a running Shapio; README.md has the details):',\n ` cd ${directory}`,\n ' SHAPIO_URL=http://localhost:4300 SHAPIO_ADMIN_EMAIL=you@example.com SHAPIO_ADMIN_PASSWORD=... npm run seed',\n ' npm run build',\n ` ${SERVE_COMMAND[site]}`,\n '',\n ].join('\\n');\n","#!/usr/bin/env node\nimport { main } from './main.js';\n\nprocess.exitCode = main(process.argv.slice(2), {\n out: (text) => process.stdout.write(text),\n err: (text) => process.stderr.write(text),\n});\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-shapio",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Scaffold a new Shapio project (npx create-shapio my-cms) or a site starter (--site astro|next|sveltekit)",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://shapio.dev",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/mybrokengnome/shapio.git",
|
|
10
|
+
"directory": "packages/create-shapio"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/mybrokengnome/shapio/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=24"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"create-shapio": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"templates"
|
|
25
|
+
],
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "24.19.1",
|
|
28
|
+
"tsdown": "0.23.0",
|
|
29
|
+
"tsx": "4.23.15",
|
|
30
|
+
"typescript": "6.0.3",
|
|
31
|
+
"vitest": "5.0.3"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsdown && node --import tsx scripts/packTemplates.ts",
|
|
35
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Copy to .env, or let `npm run seed` write SHAPIO_URL and SHAPIO_DELIVERY_TOKEN for you.
|
|
2
|
+
SHAPIO_URL=http://localhost:4300
|
|
3
|
+
# A read-only delivery token (Shapio admin: Settings → API tokens, a delivery role).
|
|
4
|
+
SHAPIO_DELIVERY_TOKEN=
|
|
5
|
+
# Optional: build this publication snapshot instead of the latest one.
|
|
6
|
+
# SHAPIO_SNAPSHOT=42
|
|
7
|
+
# Optional: the site key on a multi-site Shapio (default: the token's site, else the primary site).
|
|
8
|
+
# SHAPIO_SITE=marketing
|
|
9
|
+
# Optional: the origin the browser calls for draft previews (defaults to SHAPIO_URL).
|
|
10
|
+
# PUBLIC_SHAPIO_URL=https://cms.example.com
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Shapio starter: Astro
|
|
2
|
+
|
|
3
|
+
A static [Astro](https://astro.build) site built from [Shapio](https://github.com/mybrokengnome/shapio)
|
|
4
|
+
content: pages made of sections, a journal of articles with an author, and the `siteSettings` singleton, in
|
|
5
|
+
English and French. Every build reads one pinned publication snapshot. Drafts preview through `/preview/`.
|
|
6
|
+
|
|
7
|
+
You need a running Shapio (>= 0.1) with an owner account.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install
|
|
11
|
+
SHAPIO_URL=http://localhost:4300 SHAPIO_ADMIN_EMAIL=you@example.com SHAPIO_ADMIN_PASSWORD='…' npm run seed
|
|
12
|
+
npm run build # → dist/
|
|
13
|
+
npm run preview # serves dist/ in the foreground (Ctrl+C stops it)
|
|
14
|
+
npm run smoke # checks dist/ and the preview path (needs SHAPIO_ADMIN_EMAIL/PASSWORD)
|
|
15
|
+
npm run smoke:http # checks the served pages over HTTP (in another terminal, while preview runs)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`npm run seed` applies the models in `shapio/` to Shapio live, creates the content and writes `SHAPIO_URL` and
|
|
19
|
+
`SHAPIO_DELIVERY_TOKEN` to `.env`. Other settings (`SHAPIO_SNAPSHOT`, `SHAPIO_SITE`, `PUBLIC_SHAPIO_URL`) are
|
|
20
|
+
in `.env.example`. Astro's telemetry is turned off in the npm scripts.
|
|
21
|
+
|
|
22
|
+
Preview: the seed creates a deployment connection named **Preview** that opens drafts at
|
|
23
|
+
`http://localhost:4321/preview/` (set `SITE_URL` when seeding for another address). Add that origin to Shapio's
|
|
24
|
+
`CORS_ORIGINS`. In Shapio's preview pane, clicking the title, body or cover focuses that field and saves
|
|
25
|
+
re-render the draft ([visual editing](https://github.com/mybrokengnome/shapio/blob/main/documentation/visual-editing.md)). `astro.config.mjs` sends `frame-ancestors` (only the site and
|
|
26
|
+
Shapio may frame it) and writes it to `dist/_headers`.
|
|
27
|
+
|
|
28
|
+
Guides: [site starters](https://github.com/mybrokengnome/shapio/blob/main/documentation/starters.md) and the
|
|
29
|
+
[walkthrough](https://github.com/mybrokengnome/shapio/blob/main/documentation/example-site.md) (model, preview,
|
|
30
|
+
deploy to Cloudflare Pages, build callbacks). Inside the Shapio repository, run the scripts with
|
|
31
|
+
`pnpm --filter example-astro <script>` after `pnpm build`.
|