@fmaplabs/meta-manifest 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/LICENSE +21 -0
- package/README.md +122 -0
- package/dist/chunk-3R6VQ3Z3.js +97 -0
- package/dist/chunk-3R6VQ3Z3.js.map +1 -0
- package/dist/chunk-GH5DXHS5.js +1108 -0
- package/dist/chunk-GH5DXHS5.js.map +1 -0
- package/dist/chunk-PFU5VAO7.js +31 -0
- package/dist/chunk-PFU5VAO7.js.map +1 -0
- package/dist/cli/index.cjs +805 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +12 -0
- package/dist/cli/index.d.ts +12 -0
- package/dist/cli/index.js +239 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client-CdkKviW2.d.cts +25 -0
- package/dist/client-CdkKviW2.d.ts +25 -0
- package/dist/index.cjs +1229 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +602 -0
- package/dist/index.d.ts +602 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/node/client.cjs +65 -0
- package/dist/node/client.cjs.map +1 -0
- package/dist/node/client.d.cts +14 -0
- package/dist/node/client.d.ts +14 -0
- package/dist/node/client.js +8 -0
- package/dist/node/client.js.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fmap labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# meta-manifest
|
|
2
|
+
|
|
3
|
+
A zero-dependency, zod-style builder for Shopify **metaobject definitions**, plus a CLI
|
|
4
|
+
(`mm` / `meta-manifest`) that keeps a store's definitions in sync with schema declared in code.
|
|
5
|
+
Think [tento](https://github.com/drizzle-team/tento), but scoped to metaobject-definition
|
|
6
|
+
schema/migrations rather than a runtime query client (see [Roadmap](#roadmap-runtime-query-client)
|
|
7
|
+
below).
|
|
8
|
+
|
|
9
|
+
`meta-manifest` is **not** a runtime client for reading/writing metaobject *entries* — it declares
|
|
10
|
+
definitions, validates values against them, and syncs the definitions themselves (create/update
|
|
11
|
+
fields) to a store via `pull` → `diff` → `push`.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i -D @fmaplabs/meta-manifest
|
|
17
|
+
# or
|
|
18
|
+
pnpm add -D @fmaplabs/meta-manifest
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Library usage
|
|
22
|
+
|
|
23
|
+
Declare a metaobject with `defineMetaobject` and the `m` field builders. Implements
|
|
24
|
+
[Standard Schema](https://github.com/standard-schema/standard-schema).
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { defineMetaobject, m, type Infer } from "@fmaplabs/meta-manifest";
|
|
28
|
+
|
|
29
|
+
export const Author = defineMetaobject("author", {
|
|
30
|
+
name: "Author",
|
|
31
|
+
displayName: "name",
|
|
32
|
+
access: { storefront: "public_read" },
|
|
33
|
+
fields: {
|
|
34
|
+
name: m.text({ required: true, max: 120 }),
|
|
35
|
+
bio: m.multilineText(),
|
|
36
|
+
rating: m.rating({ min: 1, max: 5 }),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
type AuthorValue = Infer<typeof Author.fields>;
|
|
41
|
+
|
|
42
|
+
Author.type; // "$app:author"
|
|
43
|
+
Author.toDefinitionInput(); // MetaobjectDefinitionCreateInput (for metaobjectDefinitionCreate)
|
|
44
|
+
Author.parse(fields); // Shopify {key, jsonValue}[] -> typed, validated
|
|
45
|
+
Author.encode({ name: "Ursula" }); // typed -> [{ key, value }] for metaobjectUpsert
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
References between metaobjects are declared with `m.ref(...)` / `m.list(m.ref(...))`:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
export const Book = defineMetaobject("book", {
|
|
52
|
+
name: "Book",
|
|
53
|
+
fields: {
|
|
54
|
+
title: m.text({ required: true }),
|
|
55
|
+
author: m.ref(Author),
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
For the full `pull` → `diff` → `push` sync model (how local schema and a live store are
|
|
61
|
+
reconciled, destructive-change gating, dependency ordering, error handling), see
|
|
62
|
+
[`docs/SYNC.md`](./docs/SYNC.md).
|
|
63
|
+
|
|
64
|
+
## CLI
|
|
65
|
+
|
|
66
|
+
The CLI drives sync against a real store using an Admin API access token. For a
|
|
67
|
+
step-by-step walk-through (install → token → `init` → `pull`/`diff`/`push`, with
|
|
68
|
+
example output and CI usage), see the [CLI quick start & usage guide](./docs/CLI.md).
|
|
69
|
+
|
|
70
|
+
### Config
|
|
71
|
+
|
|
72
|
+
`meta-manifest.config.ts` (safe to commit — the token comes from the environment):
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { defineConfig } from "@fmaplabs/meta-manifest";
|
|
76
|
+
|
|
77
|
+
export default defineConfig({
|
|
78
|
+
shop: "my-store.myshopify.com",
|
|
79
|
+
accessToken: process.env.SHOPIFY_ADMIN_TOKEN!,
|
|
80
|
+
apiVersion: "2026-07", // optional; defaults to DEFAULT_API_VERSION
|
|
81
|
+
schema: "./src/schema.ts", // where `pull` writes, `diff`/`push` read
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Set `SHOPIFY_ADMIN_TOKEN` in your environment before running `pull`, `diff`, or `push`. The token
|
|
86
|
+
needs the `read_metaobject_definitions` scope for `pull`/`diff`, and `write_metaobject_definitions`
|
|
87
|
+
(which implies read) for `push`.
|
|
88
|
+
|
|
89
|
+
### Commands
|
|
90
|
+
|
|
91
|
+
| Command | Behavior | Exit |
|
|
92
|
+
|----------|----------|------|
|
|
93
|
+
| `mm init` | Scaffold `meta-manifest.config.ts` + a starter `src/schema.ts`. No network. | 0 / 1 |
|
|
94
|
+
| `mm pull` | Enumerate the store's app-owned metaobject definitions and **codegen** `schema.ts` (tento-style — writes/overwrites the schema source file). | 0 / 1 |
|
|
95
|
+
| `mm diff` | Load `schema.ts`, compare it against the store, and print the plan. Read-only. | 0 / 1 |
|
|
96
|
+
| `mm push` | Diff, then apply: topologically ordered (referenced types created first) and **destructive-gated** — `removeField`/`changeFieldType` are skipped unless you pass `--allow-destructive`. | 0 / 1 / 2 |
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx mm init # scaffold config + schema
|
|
100
|
+
npx mm pull # bootstrap schema.ts from an existing store
|
|
101
|
+
npx mm diff # preview what push would do
|
|
102
|
+
npx mm push # apply non-destructive changes
|
|
103
|
+
npx mm push --allow-destructive # also apply field removals/type changes
|
|
104
|
+
npx mm pull --force # overwrite an existing schema.ts without the warning
|
|
105
|
+
npx mm diff --config ./staging.config.ts # use a non-default config file
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Flags
|
|
109
|
+
|
|
110
|
+
- `--config <path>` — use a non-default config file instead of `meta-manifest.config.ts`.
|
|
111
|
+
- `--allow-destructive` — apply destructive changes (`removeField`/`changeFieldType`) on push.
|
|
112
|
+
- `--force` — overwrite the schema file on `pull` without the "overwriting" warning.
|
|
113
|
+
|
|
114
|
+
`mm push` exits `2` if any operation failed **or was blocked** (e.g. a reference cycle among the
|
|
115
|
+
definitions being created in that push — so CI can detect a partial failure), `1` on a
|
|
116
|
+
config/transport error, and `0` otherwise — including when destructive ops were skipped.
|
|
117
|
+
|
|
118
|
+
## Roadmap: runtime query client
|
|
119
|
+
|
|
120
|
+
v1 covers metaobject **definitions** (schema sync) only. A runtime client for reading/writing
|
|
121
|
+
metaobject **entries** — the tento-style query API — is not implemented yet and is tracked as a
|
|
122
|
+
follow-up, along with codegen of `access`/`capabilities` config.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
var DEFAULT_API_VERSION = "2026-07";
|
|
3
|
+
function defineConfig(config) {
|
|
4
|
+
return config;
|
|
5
|
+
}
|
|
6
|
+
function validateConfig(raw) {
|
|
7
|
+
const c = raw;
|
|
8
|
+
for (const key of ["shop", "accessToken", "schema"]) {
|
|
9
|
+
if (!c || typeof c[key] !== "string" || c[key] === "") {
|
|
10
|
+
throw new Error(`Invalid config: missing or empty "${key}".`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return c;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/sync/client.ts
|
|
17
|
+
var PULL_DEFINITION_QUERY = `query PullMetaobjectDefinition($type: String!) {
|
|
18
|
+
metaobjectDefinitionByType(type: $type) {
|
|
19
|
+
id
|
|
20
|
+
name
|
|
21
|
+
type
|
|
22
|
+
description
|
|
23
|
+
displayNameKey
|
|
24
|
+
fieldDefinitions {
|
|
25
|
+
key
|
|
26
|
+
name
|
|
27
|
+
description
|
|
28
|
+
required
|
|
29
|
+
type { name }
|
|
30
|
+
validations { name value }
|
|
31
|
+
}
|
|
32
|
+
access { admin storefront }
|
|
33
|
+
capabilities {
|
|
34
|
+
publishable { enabled }
|
|
35
|
+
translatable { enabled }
|
|
36
|
+
renderable { enabled }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}`;
|
|
40
|
+
var LIST_DEFINITIONS_QUERY = `query ListMetaobjectDefinitions($after: String) {
|
|
41
|
+
metaobjectDefinitions(first: 50, after: $after) {
|
|
42
|
+
nodes {
|
|
43
|
+
id
|
|
44
|
+
name
|
|
45
|
+
type
|
|
46
|
+
fieldDefinitions {
|
|
47
|
+
key
|
|
48
|
+
name
|
|
49
|
+
description
|
|
50
|
+
required
|
|
51
|
+
type { name }
|
|
52
|
+
validations { name value }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
pageInfo { hasNextPage endCursor }
|
|
56
|
+
}
|
|
57
|
+
}`;
|
|
58
|
+
var CREATE_DEFINITION_MUTATION = `mutation CreateMetaobjectDefinition($definition: MetaobjectDefinitionCreateInput!) {
|
|
59
|
+
metaobjectDefinitionCreate(definition: $definition) {
|
|
60
|
+
metaobjectDefinition { id type }
|
|
61
|
+
userErrors { field message code }
|
|
62
|
+
}
|
|
63
|
+
}`;
|
|
64
|
+
var UPDATE_DEFINITION_MUTATION = `mutation UpdateMetaobjectDefinition($id: ID!, $definition: MetaobjectDefinitionUpdateInput!) {
|
|
65
|
+
metaobjectDefinitionUpdate(id: $id, definition: $definition) {
|
|
66
|
+
metaobjectDefinition { id type }
|
|
67
|
+
userErrors { field message code }
|
|
68
|
+
}
|
|
69
|
+
}`;
|
|
70
|
+
var SyncTransportError = class extends Error {
|
|
71
|
+
constructor(message, errors) {
|
|
72
|
+
super(message);
|
|
73
|
+
this.errors = errors;
|
|
74
|
+
this.name = "SyncTransportError";
|
|
75
|
+
}
|
|
76
|
+
errors;
|
|
77
|
+
};
|
|
78
|
+
async function execute(client, query, variables) {
|
|
79
|
+
const result = await client(query, variables ? { variables } : void 0);
|
|
80
|
+
if (Array.isArray(result.errors) ? result.errors.length > 0 : result.errors != null) {
|
|
81
|
+
throw new SyncTransportError("GraphQL request failed", result.errors);
|
|
82
|
+
}
|
|
83
|
+
return result.data;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
DEFAULT_API_VERSION,
|
|
88
|
+
defineConfig,
|
|
89
|
+
validateConfig,
|
|
90
|
+
PULL_DEFINITION_QUERY,
|
|
91
|
+
LIST_DEFINITIONS_QUERY,
|
|
92
|
+
CREATE_DEFINITION_MUTATION,
|
|
93
|
+
UPDATE_DEFINITION_MUTATION,
|
|
94
|
+
SyncTransportError,
|
|
95
|
+
execute
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=chunk-3R6VQ3Z3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/sync/client.ts"],"sourcesContent":["export const DEFAULT_API_VERSION = \"2026-07\";\n\nexport interface Config {\n /** e.g. \"my-store.myshopify.com\" */\n shop: string;\n /** Admin API access token; reference via process.env in your config file. */\n accessToken: string;\n /** Admin API version. Defaults to DEFAULT_API_VERSION. */\n apiVersion?: string;\n /** Path to the schema module whose `schemas` export drives diff/push, and pull writes. */\n schema: string;\n}\n\n/** Identity helper for type inference in `meta-manifest.config.ts`. */\nexport function defineConfig(config: Config): Config {\n return config;\n}\n\n/** Validate a loaded config object, throwing a one-line Error naming the first missing field. */\nexport function validateConfig(raw: unknown): Config {\n const c = raw as Partial<Config> | null | undefined;\n for (const key of [\"shop\", \"accessToken\", \"schema\"] as const) {\n if (!c || typeof c[key] !== \"string\" || c[key] === \"\") {\n throw new Error(`Invalid config: missing or empty \"${key}\".`);\n }\n }\n return c as Config;\n}\n","/**\n * The minimal Admin GraphQL transport the sync adapter depends on. The app\n * supplies a concrete implementation at its edge (wrapping `admin.graphql`),\n * keeping `@fmaplabs/meta-manifest` free of any runtime dependency on a GraphQL\n * client. [design §5]\n */\nexport interface AdminGraphQLClient {\n (query: string, options?: { variables?: Record<string, unknown> }): Promise<{ data?: unknown; errors?: unknown }>;\n}\n\n// GraphQL operation strings, copied verbatim from the schema-validated documents\n// in the design spec §3. Re-validated against Admin API 2026-07 during planning.\n// Do not edit by hand — keep equal to the validated documents (drift guard, §12).\n\nexport const PULL_DEFINITION_QUERY = `query PullMetaobjectDefinition($type: String!) {\n metaobjectDefinitionByType(type: $type) {\n id\n name\n type\n description\n displayNameKey\n fieldDefinitions {\n key\n name\n description\n required\n type { name }\n validations { name value }\n }\n access { admin storefront }\n capabilities {\n publishable { enabled }\n translatable { enabled }\n renderable { enabled }\n }\n }\n}`;\n\nexport const LIST_DEFINITIONS_QUERY = `query ListMetaobjectDefinitions($after: String) {\n metaobjectDefinitions(first: 50, after: $after) {\n nodes {\n id\n name\n type\n fieldDefinitions {\n key\n name\n description\n required\n type { name }\n validations { name value }\n }\n }\n pageInfo { hasNextPage endCursor }\n }\n}`;\n\nexport const CREATE_DEFINITION_MUTATION = `mutation CreateMetaobjectDefinition($definition: MetaobjectDefinitionCreateInput!) {\n metaobjectDefinitionCreate(definition: $definition) {\n metaobjectDefinition { id type }\n userErrors { field message code }\n }\n}`;\n\nexport const UPDATE_DEFINITION_MUTATION = `mutation UpdateMetaobjectDefinition($id: ID!, $definition: MetaobjectDefinitionUpdateInput!) {\n metaobjectDefinitionUpdate(id: $id, definition: $definition) {\n metaobjectDefinition { id type }\n userErrors { field message code }\n }\n}`;\n\n/**\n * Thrown when a request fails at the transport or top-level GraphQL layer —\n * distinct from per-op `userErrors`, which `push` reports as `failed` rather\n * than throwing. Carries the offending top-level `errors` payload. [design §5]\n */\nexport class SyncTransportError extends Error {\n constructor(message: string, readonly errors: unknown) {\n super(message);\n this.name = \"SyncTransportError\";\n }\n}\n\n/**\n * Runs an operation through the injected client and returns its `data`.\n * A non-empty top-level `errors` payload becomes a `SyncTransportError`;\n * a rejected transport promise propagates unchanged. [design §5]\n */\nexport async function execute<T>(\n client: AdminGraphQLClient,\n query: string,\n variables?: Record<string, unknown>,\n): Promise<T> {\n const result = await client(query, variables ? { variables } : undefined);\n if (Array.isArray(result.errors) ? result.errors.length > 0 : result.errors != null) {\n throw new SyncTransportError(\"GraphQL request failed\", result.errors);\n }\n return result.data as T;\n}\n"],"mappings":";AAAO,IAAM,sBAAsB;AAc5B,SAAS,aAAa,QAAwB;AACnD,SAAO;AACT;AAGO,SAAS,eAAe,KAAsB;AACnD,QAAM,IAAI;AACV,aAAW,OAAO,CAAC,QAAQ,eAAe,QAAQ,GAAY;AAC5D,QAAI,CAAC,KAAK,OAAO,EAAE,GAAG,MAAM,YAAY,EAAE,GAAG,MAAM,IAAI;AACrD,YAAM,IAAI,MAAM,qCAAqC,GAAG,IAAI;AAAA,IAC9D;AAAA,EACF;AACA,SAAO;AACT;;;ACbO,IAAM,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwB9B,IAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmB/B,IAAM,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAOnC,IAAM,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAYnC,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAC5C,YAAY,SAA0B,QAAiB;AACrD,UAAM,OAAO;AADuB;AAEpC,SAAK,OAAO;AAAA,EACd;AAAA,EAHsC;AAIxC;AAOA,eAAsB,QACpB,QACA,OACA,WACY;AACZ,QAAM,SAAS,MAAM,OAAO,OAAO,YAAY,EAAE,UAAU,IAAI,MAAS;AACxE,MAAI,MAAM,QAAQ,OAAO,MAAM,IAAI,OAAO,OAAO,SAAS,IAAI,OAAO,UAAU,MAAM;AACnF,UAAM,IAAI,mBAAmB,0BAA0B,OAAO,MAAM;AAAA,EACtE;AACA,SAAO,OAAO;AAChB;","names":[]}
|