saasaloy 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/dist/index.js +8455 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
- package/schemas/manifest.schema.json +107 -0
- package/schemas/registry-item.schema.json +336 -0
- package/schemas/saasaloy-lock.schema.json +86 -0
- package/schemas/saasaloy.schema.json +42 -0
- package/templates/base/AGENTS.md +450 -0
- package/templates/base/CLAUDE.md +1 -0
- package/templates/base/DESIGN.md +209 -0
- package/templates/base/README.md +71 -0
- package/templates/base/_agents/skills/saasaloy-design/SKILL.md +198 -0
- package/templates/base/_agents/skills/saasaloy-landing-copy/SKILL.md +395 -0
- package/templates/base/_agents/skills/saasaloy-setup/SKILL.md +273 -0
- package/templates/base/_gitignore +32 -0
- package/templates/base/_husky/commit-msg +1 -0
- package/templates/base/_husky/pre-commit +1 -0
- package/templates/base/_prettierignore +31 -0
- package/templates/base/_saasaloy-base.json +9 -0
- package/templates/base/apps/web/astro.config.mjs +61 -0
- package/templates/base/apps/web/package.json +30 -0
- package/templates/base/apps/web/public/favicon.svg +4 -0
- package/templates/base/apps/web/src/layouts/Layout.astro +52 -0
- package/templates/base/apps/web/src/pages/404.astro +24 -0
- package/templates/base/apps/web/src/pages/500.astro +33 -0
- package/templates/base/apps/web/src/pages/index.astro +61 -0
- package/templates/base/apps/web/src/pages/privacy.astro +15 -0
- package/templates/base/apps/web/src/pages/terms.astro +14 -0
- package/templates/base/apps/web/tsconfig.json +11 -0
- package/templates/base/apps/web/wrangler.jsonc +27 -0
- package/templates/base/commitlint.config.js +9 -0
- package/templates/base/lint-staged.config.js +17 -0
- package/templates/base/oxlint.config.mjs +155 -0
- package/templates/base/package.json +44 -0
- package/templates/base/packages/tsconfig/base.json +17 -0
- package/templates/base/packages/tsconfig/package.json +18 -0
- package/templates/base/packages/ui/components.json +19 -0
- package/templates/base/packages/ui/package.json +39 -0
- package/templates/base/packages/ui/src/blocks/cta.tsx +82 -0
- package/templates/base/packages/ui/src/blocks/error-state.tsx +144 -0
- package/templates/base/packages/ui/src/blocks/faq.tsx +64 -0
- package/templates/base/packages/ui/src/blocks/feature-grid.tsx +185 -0
- package/templates/base/packages/ui/src/blocks/footer.tsx +99 -0
- package/templates/base/packages/ui/src/blocks/hero.tsx +84 -0
- package/templates/base/packages/ui/src/blocks/navbar.tsx +159 -0
- package/templates/base/packages/ui/src/blocks/pricing-table.tsx +175 -0
- package/templates/base/packages/ui/src/blocks/theme-toggle.tsx +51 -0
- package/templates/base/packages/ui/src/components/accordion.tsx +78 -0
- package/templates/base/packages/ui/src/components/badge.tsx +53 -0
- package/templates/base/packages/ui/src/components/button.tsx +59 -0
- package/templates/base/packages/ui/src/components/card.tsx +103 -0
- package/templates/base/packages/ui/src/components/input.tsx +20 -0
- package/templates/base/packages/ui/src/components/label.tsx +18 -0
- package/templates/base/packages/ui/src/components/separator.tsx +23 -0
- package/templates/base/packages/ui/src/containers/README.md +11 -0
- package/templates/base/packages/ui/src/content/errors.ts +58 -0
- package/templates/base/packages/ui/src/content/landing.ts +304 -0
- package/templates/base/packages/ui/src/index.ts +8 -0
- package/templates/base/packages/ui/src/lib/interpolate.ts +31 -0
- package/templates/base/packages/ui/src/lib/sentinel.ts +11 -0
- package/templates/base/packages/ui/src/lib/theme.ts +167 -0
- package/templates/base/packages/ui/src/lib/utils.ts +11 -0
- package/templates/base/packages/ui/src/styles/globals.css +164 -0
- package/templates/base/packages/ui/tsconfig.json +7 -0
- package/templates/base/pnpm-workspace.yaml +23 -0
- package/templates/base/prettier.config.js +10 -0
- package/templates/base/saasaloy.json +8 -0
- package/templates/base/stylelint.config.js +46 -0
- package/templates/base/turbo.json +20 -0
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "saasaloy",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Composable SaaS accelerator kit.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"saas",
|
|
7
|
+
"scaffold",
|
|
8
|
+
"starter",
|
|
9
|
+
"cli",
|
|
10
|
+
"astro",
|
|
11
|
+
"cloudflare-workers",
|
|
12
|
+
"drizzle",
|
|
13
|
+
"generator"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/mimukit/saasaloy#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/mimukit/saasaloy/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/mimukit/saasaloy.git",
|
|
22
|
+
"directory": "packages/cli"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"bin": {
|
|
27
|
+
"saasaloy": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"templates",
|
|
32
|
+
"schemas/*.schema.json"
|
|
33
|
+
],
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=24.13.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"prepack": "pnpm run build",
|
|
43
|
+
"dev": "tsup --watch",
|
|
44
|
+
"start": "node ./dist/index.js",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"test": "vitest run --coverage",
|
|
47
|
+
"test:e2e": "vitest run --config vitest.integration.config.ts test/e2e",
|
|
48
|
+
"test:matrix": "vitest run --config vitest.integration.config.ts test/matrix",
|
|
49
|
+
"release": "release-it",
|
|
50
|
+
"release:dry": "release-it --dry-run"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@release-it/conventional-changelog": "12.0.0",
|
|
54
|
+
"@types/node": "26.1.1",
|
|
55
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
56
|
+
"release-it": "21.0.2",
|
|
57
|
+
"tsup": "8.5.1",
|
|
58
|
+
"vitest": "4.1.10"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@clack/prompts": "1.7.0",
|
|
62
|
+
"ajv": "8.17.1",
|
|
63
|
+
"diff": "9.0.0",
|
|
64
|
+
"giget": "3.3.0",
|
|
65
|
+
"jsonc-parser": "3.3.1",
|
|
66
|
+
"magicast": "0.5.3",
|
|
67
|
+
"picocolors": "1.1.1"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://saasaloy.dev/schemas/manifest.schema.json",
|
|
4
|
+
"title": ".saasaloy/manifest.json — managed-file tracking",
|
|
5
|
+
"description": "Records every file a module applied — copied source files and copied skill files — with the owning module and a content hash, so update/remove know exactly what to undo. Replaces in-file `// saasaloy:managed` markers (build spec §2.9 / §3.2). `saasaloy init` writes it first, with one entry per base template file under the reserved module name `base` (ADR 0032).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["managed"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Optional pointer to this schema for editor validation."
|
|
13
|
+
},
|
|
14
|
+
"managed": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"description": "Project-relative file path → the module that owns it and a content hash. On update the tool re-hashes the file: match → safe overwrite; drift → route to AI-merge.",
|
|
17
|
+
"additionalProperties": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"additionalProperties": false,
|
|
20
|
+
"required": ["module", "hash"],
|
|
21
|
+
"properties": {
|
|
22
|
+
"module": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
25
|
+
"description": "Name of the module that applied this file. `base` is reserved for the template `saasaloy init` rendered; it is never in saasaloy.json's `installed` list."
|
|
26
|
+
},
|
|
27
|
+
"hash": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
30
|
+
"description": "sha256 hex digest of the file's content as written."
|
|
31
|
+
},
|
|
32
|
+
"from": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"minLength": 1,
|
|
35
|
+
"description": "Module-relative POSIX source path this file was copied from (e.g. `files/lib/email.ts`). `update` uses it to fetch the same file at the old and new commit SHAs. Optional — entries written before this field shipped fall back to re-deriving the target from the module descriptor."
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"links": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"description": "Symlink map (source path → link path): a module's skill files live under .agents/skills/<name>/ and `add` points a .claude/skills/<name> symlink at them, recorded here so `remove` can undo both (ADR 0015).",
|
|
43
|
+
"additionalProperties": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"minLength": 1
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"removeWarnings": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"description": "Installed module name to removal warnings copied from its descriptor. The remover reads this map offline.",
|
|
51
|
+
"propertyNames": {
|
|
52
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
53
|
+
},
|
|
54
|
+
"additionalProperties": {
|
|
55
|
+
"type": "array",
|
|
56
|
+
"items": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"minLength": 1
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"patches": {
|
|
63
|
+
"type": "array",
|
|
64
|
+
"description": "Structural config patches actually applied to disk, so `remove` can warn which files it can't clean up on its own (reversal is a follow-up issue). Deduped on re-apply (structural equality) so `--force` stays idempotent.",
|
|
65
|
+
"items": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"additionalProperties": false,
|
|
68
|
+
"required": ["module", "file", "patch"],
|
|
69
|
+
"properties": {
|
|
70
|
+
"module": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
73
|
+
"description": "Name of the module that applied this patch."
|
|
74
|
+
},
|
|
75
|
+
"file": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"minLength": 1,
|
|
78
|
+
"description": "Project-relative POSIX path of the file that was patched."
|
|
79
|
+
},
|
|
80
|
+
"patch": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"required": ["file", "kind"],
|
|
83
|
+
"properties": {
|
|
84
|
+
"file": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"minLength": 1
|
|
87
|
+
},
|
|
88
|
+
"kind": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"enum": [
|
|
91
|
+
"wrangler-binding",
|
|
92
|
+
"package-json-dependency",
|
|
93
|
+
"package-json-script",
|
|
94
|
+
"plugin-array",
|
|
95
|
+
"const-array",
|
|
96
|
+
"chained-route"
|
|
97
|
+
],
|
|
98
|
+
"description": "Which codemod applied this patch. Must list exactly the kinds registry-item.schema.json accepts — the applier records here whatever a descriptor authored, so a kind missing from this enum makes the next `loadManifest` refuse a manifest the tool itself wrote (#98). `schema.test.ts` asserts the two enums and the `Patch` union agree."
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"description": "The op as authored (kind + payload + file), re-applicable at execute time."
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://saasaloy.dev/schemas/registry-item.schema.json",
|
|
4
|
+
"title": "registry-item.json — module descriptor",
|
|
5
|
+
"description": "shadcn-shaped descriptor authored in the CLI/registry repo. The applier reads it off disk to resolve dependencies, apply files + npm deps + patches, and copy the module's skill folder(s) into .claude/skills/ (build spec §3.3).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["name", "type"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Optional pointer to this schema for editor validation."
|
|
13
|
+
},
|
|
14
|
+
"name": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
17
|
+
"description": "Module name (lowercase, digits, hyphens). Unique within the registry."
|
|
18
|
+
},
|
|
19
|
+
"type": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"enum": ["saasaloy:capability", "saasaloy:feature"],
|
|
22
|
+
"description": "`saasaloy:capability` scaffolds an app/package and establishes extension points; `saasaloy:feature` extends capabilities by dropping files into their conventions."
|
|
23
|
+
},
|
|
24
|
+
"requires": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"additionalProperties": false,
|
|
27
|
+
"description": "Tool versions this descriptor needs. A descriptor sets `additionalProperties: false`, so an old CLI meeting a new field already fails — as a raw schema dump, and not at all when an existing field merely changed meaning. Declaring a range turns both into one fatal, actionable message, checked before any write is planned. Optional: a descriptor without it works on every CLI version.",
|
|
28
|
+
"properties": {
|
|
29
|
+
"saasaloy": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"minLength": 1,
|
|
32
|
+
"description": "Semver range the running `saasaloy` CLI has to satisfy (e.g. `>=0.3`, `>=0.3 <2`, `^1.2.0`, `1.x`). Any range shape is accepted, upper bounds included, so there is no separate maximum field. A CLI outside the range refuses the whole run and names this module, even when it is a transitive `dependsOn`."
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"dependsOn": {
|
|
37
|
+
"type": "array",
|
|
38
|
+
"description": "Modules this one requires. Resolved recursively and topologically sorted behind a confirmation prompt.",
|
|
39
|
+
"uniqueItems": true,
|
|
40
|
+
"items": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"conflictsWith": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"description": "Modules this one refuses to sit beside — two implementations of the same capability, say. `add` refuses with a message naming both modules when a conflicting module is already installed. Either side may declare it: the field is recorded into saasaloy-lock.json, so the refusal fires in either install order. Never auto-resolved; `add` uninstalls nothing.",
|
|
48
|
+
"uniqueItems": true,
|
|
49
|
+
"items": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"requiresOneOf": {
|
|
55
|
+
"type": "array",
|
|
56
|
+
"description": "Modules exactly one of which has to be present — a capability naming its mutually exclusive drivers (e.g. `database` naming `database-d1` and `database-postgres`). `add` counts a module as present when it is already installed or arrives in the same resolved graph, refuses when none is, and offers the list as a prompt on an interactive terminal. Use `dependsOn` for a single hard prerequisite; this field exists for a choice, so it takes at least two names (#98, ADR 0026).",
|
|
57
|
+
"uniqueItems": true,
|
|
58
|
+
"minItems": 2,
|
|
59
|
+
"items": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"description": "npm packages merged into the consumer's `dependencies`. Each must be exact-pinned `name@version` (e.g. zod@4.0.5) — bare names and ranges are rejected; `pnpm deps:update` fills/refreshes the versions.",
|
|
67
|
+
"uniqueItems": true,
|
|
68
|
+
"items": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"pattern": "^(@[a-z0-9][a-z0-9._-]*/)?[a-z0-9][a-z0-9._-]*@\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?(\\+[0-9A-Za-z.-]+)?$"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"type": "array",
|
|
75
|
+
"description": "npm packages merged into the consumer's `devDependencies` (e.g. @types/*, build tooling). Same exact-pinned `name@version` form as `dependencies`. A name declared in both buckets lands in `dependencies` only.",
|
|
76
|
+
"uniqueItems": true,
|
|
77
|
+
"items": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"pattern": "^(@[a-z0-9][a-z0-9._-]*/)?[a-z0-9][a-z0-9._-]*@\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?(\\+[0-9A-Za-z.-]+)?$"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"files": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"description": "Files to copy into the project. `path` is relative to the module folder; `target` resolves through the project's alias map.",
|
|
85
|
+
"items": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"additionalProperties": false,
|
|
88
|
+
"required": ["path", "target"],
|
|
89
|
+
"properties": {
|
|
90
|
+
"path": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"minLength": 1,
|
|
93
|
+
"not": { "pattern": "(^|/)\\.\\.?(/|$)" },
|
|
94
|
+
"description": "Source path relative to the module folder (e.g. files/api/routes/waitlist.ts). No '.' or '..' path segment: the applier refuses one at runtime and this fails the descriptor at authoring time (#98). A dot-leading file name such as .gitignore is fine."
|
|
95
|
+
},
|
|
96
|
+
"target": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"pattern": "^@[a-z0-9][a-z0-9-]*/.+",
|
|
99
|
+
"not": { "pattern": "(^|/)\\.\\.?(/|$)" },
|
|
100
|
+
"description": "Alias-prefixed destination (e.g. @api/routes/waitlist.ts). No '.' or '..' path segment: the applier refuses one at runtime and this fails the descriptor at authoring time (#98). A dot-leading file name such as .gitignore is fine."
|
|
101
|
+
},
|
|
102
|
+
"onlyWith": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
105
|
+
"description": "Install this file only when the named module is in the resolved install set — this run's dependsOn graph plus what saasaloy.json already records (#99). Two entries may share one `target` when their conditions are disjoint: that is how a module ships a per-driver variant of a dialect-bound file. A target whose entries are all conditional and none matches is a plan-time refusal, not a silently missing file."
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"envVars": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"description": "Environment variables the module needs: NAME → human description (or default/placeholder).",
|
|
113
|
+
"propertyNames": {
|
|
114
|
+
"pattern": "^[A-Z][A-Z0-9_]*$"
|
|
115
|
+
},
|
|
116
|
+
"additionalProperties": {
|
|
117
|
+
"type": "string"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"devVars": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"description": "Local-development values for a subset of `envVars`: NAME → the value `saasaloy add` pre-fills into the generated `apps/api/.dev.vars.example`. Only for a value that is the same on every machine and is not a secret (a loopback URL, a fixed port). Every key here must also appear in `envVars`; a key absent here is written with an empty value for the owner to fill in.",
|
|
123
|
+
"propertyNames": {
|
|
124
|
+
"pattern": "^[A-Z][A-Z0-9_]*$"
|
|
125
|
+
},
|
|
126
|
+
"additionalProperties": {
|
|
127
|
+
"type": "string"
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"removeWarnings": {
|
|
131
|
+
"type": "array",
|
|
132
|
+
"description": "Warnings saved with an installed module and shown before removal. Use this for deployed resources that file and patch removal cannot delete safely.",
|
|
133
|
+
"uniqueItems": true,
|
|
134
|
+
"items": {
|
|
135
|
+
"type": "string",
|
|
136
|
+
"minLength": 1
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"patches": {
|
|
140
|
+
"type": "array",
|
|
141
|
+
"description": "Structural config patches applied at `add` time by the patch engine (issue #7). A flat array where each op names its own target `file` and codemod `kind` (wrangler.jsonc bindings, package.json dependency merges, package.json script entries, Better Auth plugin-array insertions, router chain `.route()` links); kind-specific payload fields are defined by the engine. An empty array means no patches (ADR 0019).",
|
|
142
|
+
"items": {
|
|
143
|
+
"type": "object",
|
|
144
|
+
"required": ["file", "kind"],
|
|
145
|
+
"properties": {
|
|
146
|
+
"file": {
|
|
147
|
+
"type": "string",
|
|
148
|
+
"minLength": 1,
|
|
149
|
+
"pattern": "^[^/@]",
|
|
150
|
+
"not": { "pattern": "(^|/)\\.\\.?(/|$)" },
|
|
151
|
+
"description": "Project-relative POSIX path of the file to patch (e.g. apps/api/wrangler.jsonc). No leading slash or @alias, and no '.' or '..' path segment (#98). A dot-leading file name such as .eslintrc.json is fine."
|
|
152
|
+
},
|
|
153
|
+
"kind": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"enum": [
|
|
156
|
+
"wrangler-binding",
|
|
157
|
+
"package-json-dependency",
|
|
158
|
+
"package-json-script",
|
|
159
|
+
"plugin-array",
|
|
160
|
+
"const-array",
|
|
161
|
+
"chained-route"
|
|
162
|
+
],
|
|
163
|
+
"description": "Which codemod applies this patch. `const-array`, `plugin-array`, and `chained-route` have removal inverses."
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
"allOf": [
|
|
167
|
+
{
|
|
168
|
+
"if": {
|
|
169
|
+
"properties": { "kind": { "const": "const-array" } },
|
|
170
|
+
"required": ["kind"]
|
|
171
|
+
},
|
|
172
|
+
"then": {
|
|
173
|
+
"required": ["constName", "key", "entry"],
|
|
174
|
+
"properties": {
|
|
175
|
+
"constName": {
|
|
176
|
+
"type": "string",
|
|
177
|
+
"minLength": 1,
|
|
178
|
+
"description": "Name of the module-scope const array to extend."
|
|
179
|
+
},
|
|
180
|
+
"key": {
|
|
181
|
+
"type": "string",
|
|
182
|
+
"minLength": 1,
|
|
183
|
+
"description": "Entry property whose JSON value identifies the entry for add and remove."
|
|
184
|
+
},
|
|
185
|
+
"entry": {
|
|
186
|
+
"type": "object",
|
|
187
|
+
"minProperties": 1,
|
|
188
|
+
"description": "JSON object literal appended to the named array."
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"if": {
|
|
195
|
+
"properties": { "kind": { "const": "package-json-script" } },
|
|
196
|
+
"required": ["kind"]
|
|
197
|
+
},
|
|
198
|
+
"then": {
|
|
199
|
+
"required": ["name", "value"],
|
|
200
|
+
"properties": {
|
|
201
|
+
"name": {
|
|
202
|
+
"type": "string",
|
|
203
|
+
"minLength": 1,
|
|
204
|
+
"description": "Script key to upsert into the target's `scripts` map (e.g. db:generate)."
|
|
205
|
+
},
|
|
206
|
+
"value": {
|
|
207
|
+
"type": "string",
|
|
208
|
+
"minLength": 1,
|
|
209
|
+
"description": "Command the script runs (e.g. drizzle-kit generate). An entry already present under this key is never overwritten."
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"if": {
|
|
216
|
+
"properties": { "kind": { "const": "chained-route" } },
|
|
217
|
+
"required": ["kind"]
|
|
218
|
+
},
|
|
219
|
+
"then": {
|
|
220
|
+
"required": ["exportName", "path", "call", "import"],
|
|
221
|
+
"properties": {
|
|
222
|
+
"exportName": {
|
|
223
|
+
"type": "string",
|
|
224
|
+
"minLength": 1,
|
|
225
|
+
"description": "Exported binding whose call chain to extend — `default`, or the name in `export const <name> = …`. A bare `export default app` is followed to its declaration."
|
|
226
|
+
},
|
|
227
|
+
"path": {
|
|
228
|
+
"type": "string",
|
|
229
|
+
"minLength": 1,
|
|
230
|
+
"description": "Route path — `.route()`'s first argument (e.g. /waitlist). Also the match key: a chain already routing this path is left alone, and `remove` finds the link by it."
|
|
231
|
+
},
|
|
232
|
+
"call": {
|
|
233
|
+
"type": "string",
|
|
234
|
+
"minLength": 1,
|
|
235
|
+
"description": "Identifier passed as `.route()`'s second argument (e.g. waitlist) — the sub-router the import supplies."
|
|
236
|
+
},
|
|
237
|
+
"import": {
|
|
238
|
+
"type": "object",
|
|
239
|
+
"additionalProperties": false,
|
|
240
|
+
"required": ["name", "from"],
|
|
241
|
+
"description": "Named import to add for `call`, dropped again on `remove` (with its statement, when it was the only specifier).",
|
|
242
|
+
"properties": {
|
|
243
|
+
"name": {
|
|
244
|
+
"type": "string",
|
|
245
|
+
"minLength": 1,
|
|
246
|
+
"description": "Imported binding name (e.g. waitlist)."
|
|
247
|
+
},
|
|
248
|
+
"from": {
|
|
249
|
+
"type": "string",
|
|
250
|
+
"minLength": 1,
|
|
251
|
+
"description": "Module specifier to import from (e.g. ./routes/waitlist.js)."
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
]
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
"scaffolds": {
|
|
262
|
+
"type": "array",
|
|
263
|
+
"description": "New workspaces a capability module creates (apps/packages). Each entry births one workspace: copies its files to workspace-root-relative targets and registers the aliases it declares into saasaloy.json (ADR 0013).",
|
|
264
|
+
"items": {
|
|
265
|
+
"type": "object",
|
|
266
|
+
"additionalProperties": false,
|
|
267
|
+
"required": ["workspace", "files"],
|
|
268
|
+
"properties": {
|
|
269
|
+
"workspace": {
|
|
270
|
+
"type": "string",
|
|
271
|
+
"minLength": 1,
|
|
272
|
+
"pattern": "^[^/@]",
|
|
273
|
+
"description": "New workspace directory, project-relative POSIX (e.g. apps/api). No leading slash or @alias."
|
|
274
|
+
},
|
|
275
|
+
"aliases": {
|
|
276
|
+
"type": "object",
|
|
277
|
+
"description": "Alias → path-prefix map this scaffold registers into saasaloy.json (e.g. { \"@api\": \"apps/api/src\" }).",
|
|
278
|
+
"propertyNames": {
|
|
279
|
+
"pattern": "^@[a-z0-9][a-z0-9-]*$"
|
|
280
|
+
},
|
|
281
|
+
"additionalProperties": {
|
|
282
|
+
"type": "string",
|
|
283
|
+
"minLength": 1,
|
|
284
|
+
"description": "Project-relative path prefix (POSIX separators, no leading slash)."
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
"files": {
|
|
288
|
+
"type": "array",
|
|
289
|
+
"description": "Files copied into the workspace. `path` is relative to the module folder; `target` is relative to the workspace root (no @alias — the alias root doesn't exist yet).",
|
|
290
|
+
"items": {
|
|
291
|
+
"type": "object",
|
|
292
|
+
"additionalProperties": false,
|
|
293
|
+
"required": ["path", "target"],
|
|
294
|
+
"properties": {
|
|
295
|
+
"path": {
|
|
296
|
+
"type": "string",
|
|
297
|
+
"minLength": 1,
|
|
298
|
+
"not": { "pattern": "(^|/)\\.\\.?(/|$)" },
|
|
299
|
+
"description": "Source path relative to the module folder (e.g. files/src/index.ts). No '.' or '..' path segment: the applier refuses one at runtime and this fails the descriptor at authoring time (#98). A dot-leading file name such as .dev.vars.example is fine."
|
|
300
|
+
},
|
|
301
|
+
"target": {
|
|
302
|
+
"type": "string",
|
|
303
|
+
"minLength": 1,
|
|
304
|
+
"pattern": "^[^/@]",
|
|
305
|
+
"not": { "pattern": "(^|/)\\.\\.?(/|$)" },
|
|
306
|
+
"description": "Destination relative to the workspace root (e.g. src/index.ts). No leading slash or @alias, and no '.' or '..' path segment (#98). A dot-leading file name such as .dev.vars.example is fine."
|
|
307
|
+
},
|
|
308
|
+
"onlyWith": {
|
|
309
|
+
"type": "string",
|
|
310
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
311
|
+
"description": "Install this file only when the named module is in the resolved install set — this run's dependsOn graph plus what saasaloy.json already records (#99). Same rule as a top-level files[] entry: two entries may share one `target` when their conditions are disjoint, and a target no candidate matches is a plan-time refusal. auth's per-driver db-provider.ts lives here rather than in files[], which is why both shapes carry the keyword."
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
"agent": {
|
|
320
|
+
"type": "object",
|
|
321
|
+
"description": "AI context contributed by this module. A module ships skill folder(s) that `add` copies into .claude/skills/ (recorded in the manifest for a clean `remove`); it never edits the committed AGENTS.md/CLAUDE.md.",
|
|
322
|
+
"additionalProperties": false,
|
|
323
|
+
"properties": {
|
|
324
|
+
"skills": {
|
|
325
|
+
"type": "array",
|
|
326
|
+
"description": "Skill folder(s) relative to the module folder, copied into .claude/skills/.",
|
|
327
|
+
"uniqueItems": true,
|
|
328
|
+
"items": {
|
|
329
|
+
"type": "string",
|
|
330
|
+
"minLength": 1
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://saasaloy.dev/schemas/saasaloy-lock.schema.json",
|
|
4
|
+
"title": "saasaloy-lock.json — resolved module provenance",
|
|
5
|
+
"description": "Machine-owned lock at a generated project's root: for every installed module, the registry source it came from, the ref requested, and the exact commit SHA resolved — so a re-install reproduces identical bytes. The npm-style lock to saasaloy.json's intent (build spec §2.11; ADR 0012). Integrity is the commit SHA itself; there is no separate integrity field. The optional `base` object records the template `saasaloy init` rendered, keyed by CLI version and template hash rather than a commit, because the base ships inside the CLI package (ADR 0032).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["lockfileVersion", "modules"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Optional pointer to this schema for editor validation."
|
|
13
|
+
},
|
|
14
|
+
"lockfileVersion": {
|
|
15
|
+
"const": 1,
|
|
16
|
+
"description": "Lock format version; bumped only on a breaking shape change."
|
|
17
|
+
},
|
|
18
|
+
"base": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"description": "Provenance of the base template `init` rendered, or `update` adopted. Absent on a project scaffolded before the record existed; `saasaloy update` writes it.",
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"required": ["name", "cliVersion", "templateHash"],
|
|
23
|
+
"properties": {
|
|
24
|
+
"name": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
27
|
+
"description": "The base app's name — saasaloy.json's `base` field (`web`)."
|
|
28
|
+
},
|
|
29
|
+
"cliVersion": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"minLength": 1,
|
|
32
|
+
"description": "The CLI version that rendered or adopted the base. A label beside the hash: `outdated` compares `templateHash`, and `update` refuses a downgrade when both sides are real semver."
|
|
33
|
+
},
|
|
34
|
+
"templateHash": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
37
|
+
"description": "sha256 over the sorted (path, sha256) pairs of the template as shipped in the CLI package. Differs whenever any shipped template file differs."
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"modules": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"description": "Module name → the source it was resolved from. Holds the full resolved graph (dependencies included), not just the top-level asks.",
|
|
44
|
+
"additionalProperties": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"additionalProperties": false,
|
|
47
|
+
"required": ["source", "ref", "resolved"],
|
|
48
|
+
"properties": {
|
|
49
|
+
"source": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"minLength": 1,
|
|
52
|
+
"description": "Registry source slug: `owner/repo` for a remote GitHub source, or `local` for a SAASALOY_REGISTRY_DIR checkout."
|
|
53
|
+
},
|
|
54
|
+
"ref": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"minLength": 1,
|
|
57
|
+
"description": "The ref requested (branch/tag/SHA), or `local` for a local checkout."
|
|
58
|
+
},
|
|
59
|
+
"resolved": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"minLength": 1,
|
|
62
|
+
"description": "The exact commit SHA the ref resolved to — the integrity anchor. `local` for a local checkout."
|
|
63
|
+
},
|
|
64
|
+
"dependsOn": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"description": "The module's declared dependencies, recorded so the resolved graph is self-describing.",
|
|
67
|
+
"uniqueItems": true,
|
|
68
|
+
"items": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"conflictsWith": {
|
|
74
|
+
"type": "array",
|
|
75
|
+
"description": "The module's declared `conflictsWith` list, recorded because descriptors aren't kept after install — this is what lets `add` refuse a conflicting module that goes in second.",
|
|
76
|
+
"uniqueItems": true,
|
|
77
|
+
"items": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://saasaloy.dev/schemas/saasaloy.schema.json",
|
|
4
|
+
"title": "saasaloy.json — consumer manifest",
|
|
5
|
+
"description": "Alias map + installed-module list that lives at a generated project's root. Resolves module file targets and drives dependsOn resolution (build spec §3.2).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["aliases", "installed"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Optional pointer to this schema for editor validation."
|
|
13
|
+
},
|
|
14
|
+
"aliases": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"description": "Alias → path-prefix map. Keys are `@`-prefixed aliases (e.g. `@api`); values are project-relative directories a module's file targets resolve against.",
|
|
17
|
+
"propertyNames": {
|
|
18
|
+
"pattern": "^@[a-z0-9][a-z0-9-]*$"
|
|
19
|
+
},
|
|
20
|
+
"additionalProperties": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"minLength": 1,
|
|
23
|
+
"description": "Project-relative path prefix (POSIX separators, no leading slash)."
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"base": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
29
|
+
"description": "The base app `saasaloy init` scaffolded (e.g. `web`). It is not a module: the tool never applied it, it has no descriptor and no lock entry, so it never takes part in dependsOn resolution or conflict checks. Absent in a project scaffolded before this field existed; `saasaloy` migrates such a project on its next write."
|
|
30
|
+
},
|
|
31
|
+
"installed": {
|
|
32
|
+
"type": "array",
|
|
33
|
+
"description": "Modules present in this project. Grows as `saasaloy add` runs; drives dependsOn resolution.",
|
|
34
|
+
"uniqueItems": true,
|
|
35
|
+
"items": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
38
|
+
"description": "Module name (lowercase, digits, hyphens)."
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|