@janmarkuslanger/astro-strapi-platform 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/core/blocks.d.ts +34 -0
  2. package/dist/core/blocks.js +7 -0
  3. package/dist/core/index.d.ts +6 -0
  4. package/dist/core/index.js +6 -0
  5. package/dist/core/llms.d.ts +3 -0
  6. package/dist/core/llms.js +42 -0
  7. package/dist/core/paths.d.ts +9 -0
  8. package/dist/core/paths.js +13 -0
  9. package/dist/core/repository.d.ts +18 -0
  10. package/dist/core/repository.js +1 -0
  11. package/dist/core/seo.d.ts +13 -0
  12. package/dist/core/seo.js +34 -0
  13. package/dist/core/types.d.ts +49 -0
  14. package/dist/core/types.js +9 -0
  15. package/dist/strapi/adapter.d.ts +91 -0
  16. package/dist/strapi/adapter.js +129 -0
  17. package/dist/strapi/index.d.ts +11 -0
  18. package/dist/strapi/index.js +10 -0
  19. package/dist/strapi/mapper.d.ts +74 -0
  20. package/dist/strapi/mapper.js +100 -0
  21. package/dist/strapi-kit/config.d.ts +102 -0
  22. package/dist/strapi-kit/config.js +95 -0
  23. package/dist/strapi-kit/index.d.ts +5 -0
  24. package/dist/strapi-kit/index.js +18 -0
  25. package/dist/strapi-kit/package.json +1 -0
  26. package/dist/strapi-kit/permissions.d.ts +15 -0
  27. package/dist/strapi-kit/permissions.js +33 -0
  28. package/dist/strapi-kit/platform.d.ts +8 -0
  29. package/dist/strapi-kit/platform.js +21 -0
  30. package/dist/strapi-kit/rebuild.d.ts +47 -0
  31. package/dist/strapi-kit/rebuild.js +123 -0
  32. package/dist/strapi-kit/schemas-cli.d.ts +5 -0
  33. package/dist/strapi-kit/schemas-cli.js +104 -0
  34. package/dist/strapi-kit/schemas.d.ts +56 -0
  35. package/dist/strapi-kit/schemas.js +180 -0
  36. package/dist/strapi-kit/seed.d.ts +52 -0
  37. package/dist/strapi-kit/seed.js +113 -0
  38. package/package.json +68 -4
  39. package/schemas/api/global/content-types/global/schema.json +40 -0
  40. package/schemas/api/global/controllers/global.ts +3 -0
  41. package/schemas/api/global/routes/global.ts +3 -0
  42. package/schemas/api/global/services/global.ts +3 -0
  43. package/schemas/api/page/content-types/page/schema.json +39 -0
  44. package/schemas/api/page/controllers/page.ts +3 -0
  45. package/schemas/api/page/routes/page.ts +3 -0
  46. package/schemas/api/page/services/page.ts +3 -0
  47. package/schemas/api/shared-block/content-types/shared-block/schema.json +28 -0
  48. package/schemas/api/shared-block/controllers/shared-block.ts +3 -0
  49. package/schemas/api/shared-block/routes/shared-block.ts +3 -0
  50. package/schemas/api/shared-block/services/shared-block.ts +3 -0
  51. package/schemas/components/blocks/reference.json +15 -0
  52. package/schemas/components/shared/link.json +9 -0
  53. package/schemas/components/shared/seo.json +9 -0
  54. package/src/astro/BlockRenderer.astro +29 -0
  55. package/src/astro/analytics.ts +76 -0
  56. package/src/astro/consent-ui.ts +108 -0
  57. package/src/astro/consent.ts +78 -0
  58. package/src/astro/index.ts +4 -0
  59. package/src/astro/routes.ts +76 -0
  60. package/README.md +0 -3
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.syncSchemas = syncSchemas;
7
+ exports.main = main;
8
+ /**
9
+ * Writes the platform schemas into the Strapi app in the current directory.
10
+ * Runs before `strapi develop` and `strapi build` (the app's pre-scripts), so
11
+ * a version bump of this package is all it takes to update them.
12
+ *
13
+ * Called through `require(…).main()`, not an npm `bin`: in the platform repo
14
+ * `dist/` does not exist yet at `npm ci`, and npm then links no bin at all.
15
+ *
16
+ * src/** generated, committed, never edited by hand
17
+ * schema-extensions/<name>.json the site's additions (info, new attributes)
18
+ * platform-schemas.json manifest of what was generated last
19
+ */
20
+ const node_fs_1 = __importDefault(require("node:fs"));
21
+ const node_path_1 = __importDefault(require("node:path"));
22
+ const schemas_1 = require("./schemas");
23
+ const MANIFEST = "platform-schemas.json";
24
+ const PLATFORM_DIR = node_path_1.default.join(__dirname, "..", "..", "schemas");
25
+ function readTree(root, dir = "") {
26
+ const files = {};
27
+ for (const entry of node_fs_1.default.readdirSync(node_path_1.default.join(root, dir), { withFileTypes: true })) {
28
+ const rel = dir ? `${dir}/${entry.name}` : entry.name;
29
+ if (entry.isDirectory())
30
+ Object.assign(files, readTree(root, rel));
31
+ else
32
+ files[rel] = node_fs_1.default.readFileSync(node_path_1.default.join(root, rel), "utf8");
33
+ }
34
+ return files;
35
+ }
36
+ const readIfExists = (file) => node_fs_1.default.existsSync(file) ? node_fs_1.default.readFileSync(file, "utf8") : undefined;
37
+ function parseJson(file) {
38
+ try {
39
+ return JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
40
+ }
41
+ catch (error) {
42
+ throw new schemas_1.SchemaError([`${file}: ${error.message}`]);
43
+ }
44
+ }
45
+ /** Removes `dir` and its parents up to `stop` while they are empty. */
46
+ function pruneEmpty(dir, stop) {
47
+ while (dir !== stop && node_fs_1.default.existsSync(dir) && node_fs_1.default.readdirSync(dir).length === 0) {
48
+ node_fs_1.default.rmdirSync(dir);
49
+ dir = node_path_1.default.dirname(dir);
50
+ }
51
+ }
52
+ function syncSchemas(appDir, platformDir = PLATFORM_DIR) {
53
+ const src = node_path_1.default.join(appDir, "src");
54
+ const platform = readTree(platformDir);
55
+ const blocksDir = node_path_1.default.join(src, "components", "blocks");
56
+ const siteBlocks = node_fs_1.default.existsSync(blocksDir)
57
+ ? node_fs_1.default
58
+ .readdirSync(blocksDir)
59
+ .filter((f) => f.endsWith(".json") && !(`components/blocks/${f}` in platform))
60
+ .map((f) => f.slice(0, -".json".length))
61
+ : [];
62
+ const extensionsDir = node_path_1.default.join(appDir, "schema-extensions");
63
+ const extensions = {};
64
+ if (node_fs_1.default.existsSync(extensionsDir)) {
65
+ for (const f of node_fs_1.default.readdirSync(extensionsDir).filter((f) => f.endsWith(".json"))) {
66
+ extensions[f.slice(0, -".json".length)] = parseJson(node_path_1.default.join(extensionsDir, f));
67
+ }
68
+ }
69
+ const generated = (0, schemas_1.buildSchemas)({ platform, siteBlocks, extensions });
70
+ const manifestFile = node_path_1.default.join(appDir, MANIFEST);
71
+ const manifest = node_fs_1.default.existsSync(manifestFile) ? parseJson(manifestFile).files : {};
72
+ const disk = {};
73
+ for (const rel of new Set([...Object.keys(generated), ...Object.keys(manifest)])) {
74
+ disk[rel] = readIfExists(node_path_1.default.join(src, rel));
75
+ }
76
+ const plan = (0, schemas_1.planSync)(manifest, disk, generated);
77
+ for (const [rel, content] of Object.entries(plan.write)) {
78
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(node_path_1.default.join(src, rel)), { recursive: true });
79
+ node_fs_1.default.writeFileSync(node_path_1.default.join(src, rel), content);
80
+ }
81
+ for (const rel of plan.remove) {
82
+ node_fs_1.default.rmSync(node_path_1.default.join(src, rel));
83
+ pruneEmpty(node_path_1.default.dirname(node_path_1.default.join(src, rel)), src);
84
+ }
85
+ node_fs_1.default.writeFileSync(manifestFile, `${JSON.stringify({ $comment: "Generated by platform-schemas. Do not edit.", files: plan.manifest }, null, 2)}\n`);
86
+ return { written: Object.keys(plan.write).length, removed: plan.remove.length };
87
+ }
88
+ function main() {
89
+ try {
90
+ const { written, removed } = syncSchemas(process.cwd());
91
+ console.log(`platform-schemas: ${written} written, ${removed} removed.`);
92
+ }
93
+ catch (error) {
94
+ if (error instanceof schemas_1.SchemaError) {
95
+ console.error("platform-schemas: refusing to write platform schemas:");
96
+ for (const problem of error.problems)
97
+ console.error(` - ${problem}`);
98
+ }
99
+ else {
100
+ console.error(error);
101
+ }
102
+ process.exit(1);
103
+ }
104
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Platform schemas for a site's Strapi app. Pure: the CLI (schemas-cli.ts)
3
+ * reads and writes the files, everything deciding *what* to write lives here.
4
+ *
5
+ * The platform owns its content types and components (page, shared-block,
6
+ * global, shared.*, blocks.reference). A site extends them through
7
+ * `schema-extensions/<name>.json` and never edits the generated files —
8
+ * otherwise the next platform version could not update them.
9
+ */
10
+ /** Paths relative to the Strapi app's `src/`, posix separators. */
11
+ export type FileMap = Record<string, string>;
12
+ export interface SchemaInputs {
13
+ /** The platform's schema files, as shipped in this package. */
14
+ platform: FileMap;
15
+ /** Names of the site's own block components (`src/components/blocks/<name>.json`). */
16
+ siteBlocks: string[];
17
+ /** Parsed `schema-extensions/<name>.json`, keyed by `<name>`. */
18
+ extensions: Record<string, unknown>;
19
+ }
20
+ /** Path → sha256 of each generated file, as last written. */
21
+ export type Manifest = Record<string, string>;
22
+ export interface SyncPlan {
23
+ write: FileMap;
24
+ remove: string[];
25
+ manifest: Manifest;
26
+ }
27
+ /** Every problem found, so one run reports them all. */
28
+ export declare class SchemaError extends Error {
29
+ readonly problems: string[];
30
+ constructor(problems: string[]);
31
+ }
32
+ /**
33
+ * Extension name of a platform schema file: the content type's singular name
34
+ * (`page`) or the component uid (`shared.seo`). The dot keeps them apart.
35
+ */
36
+ export declare function extensionName(path: string): string | null;
37
+ /**
38
+ * The files the site's `src/` must contain: platform schemas with the site's
39
+ * extensions applied and every site block added to the platform's dynamic
40
+ * zones. Blocks the platform lists itself (`blocks.reference` on pages) keep
41
+ * their place; `shared-block` lists none, so a shared block can never hold a
42
+ * reference and cycles stay impossible by construction.
43
+ */
44
+ export declare function buildSchemas({ platform, siteBlocks, extensions }: SchemaInputs): FileMap;
45
+ export declare const hash: (content: string) => string;
46
+ /**
47
+ * What to write and remove so `src/` matches `generated`, refusing to
48
+ * overwrite anything a person changed. A generated file that differs from its
49
+ * manifest hash was edited by hand (e.g. in the Content-Type Builder); a file
50
+ * the manifest does not know but that differs from the platform's was never
51
+ * generated. Both would be lost silently, so both stop the run instead.
52
+ *
53
+ * @param disk current content of every path in `generated` or `manifest`,
54
+ * `undefined` where the file does not exist
55
+ */
56
+ export declare function planSync(manifest: Manifest, disk: Record<string, string | undefined>, generated: FileMap): SyncPlan;
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hash = exports.SchemaError = void 0;
4
+ exports.extensionName = extensionName;
5
+ exports.buildSchemas = buildSchemas;
6
+ exports.planSync = planSync;
7
+ const node_crypto_1 = require("node:crypto");
8
+ /** Every problem found, so one run reports them all. */
9
+ class SchemaError extends Error {
10
+ problems;
11
+ constructor(problems) {
12
+ super(problems.join("\n"));
13
+ this.problems = problems;
14
+ this.name = "SchemaError";
15
+ }
16
+ }
17
+ exports.SchemaError = SchemaError;
18
+ const EXTENSION_KEYS = new Set(["info", "attributes"]);
19
+ // Not singularName/pluralName: they name the REST routes the frontend reads.
20
+ const EXTENSION_INFO_KEYS = new Set(["displayName", "description", "icon"]);
21
+ const CONTENT_TYPE = /^api\/[^/]+\/content-types\/([^/]+)\/schema\.json$/;
22
+ const COMPONENT = /^components\/([^/]+)\/([^/]+)\.json$/;
23
+ /**
24
+ * Extension name of a platform schema file: the content type's singular name
25
+ * (`page`) or the component uid (`shared.seo`). The dot keeps them apart.
26
+ */
27
+ function extensionName(path) {
28
+ const ct = CONTENT_TYPE.exec(path);
29
+ if (ct)
30
+ return ct[1];
31
+ const component = COMPONENT.exec(path);
32
+ if (component)
33
+ return `${component[1]}.${component[2]}`;
34
+ return null;
35
+ }
36
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
37
+ function applyExtension(name, schema, extension, problems) {
38
+ const where = `schema-extensions/${name}.json`;
39
+ if (!isObject(extension)) {
40
+ problems.push(`${where}: must be a JSON object with "info" and/or "attributes".`);
41
+ return;
42
+ }
43
+ for (const key of Object.keys(extension)) {
44
+ if (!EXTENSION_KEYS.has(key)) {
45
+ problems.push(`${where}: "${key}" cannot be extended; only "info" and "attributes".`);
46
+ }
47
+ }
48
+ if (extension.info !== undefined) {
49
+ if (!isObject(extension.info)) {
50
+ problems.push(`${where}: "info" must be an object.`);
51
+ }
52
+ else {
53
+ for (const [key, value] of Object.entries(extension.info)) {
54
+ if (!EXTENSION_INFO_KEYS.has(key)) {
55
+ problems.push(`${where}: info.${key} is owned by the platform; a site may set ${[...EXTENSION_INFO_KEYS].join(", ")}.`);
56
+ }
57
+ else if (typeof value !== "string") {
58
+ problems.push(`${where}: info.${key} must be a string.`);
59
+ }
60
+ else {
61
+ schema.info[key] = value;
62
+ }
63
+ }
64
+ }
65
+ }
66
+ if (extension.attributes !== undefined) {
67
+ if (!isObject(extension.attributes)) {
68
+ problems.push(`${where}: "attributes" must be an object.`);
69
+ }
70
+ else {
71
+ for (const [key, attribute] of Object.entries(extension.attributes)) {
72
+ if (key in schema.attributes) {
73
+ problems.push(`${where}: attribute "${key}" belongs to the platform and cannot be redefined; add a field with another name.`);
74
+ }
75
+ else if (!isObject(attribute) || typeof attribute.type !== "string") {
76
+ problems.push(`${where}: attribute "${key}" needs a "type".`);
77
+ }
78
+ else {
79
+ schema.attributes[key] = attribute;
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
86
+ /**
87
+ * The files the site's `src/` must contain: platform schemas with the site's
88
+ * extensions applied and every site block added to the platform's dynamic
89
+ * zones. Blocks the platform lists itself (`blocks.reference` on pages) keep
90
+ * their place; `shared-block` lists none, so a shared block can never hold a
91
+ * reference and cycles stay impossible by construction.
92
+ */
93
+ function buildSchemas({ platform, siteBlocks, extensions }) {
94
+ const problems = [];
95
+ const files = {};
96
+ const known = new Set();
97
+ const blockUids = [...siteBlocks].sort().map((name) => `blocks.${name}`);
98
+ for (const [path, content] of Object.entries(platform)) {
99
+ const name = extensionName(path);
100
+ if (!name) {
101
+ files[path] = content;
102
+ continue;
103
+ }
104
+ known.add(name);
105
+ const schema = JSON.parse(content);
106
+ if (CONTENT_TYPE.test(path)) {
107
+ for (const attribute of Object.values(schema.attributes)) {
108
+ if (attribute.type === "dynamiczone") {
109
+ attribute.components = [...new Set([...attribute.components, ...blockUids])];
110
+ }
111
+ }
112
+ }
113
+ if (name in extensions)
114
+ applyExtension(name, schema, extensions[name], problems);
115
+ files[path] = json(schema);
116
+ }
117
+ for (const name of Object.keys(extensions)) {
118
+ if (!known.has(name)) {
119
+ problems.push(`schema-extensions/${name}.json: no platform schema "${name}". Known: ${[...known].sort().join(", ")}.`);
120
+ }
121
+ }
122
+ for (const name of siteBlocks) {
123
+ if (`components/blocks/${name}.json` in platform) {
124
+ problems.push(`components/blocks/${name}.json is a platform component; give the site block another name.`);
125
+ }
126
+ }
127
+ if (problems.length)
128
+ throw new SchemaError(problems);
129
+ return files;
130
+ }
131
+ const hash = (content) => (0, node_crypto_1.createHash)("sha256").update(content).digest("hex");
132
+ exports.hash = hash;
133
+ /**
134
+ * What to write and remove so `src/` matches `generated`, refusing to
135
+ * overwrite anything a person changed. A generated file that differs from its
136
+ * manifest hash was edited by hand (e.g. in the Content-Type Builder); a file
137
+ * the manifest does not know but that differs from the platform's was never
138
+ * generated. Both would be lost silently, so both stop the run instead.
139
+ *
140
+ * @param disk current content of every path in `generated` or `manifest`,
141
+ * `undefined` where the file does not exist
142
+ */
143
+ function planSync(manifest, disk, generated) {
144
+ const problems = [];
145
+ const edited = (path) => `src/${path} was changed by hand. Platform schemas are generated: move the change into schema-extensions/, then delete the file and run again.`;
146
+ const write = {};
147
+ for (const [path, content] of Object.entries(generated)) {
148
+ const current = disk[path];
149
+ if (current === undefined) {
150
+ write[path] = content;
151
+ }
152
+ else if (path in manifest) {
153
+ if ((0, exports.hash)(current) !== manifest[path])
154
+ problems.push(edited(path));
155
+ else if (current !== content)
156
+ write[path] = content;
157
+ }
158
+ else if (current !== content) {
159
+ problems.push(`src/${path} exists but was not generated. It is a platform schema now: move site changes into schema-extensions/, then delete the file and run again.`);
160
+ }
161
+ }
162
+ const remove = [];
163
+ for (const [path, recorded] of Object.entries(manifest)) {
164
+ if (path in generated)
165
+ continue;
166
+ const current = disk[path];
167
+ if (current === undefined)
168
+ continue;
169
+ if ((0, exports.hash)(current) !== recorded)
170
+ problems.push(edited(path));
171
+ else
172
+ remove.push(path);
173
+ }
174
+ if (problems.length)
175
+ throw new SchemaError(problems);
176
+ const next = {};
177
+ for (const path of Object.keys(generated).sort())
178
+ next[path] = (0, exports.hash)(generated[path]);
179
+ return { write, remove, manifest: next };
180
+ }
@@ -0,0 +1,52 @@
1
+ import type { Core } from "@strapi/strapi";
2
+ /**
3
+ * Initial content from fixtures.
4
+ *
5
+ * Runs exactly once, into an empty database: the fixtures are the starting
6
+ * state of a fresh installation, nothing more. As soon as any content exists —
7
+ * from an earlier seed or written in the panel — seeding steps aside and never
8
+ * touches the database again. The CMS is then the single source of truth, and
9
+ * nobody can lose an afternoon of editing to a restart.
10
+ *
11
+ * There is deliberately NO force flag. If you want one, you want a database
12
+ * reset instead (local SQLite: delete apps/cms/.tmp).
13
+ *
14
+ * Structure (content types, blocks, fields) still comes from code and ships
15
+ * with every deploy; only the *content* is one-way and one-time.
16
+ *
17
+ * Untouched either way: the media library (files are matched by name and
18
+ * reused, so re-seeding never piles up duplicates) and anything a visitor
19
+ * created, such as form submissions.
20
+ *
21
+ * All steps run in ONE transaction. A failure rolls everything back, so the
22
+ * next boot sees an empty database and seeds again — a half-seeded database
23
+ * would count as "has content" and never be completed.
24
+ *
25
+ * Extending: add a step for your content type, in dependency order. Every
26
+ * step's type counts as "the site already has content".
27
+ */
28
+ export interface SeedStep {
29
+ /** Content type, e.g. "api::page.page". */
30
+ uid: string;
31
+ /** Fixture entries — an array, or a single object for a single type. */
32
+ entries: any[] | any;
33
+ /**
34
+ * Makes created entries referable by later steps: `{ [token]: entry[key] }`
35
+ * anywhere in a later fixture becomes the created documentId. Example:
36
+ * `{ key: "name", token: "__sharedBlock" }`.
37
+ */
38
+ ref?: {
39
+ key: string;
40
+ token: string;
41
+ };
42
+ }
43
+ export interface SeedOptions {
44
+ /** In dependency order: referenced types before the types that use them. */
45
+ steps: SeedStep[];
46
+ /**
47
+ * Images to upload, referenced in fixtures as `{ "__file": "name.jpg" }`.
48
+ * Default: `data/uploads` relative to the CMS app.
49
+ */
50
+ uploadsDir?: string;
51
+ }
52
+ export declare function seedContent(strapi: Core.Strapi, options: SeedOptions): Promise<void>;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.seedContent = seedContent;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ async function hasContent(strapi, uids) {
10
+ for (const uid of uids) {
11
+ const count = await strapi.documents(uid).count({});
12
+ if (count > 0)
13
+ return true;
14
+ }
15
+ return false;
16
+ }
17
+ /** Uploads images from apps/cms/data/uploads, indexed by file name. */
18
+ async function uploadSeedImages(strapi, uploadDir) {
19
+ const uploaded = new Map();
20
+ if (!node_fs_1.default.existsSync(uploadDir))
21
+ return uploaded;
22
+ const uploadService = strapi.plugin("upload").service("upload");
23
+ for (const fileName of node_fs_1.default.readdirSync(uploadDir)) {
24
+ if (!/\.(jpe?g|png|webp|svg)$/i.test(fileName))
25
+ continue;
26
+ // Reuse a file already in the library — re-seeding against an emptied
27
+ // content table must not create duplicates.
28
+ const existing = await strapi.db
29
+ .query("plugin::upload.file")
30
+ .findOne({ where: { name: fileName } });
31
+ if (existing) {
32
+ uploaded.set(fileName, existing);
33
+ continue;
34
+ }
35
+ const filePath = node_path_1.default.join(uploadDir, fileName);
36
+ const ext = node_path_1.default.extname(fileName).toLowerCase();
37
+ const mimetype = ext === ".png" ? "image/png"
38
+ : ext === ".webp" ? "image/webp"
39
+ : ext === ".svg" ? "image/svg+xml"
40
+ : "image/jpeg";
41
+ const [file] = await uploadService.upload({
42
+ data: {},
43
+ files: {
44
+ filepath: filePath,
45
+ originalFilename: fileName,
46
+ mimetype,
47
+ size: node_fs_1.default.statSync(filePath).size,
48
+ },
49
+ });
50
+ uploaded.set(fileName, file);
51
+ }
52
+ return uploaded;
53
+ }
54
+ /**
55
+ * Replaces `{ "__file": "name.jpg" }` in the fixtures with the uploaded file's
56
+ * id, so fixtures can reference images without knowing database ids.
57
+ */
58
+ function resolveFiles(value, files) {
59
+ if (Array.isArray(value))
60
+ return value.map((v) => resolveFiles(v, files));
61
+ if (value && typeof value === "object") {
62
+ if (typeof value.__file === "string") {
63
+ return files.get(value.__file)?.id ?? null;
64
+ }
65
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, resolveFiles(v, files)]));
66
+ }
67
+ return value;
68
+ }
69
+ async function seedContent(strapi, options) {
70
+ if (await hasContent(strapi, options.steps.map((step) => step.uid))) {
71
+ strapi.log.info("Seed: content already exists — skipped.");
72
+ return;
73
+ }
74
+ strapi.log.info("Seed: empty database — creating initial content.");
75
+ const files = await uploadSeedImages(strapi, options.uploadsDir ?? node_path_1.default.resolve(process.cwd(), "data", "uploads"));
76
+ /** token → (fixture key → created documentId) */
77
+ const refs = new Map();
78
+ /** Turns `{ "<token>": "Name" }` into the created entry's documentId. */
79
+ const resolveRefs = (value) => {
80
+ if (Array.isArray(value))
81
+ return value.map(resolveRefs);
82
+ if (value && typeof value === "object") {
83
+ for (const [token, ids] of refs) {
84
+ if (typeof value[token] === "string")
85
+ return ids.get(value[token]) ?? null;
86
+ }
87
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, resolveRefs(v)]));
88
+ }
89
+ return value;
90
+ };
91
+ // Document service calls join this transaction. Uploads stay outside: they
92
+ // touch the filesystem, and a retry reuses them by name.
93
+ await strapi.db.transaction(async () => {
94
+ for (const step of options.steps) {
95
+ // Types with draft & publish are created published; for the others a
96
+ // status means nothing.
97
+ const draftAndPublish = Boolean(strapi.contentType(step.uid)?.options?.draftAndPublish);
98
+ const entries = Array.isArray(step.entries) ? step.entries : [step.entries];
99
+ const ids = new Map();
100
+ for (const entry of entries) {
101
+ const created = await strapi.documents(step.uid).create({
102
+ data: resolveRefs(resolveFiles(entry, files)),
103
+ ...(draftAndPublish ? { status: "published" } : {}),
104
+ });
105
+ if (step.ref)
106
+ ids.set(String(entry[step.ref.key]), created.documentId);
107
+ }
108
+ if (step.ref)
109
+ refs.set(step.ref.token, ids);
110
+ }
111
+ });
112
+ strapi.log.info("Seed: done.");
113
+ }
package/package.json CHANGED
@@ -1,6 +1,70 @@
1
1
  {
2
2
  "name": "@janmarkuslanger/astro-strapi-platform",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Astro + Strapi platform: domain model and block contract, Strapi adapter, Astro rendering and consent, Strapi schemas and bootstrap helpers.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "keywords": [
8
+ "astro-component",
9
+ "strapi"
10
+ ],
11
+ "exports": {
12
+ "./core": {
13
+ "types": "./dist/core/index.d.ts",
14
+ "import": "./dist/core/index.js"
15
+ },
16
+ "./strapi": {
17
+ "types": "./dist/strapi/index.d.ts",
18
+ "import": "./dist/strapi/index.js"
19
+ },
20
+ "./astro": "./src/astro/index.ts",
21
+ "./astro/BlockRenderer.astro": "./src/astro/BlockRenderer.astro",
22
+ "./strapi-kit": {
23
+ "types": "./dist/strapi-kit/index.d.ts",
24
+ "default": "./dist/strapi-kit/index.js"
25
+ },
26
+ "./strapi-kit/schemas-cli": "./dist/strapi-kit/schemas-cli.js"
27
+ },
28
+ "typesVersions": {
29
+ "*": {
30
+ "strapi-kit": [
31
+ "dist/strapi-kit/index.d.ts"
32
+ ]
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "src/astro",
38
+ "schemas"
39
+ ],
40
+ "scripts": {
41
+ "prepack": "npm run build",
42
+ "build": "tsc -p tsconfig.json && tsc -p tsconfig.strapi-kit.json && node -e \"require('node:fs').copyFileSync('src/strapi-kit/package.json', 'dist/strapi-kit/package.json')\""
43
+ },
44
+ "dependencies": {
45
+ "marked": "^14.1.3"
46
+ },
47
+ "peerDependencies": {
48
+ "astro": "^4.16.0",
49
+ "@strapi/strapi": "^5.0.0"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "astro": {
53
+ "optional": true
54
+ },
55
+ "@strapi/strapi": {
56
+ "optional": true
57
+ }
58
+ },
59
+ "devDependencies": {
60
+ "typescript": "^5.6.3"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "repository": {
66
+ "type": "git",
67
+ "url": "git+https://github.com/janmarkuslanger/astro-strapi-template.git",
68
+ "directory": "packages/astro-strapi-platform"
69
+ }
70
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "kind": "singleType",
3
+ "collectionName": "globals",
4
+ "info": {
5
+ "singularName": "global",
6
+ "pluralName": "globals",
7
+ "displayName": "Global settings"
8
+ },
9
+ "options": {
10
+ "draftAndPublish": false
11
+ },
12
+ "pluginOptions": {
13
+ "content-type-builder": {
14
+ "visible": false
15
+ }
16
+ },
17
+ "attributes": {
18
+ "siteName": {
19
+ "type": "string",
20
+ "required": true
21
+ },
22
+ "claim": {
23
+ "type": "string"
24
+ },
25
+ "email": {
26
+ "type": "email",
27
+ "required": true
28
+ },
29
+ "navigation": {
30
+ "type": "component",
31
+ "repeatable": true,
32
+ "component": "shared.link"
33
+ },
34
+ "footerLinks": {
35
+ "type": "component",
36
+ "repeatable": true,
37
+ "component": "shared.link"
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,3 @@
1
+ import { factories } from "@strapi/strapi";
2
+
3
+ export default factories.createCoreController("api::global.global");
@@ -0,0 +1,3 @@
1
+ import { factories } from "@strapi/strapi";
2
+
3
+ export default factories.createCoreRouter("api::global.global");
@@ -0,0 +1,3 @@
1
+ import { factories } from "@strapi/strapi";
2
+
3
+ export default factories.createCoreService("api::global.global");
@@ -0,0 +1,39 @@
1
+ {
2
+ "kind": "collectionType",
3
+ "collectionName": "pages",
4
+ "info": {
5
+ "singularName": "page",
6
+ "pluralName": "pages",
7
+ "displayName": "Page"
8
+ },
9
+ "options": {
10
+ "draftAndPublish": true
11
+ },
12
+ "pluginOptions": {
13
+ "content-type-builder": {
14
+ "visible": false
15
+ }
16
+ },
17
+ "attributes": {
18
+ "title": {
19
+ "type": "string",
20
+ "required": true
21
+ },
22
+ "slug": {
23
+ "type": "uid",
24
+ "targetField": "title",
25
+ "required": true
26
+ },
27
+ "seo": {
28
+ "type": "component",
29
+ "repeatable": false,
30
+ "component": "shared.seo"
31
+ },
32
+ "blocks": {
33
+ "type": "dynamiczone",
34
+ "components": [
35
+ "blocks.reference"
36
+ ]
37
+ }
38
+ }
39
+ }