zero-swift 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -1
- package/dist/bin.js +206 -99
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -37,7 +37,28 @@ npx zero-swift codegen --check # fails when the output is out of date
|
|
|
37
37
|
`Int`; it must then hold integers. For a `json<T>()` column whose `T` is a
|
|
38
38
|
plain object or a literal union, the tool generates a `Codable` type.
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Every key is optional:
|
|
41
|
+
|
|
42
|
+
| Key | Default | Meaning |
|
|
43
|
+
| ----------------------------- | ------------------- | ----------------------------------------- |
|
|
44
|
+
| `schema` | `schema.ts` | The TypeScript schema module |
|
|
45
|
+
| `export` | `schema` | The module export that holds the schema |
|
|
46
|
+
| `output` | `Schema.swift` | The Swift file to write |
|
|
47
|
+
| `schemaName` | `app` | The `ZeroSchema` static, `ZeroSchema.app` |
|
|
48
|
+
| `zql` | `zql` | The global query builder, of type `ZQL` |
|
|
49
|
+
| `typePrefix` | none | Prepended to every generated type name |
|
|
50
|
+
| `access` | `public` | `public` or `internal` |
|
|
51
|
+
| `tables.<t>.type` | `<t>` in PascalCase | The row struct's name |
|
|
52
|
+
| `tables.<t>.columns.<c>.type` | from the schema | `int` or `double` |
|
|
53
|
+
|
|
54
|
+
Rename a row struct whose name clashes with a type you use. A table named
|
|
55
|
+
`list` generates `struct List`, which hides SwiftUI's `List`; set
|
|
56
|
+
`tables.list.type` to `NoteList`, or set a `typePrefix`.
|
|
57
|
+
|
|
58
|
+
Each config key has an option, such as `--schema-file`, `--out`,
|
|
59
|
+
`--type-prefix`, and `--access`, that overrides it. `--config` picks the
|
|
60
|
+
config file. Run `npx zero-swift codegen --help` for the full list.
|
|
61
|
+
`--schema <path>` from 0.1.0 still works, as `--schema-file`.
|
|
41
62
|
|
|
42
63
|
## `zero-swift drift record`
|
|
43
64
|
|
|
@@ -60,3 +81,11 @@ The samples module's default export holds:
|
|
|
60
81
|
|
|
61
82
|
Record again whenever either side changes. `--check` fails when the recorded
|
|
62
83
|
file is out of date.
|
|
84
|
+
|
|
85
|
+
## Output and agents
|
|
86
|
+
|
|
87
|
+
The CLI is built on [incur](https://github.com/wevm/incur). Errors print a
|
|
88
|
+
code and a message and exit 1, with no stack trace. `--json` (or `--format`)
|
|
89
|
+
prints the result as JSON, for scripts and CI. `--llms` prints a manifest of
|
|
90
|
+
the commands for a coding agent, and `npx zero-swift skills add` installs
|
|
91
|
+
them as an agent skill.
|
package/dist/bin.js
CHANGED
|
@@ -4,7 +4,62 @@
|
|
|
4
4
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { relative, resolve as resolve2 } from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
|
-
import {
|
|
7
|
+
import { Cli, z } from "incur";
|
|
8
|
+
|
|
9
|
+
// package.json
|
|
10
|
+
var package_default = {
|
|
11
|
+
name: "zero-swift",
|
|
12
|
+
version: "0.3.0",
|
|
13
|
+
description: "Generates the Swift row types and schema of a Zero app for the Zero Swift client.",
|
|
14
|
+
license: "Apache-2.0",
|
|
15
|
+
repository: {
|
|
16
|
+
type: "git",
|
|
17
|
+
url: "git+https://github.com/wevm/zero.git",
|
|
18
|
+
directory: "packages/zero-swift-cli"
|
|
19
|
+
},
|
|
20
|
+
bin: {
|
|
21
|
+
"zero-swift": "./src/bin.ts"
|
|
22
|
+
},
|
|
23
|
+
files: [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
type: "module",
|
|
27
|
+
publishConfig: {
|
|
28
|
+
bin: {
|
|
29
|
+
"zero-swift": "./dist/bin.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
scripts: {
|
|
33
|
+
build: "esbuild src/bin.ts --bundle --platform=node --format=esm --target=node22 --splitting --outdir=dist --external:@rocicorp/zero --external:typescript --external:incur",
|
|
34
|
+
test: "vitest run",
|
|
35
|
+
generate: "UPDATE_GENERATED=1 vitest run",
|
|
36
|
+
"check-types": "tsc",
|
|
37
|
+
lint: "oxlint --quiet --config ../../oxlint.config.ts src/",
|
|
38
|
+
format: "oxfmt .",
|
|
39
|
+
"check-format": "oxfmt --check .",
|
|
40
|
+
"version-packages": "cd ../.. && pnpm dlx @changesets/cli@3.0.3 version && jq -r .version packages/zero-swift-cli/package.json > swift/VERSION"
|
|
41
|
+
},
|
|
42
|
+
dependencies: {
|
|
43
|
+
incur: "0.7.1",
|
|
44
|
+
typescript: "6.0.3"
|
|
45
|
+
},
|
|
46
|
+
devDependencies: {
|
|
47
|
+
"@rocicorp/zero": "workspace:*",
|
|
48
|
+
"@types/node": "^22.19.19",
|
|
49
|
+
esbuild: "0.28.2",
|
|
50
|
+
oxfmt: "^0.74.0",
|
|
51
|
+
vitest: "^5.0.0",
|
|
52
|
+
"zero-schema": "workspace:*",
|
|
53
|
+
"zero-types": "workspace:*",
|
|
54
|
+
zql: "workspace:*"
|
|
55
|
+
},
|
|
56
|
+
peerDependencies: {
|
|
57
|
+
"@rocicorp/zero": ">=1.11.0"
|
|
58
|
+
},
|
|
59
|
+
engines: {
|
|
60
|
+
node: ">=22.18"
|
|
61
|
+
}
|
|
62
|
+
};
|
|
8
63
|
|
|
9
64
|
// src/generate.ts
|
|
10
65
|
var reservedTypeNames = /* @__PURE__ */ new Set([
|
|
@@ -567,39 +622,32 @@ function aliasName(type) {
|
|
|
567
622
|
}
|
|
568
623
|
|
|
569
624
|
// src/cli.ts
|
|
570
|
-
var usage = `Usage:
|
|
571
|
-
zero-swift codegen [--schema schema.ts] [--out Schema.swift]
|
|
572
|
-
[--config zero-swift.json] [--check]
|
|
573
|
-
zero-swift drift record [--samples drift-samples.ts]
|
|
574
|
-
[--out drift-vectors.json] [--check]
|
|
575
|
-
|
|
576
|
-
codegen generates Swift from the app's TypeScript schema. Options override
|
|
577
|
-
zero-swift.json, which is read when present.
|
|
578
|
-
drift record runs the app's TS queries and mutators on samples and records
|
|
579
|
-
what they did, for the Swift drift test.
|
|
580
|
-
--check fails when the output is out of date instead of writing it.
|
|
581
|
-
|
|
582
|
-
Docs: https://github.com/wevm/zero/tree/main/packages/zero-swift-cli
|
|
583
|
-
`;
|
|
584
625
|
var UsageError = class extends Error {
|
|
626
|
+
code;
|
|
627
|
+
constructor(code, message) {
|
|
628
|
+
super(message);
|
|
629
|
+
this.code = code;
|
|
630
|
+
}
|
|
585
631
|
};
|
|
586
|
-
function mustExist(path, message) {
|
|
632
|
+
function mustExist(path, code, message) {
|
|
587
633
|
if (!existsSync(path)) {
|
|
588
|
-
throw new UsageError(message);
|
|
634
|
+
throw new UsageError(code, message);
|
|
589
635
|
}
|
|
590
636
|
}
|
|
591
637
|
async function codegenFile(config, cwd) {
|
|
592
638
|
const schemaPath = resolve2(cwd, config.schema ?? "schema.ts");
|
|
593
639
|
mustExist(
|
|
594
640
|
schemaPath,
|
|
595
|
-
|
|
641
|
+
"SCHEMA_NOT_FOUND",
|
|
642
|
+
`Schema ${relative(cwd, schemaPath)} doesn't exist. Pass --schema-file, or set "schema" in zero-swift.json.`
|
|
596
643
|
);
|
|
597
644
|
const exportName = config.export ?? "schema";
|
|
598
645
|
const module = await import(pathToFileURL(schemaPath).href);
|
|
599
646
|
const schema = module[exportName];
|
|
600
647
|
if (!schema || typeof schema !== "object" || !("tables" in schema)) {
|
|
601
648
|
throw new UsageError(
|
|
602
|
-
|
|
649
|
+
"SCHEMA_NOT_EXPORTED",
|
|
650
|
+
`${relative(cwd, schemaPath)} does not export a schema as "${exportName}". Pass --export, or set "export" in zero-swift.json.`
|
|
603
651
|
);
|
|
604
652
|
}
|
|
605
653
|
const customTypes = readCustomTypes(schemaPath, exportName);
|
|
@@ -610,97 +658,156 @@ async function codegenFile(config, cwd) {
|
|
|
610
658
|
relative(cwd, schemaPath).replaceAll("\\", "/")
|
|
611
659
|
);
|
|
612
660
|
}
|
|
613
|
-
async function
|
|
614
|
-
let parsed;
|
|
661
|
+
async function reporting(fail, body) {
|
|
615
662
|
try {
|
|
616
|
-
|
|
617
|
-
args: argv,
|
|
618
|
-
allowPositionals: true,
|
|
619
|
-
options: {
|
|
620
|
-
schema: { type: "string" },
|
|
621
|
-
out: { type: "string" },
|
|
622
|
-
config: { type: "string" },
|
|
623
|
-
samples: { type: "string" },
|
|
624
|
-
check: { type: "boolean" },
|
|
625
|
-
help: { type: "boolean", short: "h" }
|
|
626
|
-
}
|
|
627
|
-
});
|
|
663
|
+
return await body();
|
|
628
664
|
} catch (e) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
const { values, positionals } = parsed;
|
|
635
|
-
const [command = "codegen", subcommand] = positionals;
|
|
636
|
-
if (values.help || command === "help") {
|
|
637
|
-
process.stdout.write(usage);
|
|
638
|
-
return 0;
|
|
639
|
-
}
|
|
640
|
-
const cwd = process.cwd();
|
|
641
|
-
if (command === "drift" && subcommand === "record") {
|
|
642
|
-
const samples = resolve2(cwd, values.samples ?? "drift-samples.ts");
|
|
643
|
-
mustExist(
|
|
644
|
-
samples,
|
|
645
|
-
`Samples file ${relative(cwd, samples)} doesn't exist. Pass --samples.`
|
|
646
|
-
);
|
|
647
|
-
const { recordDriftFile } = await import("./drift-S756Q2ZA.js");
|
|
648
|
-
const vectors = await recordDriftFile(samples);
|
|
649
|
-
return writeOutput(
|
|
650
|
-
resolve2(cwd, values.out ?? "drift-vectors.json"),
|
|
651
|
-
vectors,
|
|
652
|
-
values.check ?? false,
|
|
653
|
-
cwd
|
|
654
|
-
);
|
|
665
|
+
if (e instanceof UsageError) {
|
|
666
|
+
return fail({ code: e.code, message: e.message });
|
|
667
|
+
}
|
|
668
|
+
throw e;
|
|
655
669
|
}
|
|
656
|
-
if (command !== "codegen") {
|
|
657
|
-
process.stderr.write(
|
|
658
|
-
`Unknown command "${positionals.join(" ")}"
|
|
659
|
-
|
|
660
|
-
${usage}`
|
|
661
|
-
);
|
|
662
|
-
return 2;
|
|
663
|
-
}
|
|
664
|
-
const configPath = values.config ?? (existsSync(resolve2(cwd, "zero-swift.json")) ? "zero-swift.json" : void 0);
|
|
665
|
-
if (configPath) {
|
|
666
|
-
mustExist(resolve2(cwd, configPath), `Config ${configPath} doesn't exist.`);
|
|
667
|
-
}
|
|
668
|
-
const fileConfig = configPath ? JSON.parse(readFileSync(resolve2(cwd, configPath), "utf8")) : {};
|
|
669
|
-
const config = {
|
|
670
|
-
...fileConfig,
|
|
671
|
-
...values.schema ? { schema: values.schema } : {},
|
|
672
|
-
...values.out ? { output: values.out } : {}
|
|
673
|
-
};
|
|
674
|
-
const output = resolve2(cwd, config.output ?? "Schema.swift");
|
|
675
|
-
const swift = await codegenFile(config, cwd);
|
|
676
|
-
return writeOutput(output, swift, values.check ?? false, cwd);
|
|
677
670
|
}
|
|
678
|
-
function writeOutput(output, contents,
|
|
671
|
+
function writeOutput(fail, output, contents, check2, cwd) {
|
|
672
|
+
const path = relative(cwd, output);
|
|
679
673
|
const current = existsSync(output) ? readFileSync(output, "utf8") : void 0;
|
|
680
|
-
if (
|
|
674
|
+
if (check2) {
|
|
681
675
|
if (current !== contents) {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
676
|
+
return fail({
|
|
677
|
+
code: "OUT_OF_DATE",
|
|
678
|
+
message: `${path} is out of date. Run the command again without --check to update it.`
|
|
679
|
+
});
|
|
685
680
|
}
|
|
686
|
-
return
|
|
681
|
+
return { path, upToDate: true };
|
|
687
682
|
}
|
|
688
683
|
if (current !== contents) {
|
|
689
684
|
writeFileSync(output, contents);
|
|
690
685
|
}
|
|
691
|
-
|
|
692
|
-
`);
|
|
693
|
-
return 0;
|
|
686
|
+
return { path, written: current !== contents };
|
|
694
687
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
688
|
+
function legacyArgv(argv) {
|
|
689
|
+
if (argv[0] !== "codegen") return [...argv];
|
|
690
|
+
const out = [];
|
|
691
|
+
for (let i = 0; i < argv.length; i++) {
|
|
692
|
+
const token = argv[i];
|
|
693
|
+
const next = argv[i + 1];
|
|
694
|
+
if (token === "--schema" && next !== void 0 && !next.startsWith("-")) {
|
|
695
|
+
out.push("--schema-file", next);
|
|
696
|
+
i++;
|
|
697
|
+
} else if (token.startsWith("--schema=")) {
|
|
698
|
+
out.push("--schema-file", token.slice("--schema=".length));
|
|
699
|
+
} else {
|
|
700
|
+
out.push(token);
|
|
701
|
+
}
|
|
702
702
|
}
|
|
703
|
-
|
|
704
|
-
`);
|
|
705
|
-
process.exitCode = 1;
|
|
703
|
+
return out;
|
|
706
704
|
}
|
|
705
|
+
var check = z.boolean().optional().describe("Fail when the output is out of date instead of writing it");
|
|
706
|
+
var cli = Cli.create("zero-swift", {
|
|
707
|
+
version: package_default.version,
|
|
708
|
+
description: "Generates what the Zero Swift client needs from a Zero app's TypeScript schema, queries, and mutators."
|
|
709
|
+
}).command("codegen", {
|
|
710
|
+
description: "Generate the Swift row types, schema, and query builder from the app's TypeScript schema. Options override zero-swift.json, which is read when present.",
|
|
711
|
+
options: z.object({
|
|
712
|
+
config: z.string().optional().describe("Config file (default zero-swift.json, when present)"),
|
|
713
|
+
schemaFile: z.string().optional().describe("TS schema module (default schema.ts)"),
|
|
714
|
+
export: z.string().optional().describe("Export of the module that holds the schema (default schema)"),
|
|
715
|
+
out: z.string().optional().describe("Swift file to write (default Schema.swift)"),
|
|
716
|
+
schemaName: z.string().optional().describe("Name of the generated ZeroSchema static (default app)"),
|
|
717
|
+
zql: z.string().optional().describe("Name of the generated query builder global (default zql)"),
|
|
718
|
+
typePrefix: z.string().optional().describe("Prefix for every generated type name (default none)"),
|
|
719
|
+
access: z.enum(["public", "internal"]).optional().describe("Access level of the generated code (default public)"),
|
|
720
|
+
check
|
|
721
|
+
}),
|
|
722
|
+
examples: [
|
|
723
|
+
{ description: "Generate from zero-swift.json" },
|
|
724
|
+
{
|
|
725
|
+
options: {
|
|
726
|
+
schemaFile: "shared/schema.ts",
|
|
727
|
+
out: "Sources/App/Schema.swift"
|
|
728
|
+
},
|
|
729
|
+
description: "Generate without a config file"
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
options: { check: true },
|
|
733
|
+
description: "Fail in CI when the output is stale"
|
|
734
|
+
}
|
|
735
|
+
],
|
|
736
|
+
run: (c) => reporting(c.error, async () => {
|
|
737
|
+
const cwd = process.cwd();
|
|
738
|
+
const {
|
|
739
|
+
config: configOption,
|
|
740
|
+
schemaFile,
|
|
741
|
+
out,
|
|
742
|
+
check: check2,
|
|
743
|
+
...overrides
|
|
744
|
+
} = c.options;
|
|
745
|
+
const configPath = configOption ?? (existsSync(resolve2(cwd, "zero-swift.json")) ? "zero-swift.json" : void 0);
|
|
746
|
+
if (configPath) {
|
|
747
|
+
mustExist(
|
|
748
|
+
resolve2(cwd, configPath),
|
|
749
|
+
"CONFIG_NOT_FOUND",
|
|
750
|
+
`Config ${configPath} doesn't exist.`
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
const fileConfig = configPath ? JSON.parse(readFileSync(resolve2(cwd, configPath), "utf8")) : {};
|
|
754
|
+
const config = {
|
|
755
|
+
...fileConfig,
|
|
756
|
+
...Object.fromEntries(
|
|
757
|
+
Object.entries(overrides).filter(([, v]) => v !== void 0)
|
|
758
|
+
),
|
|
759
|
+
...schemaFile ? { schema: schemaFile } : {},
|
|
760
|
+
...out ? { output: out } : {}
|
|
761
|
+
};
|
|
762
|
+
const swift = await codegenFile(config, cwd);
|
|
763
|
+
return writeOutput(
|
|
764
|
+
c.error,
|
|
765
|
+
resolve2(cwd, config.output ?? "Schema.swift"),
|
|
766
|
+
swift,
|
|
767
|
+
check2 ?? false,
|
|
768
|
+
cwd
|
|
769
|
+
);
|
|
770
|
+
})
|
|
771
|
+
});
|
|
772
|
+
cli.command(
|
|
773
|
+
Cli.create("drift", {
|
|
774
|
+
description: "Record what the TS queries and mutators do, for the Swift drift test (Drift.failures in ZeroTesting)."
|
|
775
|
+
}).command("record", {
|
|
776
|
+
description: "Run the app's TS queries and mutators on the samples a module exports, and write what they did.",
|
|
777
|
+
options: z.object({
|
|
778
|
+
samples: z.string().optional().describe("Samples module (default drift-samples.ts)"),
|
|
779
|
+
out: z.string().optional().describe("Vectors file to write (default drift-vectors.json)"),
|
|
780
|
+
check
|
|
781
|
+
}),
|
|
782
|
+
examples: [
|
|
783
|
+
{
|
|
784
|
+
options: {
|
|
785
|
+
samples: "drift-samples.ts",
|
|
786
|
+
out: "Tests/AppTests/drift-vectors.json"
|
|
787
|
+
},
|
|
788
|
+
description: "Record the drift vectors"
|
|
789
|
+
}
|
|
790
|
+
],
|
|
791
|
+
run: (c) => reporting(c.error, async () => {
|
|
792
|
+
const cwd = process.cwd();
|
|
793
|
+
const samples = resolve2(cwd, c.options.samples ?? "drift-samples.ts");
|
|
794
|
+
mustExist(
|
|
795
|
+
samples,
|
|
796
|
+
"SAMPLES_NOT_FOUND",
|
|
797
|
+
`Samples file ${relative(cwd, samples)} doesn't exist. Pass --samples.`
|
|
798
|
+
);
|
|
799
|
+
const { recordDriftFile } = await import("./drift-S756Q2ZA.js");
|
|
800
|
+
const vectors = await recordDriftFile(samples);
|
|
801
|
+
return writeOutput(
|
|
802
|
+
c.error,
|
|
803
|
+
resolve2(cwd, c.options.out ?? "drift-vectors.json"),
|
|
804
|
+
vectors,
|
|
805
|
+
c.options.check ?? false,
|
|
806
|
+
cwd
|
|
807
|
+
);
|
|
808
|
+
})
|
|
809
|
+
})
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
// src/bin.ts
|
|
813
|
+
await cli.serve(legacyArgv(process.argv.slice(2)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zero-swift",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Generates the Swift row types and schema of a Zero app for the Zero Swift client.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
],
|
|
17
17
|
"type": "module",
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"incur": "0.7.1",
|
|
19
20
|
"typescript": "6.0.3"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
@@ -24,9 +25,9 @@
|
|
|
24
25
|
"oxfmt": "^0.74.0",
|
|
25
26
|
"vitest": "^5.0.0",
|
|
26
27
|
"@rocicorp/zero": "1.12.0",
|
|
27
|
-
"zero-schema": "0.0.0",
|
|
28
28
|
"zero-types": "0.0.0",
|
|
29
|
-
"zql": "0.0.0"
|
|
29
|
+
"zql": "0.0.0",
|
|
30
|
+
"zero-schema": "0.0.0"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
33
|
"@rocicorp/zero": ">=1.11.0"
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
"node": ">=22.18"
|
|
36
37
|
},
|
|
37
38
|
"scripts": {
|
|
38
|
-
"build": "esbuild src/bin.ts --bundle --platform=node --format=esm --target=node22 --splitting --outdir=dist --external:@rocicorp/zero --external:typescript",
|
|
39
|
+
"build": "esbuild src/bin.ts --bundle --platform=node --format=esm --target=node22 --splitting --outdir=dist --external:@rocicorp/zero --external:typescript --external:incur",
|
|
39
40
|
"test": "vitest run",
|
|
40
41
|
"generate": "UPDATE_GENERATED=1 vitest run",
|
|
41
42
|
"check-types": "tsc",
|