minutework 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.
Files changed (203) hide show
  1. package/EXTERNAL_ALPHA.md +74 -0
  2. package/README.md +57 -0
  3. package/assets/claude-local/CLAUDE.md.template +45 -0
  4. package/assets/claude-local/bundle.json +22 -0
  5. package/assets/claude-local/skills/README.md +6 -0
  6. package/assets/claude-local/skills/app-pack-authoring.md +8 -0
  7. package/assets/claude-local/skills/event-bus.md +8 -0
  8. package/assets/claude-local/skills/ontology-mapping.md +8 -0
  9. package/assets/claude-local/skills/openclaw-skill-importer.md +7 -0
  10. package/assets/claude-local/skills/schema-engine.md +8 -0
  11. package/assets/claude-local/skills/secrets-runtime-bridge.md +9 -0
  12. package/assets/claude-local/skills/sidecar-generation.md +9 -0
  13. package/assets/templates/fastapi-sidecar/.env.example +8 -0
  14. package/assets/templates/fastapi-sidecar/README.md +77 -0
  15. package/assets/templates/fastapi-sidecar/poetry.lock +757 -0
  16. package/assets/templates/fastapi-sidecar/pyproject.toml +42 -0
  17. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/__init__.py +3 -0
  18. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/auth.py +70 -0
  19. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/bridge/__init__.py +3 -0
  20. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/bridge/client.py +71 -0
  21. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/logging_utils.py +25 -0
  22. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/main.py +85 -0
  23. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/receipts.py +24 -0
  24. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/settings.py +41 -0
  25. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/template_validation.py +26 -0
  26. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/worker.py +33 -0
  27. package/assets/templates/fastapi-sidecar/template.json +43 -0
  28. package/assets/templates/fastapi-sidecar/template.schema.json +160 -0
  29. package/assets/templates/fastapi-sidecar/tests/conftest.py +36 -0
  30. package/assets/templates/fastapi-sidecar/tests/test_app.py +39 -0
  31. package/assets/templates/fastapi-sidecar/tests/test_auth.py +32 -0
  32. package/assets/templates/fastapi-sidecar/tests/test_bridge_client.py +31 -0
  33. package/assets/templates/fastapi-sidecar/tests/test_materialization.py +55 -0
  34. package/assets/templates/fastapi-sidecar/tests/test_template_contract.py +49 -0
  35. package/assets/templates/fastapi-sidecar/tests/test_worker.py +7 -0
  36. package/assets/templates/fastapi-sidecar/tools/template/validate_template.py +20 -0
  37. package/assets/templates/next-tenant-app/.env.example +8 -0
  38. package/assets/templates/next-tenant-app/.storybook/main.ts +19 -0
  39. package/assets/templates/next-tenant-app/.storybook/preview.tsx +38 -0
  40. package/assets/templates/next-tenant-app/README.md +115 -0
  41. package/assets/templates/next-tenant-app/components.json +21 -0
  42. package/assets/templates/next-tenant-app/eslint.config.mjs +41 -0
  43. package/assets/templates/next-tenant-app/next-env.d.ts +6 -0
  44. package/assets/templates/next-tenant-app/next.config.ts +8 -0
  45. package/assets/templates/next-tenant-app/package-lock.json +9682 -0
  46. package/assets/templates/next-tenant-app/package.json +59 -0
  47. package/assets/templates/next-tenant-app/pnpm-lock.yaml +6062 -0
  48. package/assets/templates/next-tenant-app/postcss.config.mjs +8 -0
  49. package/assets/templates/next-tenant-app/src/app/api/auth/context/route.test.ts +90 -0
  50. package/assets/templates/next-tenant-app/src/app/api/auth/context/route.ts +78 -0
  51. package/assets/templates/next-tenant-app/src/app/api/auth/login/route.ts +31 -0
  52. package/assets/templates/next-tenant-app/src/app/api/auth/logout/route.ts +16 -0
  53. package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.test.ts +79 -0
  54. package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.ts +40 -0
  55. package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.test.ts +42 -0
  56. package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.ts +29 -0
  57. package/assets/templates/next-tenant-app/src/app/api/auth/session/route.ts +26 -0
  58. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.test.ts +40 -0
  59. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.ts +47 -0
  60. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.test.ts +43 -0
  61. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.ts +45 -0
  62. package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.test.ts +83 -0
  63. package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.tsx +30 -0
  64. package/assets/templates/next-tenant-app/src/app/app/layout.tsx +20 -0
  65. package/assets/templates/next-tenant-app/src/app/app/page.test.ts +62 -0
  66. package/assets/templates/next-tenant-app/src/app/app/page.tsx +24 -0
  67. package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.test.ts +70 -0
  68. package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.tsx +57 -0
  69. package/assets/templates/next-tenant-app/src/app/blog/page.test.ts +42 -0
  70. package/assets/templates/next-tenant-app/src/app/blog/page.tsx +37 -0
  71. package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.test.ts +70 -0
  72. package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.tsx +55 -0
  73. package/assets/templates/next-tenant-app/src/app/docs/page.test.ts +42 -0
  74. package/assets/templates/next-tenant-app/src/app/docs/page.tsx +37 -0
  75. package/assets/templates/next-tenant-app/src/app/globals.css +70 -0
  76. package/assets/templates/next-tenant-app/src/app/layout.tsx +69 -0
  77. package/assets/templates/next-tenant-app/src/app/login/page.test.ts +55 -0
  78. package/assets/templates/next-tenant-app/src/app/login/page.tsx +33 -0
  79. package/assets/templates/next-tenant-app/src/app/page.test.ts +56 -0
  80. package/assets/templates/next-tenant-app/src/app/page.tsx +35 -0
  81. package/assets/templates/next-tenant-app/src/app/pricing/page.test.ts +55 -0
  82. package/assets/templates/next-tenant-app/src/app/pricing/page.tsx +35 -0
  83. package/assets/templates/next-tenant-app/src/app/providers.tsx +25 -0
  84. package/assets/templates/next-tenant-app/src/app/robots.test.ts +20 -0
  85. package/assets/templates/next-tenant-app/src/app/robots.ts +18 -0
  86. package/assets/templates/next-tenant-app/src/app/sitemap.test.ts +49 -0
  87. package/assets/templates/next-tenant-app/src/app/sitemap.ts +54 -0
  88. package/assets/templates/next-tenant-app/src/components/ui/button.tsx +59 -0
  89. package/assets/templates/next-tenant-app/src/components/ui/input.tsx +21 -0
  90. package/assets/templates/next-tenant-app/src/design-system/docs/governance.mdx +26 -0
  91. package/assets/templates/next-tenant-app/src/design-system/patterns/panel-frame.stories.tsx +48 -0
  92. package/assets/templates/next-tenant-app/src/design-system/patterns/panel-frame.tsx +26 -0
  93. package/assets/templates/next-tenant-app/src/design-system/patterns/status-badge.stories.tsx +26 -0
  94. package/assets/templates/next-tenant-app/src/design-system/patterns/status-badge.tsx +35 -0
  95. package/assets/templates/next-tenant-app/src/design-system/patterns/theme-mode-toggle.stories.tsx +21 -0
  96. package/assets/templates/next-tenant-app/src/design-system/patterns/theme-mode-toggle.tsx +75 -0
  97. package/assets/templates/next-tenant-app/src/design-system/primitives/button.stories.tsx +37 -0
  98. package/assets/templates/next-tenant-app/src/design-system/primitives/button.ts +1 -0
  99. package/assets/templates/next-tenant-app/src/design-system/primitives/input.stories.tsx +26 -0
  100. package/assets/templates/next-tenant-app/src/design-system/primitives/input.ts +1 -0
  101. package/assets/templates/next-tenant-app/src/design-system/recipes/chrome.ts +28 -0
  102. package/assets/templates/next-tenant-app/src/design-system/tokens/foundation.css +31 -0
  103. package/assets/templates/next-tenant-app/src/design-system/tokens/index.css +3 -0
  104. package/assets/templates/next-tenant-app/src/design-system/tokens/manifest.json +85 -0
  105. package/assets/templates/next-tenant-app/src/design-system/tokens/manifest.ts +87 -0
  106. package/assets/templates/next-tenant-app/src/design-system/tokens/semantic.css +105 -0
  107. package/assets/templates/next-tenant-app/src/design-system/tokens/theme.css +59 -0
  108. package/assets/templates/next-tenant-app/src/design-system/tokens/tokens.stories.tsx +71 -0
  109. package/assets/templates/next-tenant-app/src/features/auth/components/login-screen.tsx +198 -0
  110. package/assets/templates/next-tenant-app/src/features/dashboard/components/tenant-dashboard.tsx +153 -0
  111. package/assets/templates/next-tenant-app/src/features/examples/runtime-command-demo/components/runtime-command-demo.tsx +342 -0
  112. package/assets/templates/next-tenant-app/src/features/public-shell/components/content-article.tsx +66 -0
  113. package/assets/templates/next-tenant-app/src/features/public-shell/components/content-collection.tsx +108 -0
  114. package/assets/templates/next-tenant-app/src/features/public-shell/components/marketing-page-canvas.tsx +111 -0
  115. package/assets/templates/next-tenant-app/src/features/public-shell/components/public-site-shell.tsx +111 -0
  116. package/assets/templates/next-tenant-app/src/features/shell/components/private-app-shell.tsx +624 -0
  117. package/assets/templates/next-tenant-app/src/lib/app-routes.test.ts +20 -0
  118. package/assets/templates/next-tenant-app/src/lib/app-routes.ts +59 -0
  119. package/assets/templates/next-tenant-app/src/lib/content/__fixtures__/public-site-snapshot.ts +189 -0
  120. package/assets/templates/next-tenant-app/src/lib/content/adapter.server.test.ts +318 -0
  121. package/assets/templates/next-tenant-app/src/lib/content/adapter.server.ts +232 -0
  122. package/assets/templates/next-tenant-app/src/lib/content/contracts.ts +339 -0
  123. package/assets/templates/next-tenant-app/src/lib/content/custom-adapter.ts +5 -0
  124. package/assets/templates/next-tenant-app/src/lib/content/empty-state.ts +96 -0
  125. package/assets/templates/next-tenant-app/src/lib/platform/auth.server.test.ts +75 -0
  126. package/assets/templates/next-tenant-app/src/lib/platform/auth.server.ts +25 -0
  127. package/assets/templates/next-tenant-app/src/lib/platform/client.server.test.ts +170 -0
  128. package/assets/templates/next-tenant-app/src/lib/platform/client.server.ts +661 -0
  129. package/assets/templates/next-tenant-app/src/lib/platform/contracts.ts +131 -0
  130. package/assets/templates/next-tenant-app/src/lib/platform/endpoints.server.ts +34 -0
  131. package/assets/templates/next-tenant-app/src/lib/platform/env.server.test.ts +102 -0
  132. package/assets/templates/next-tenant-app/src/lib/platform/env.server.ts +87 -0
  133. package/assets/templates/next-tenant-app/src/lib/platform/route-response.ts +33 -0
  134. package/assets/templates/next-tenant-app/src/lib/platform/session.server.ts +108 -0
  135. package/assets/templates/next-tenant-app/src/lib/public-site.test.ts +20 -0
  136. package/assets/templates/next-tenant-app/src/lib/public-site.ts +49 -0
  137. package/assets/templates/next-tenant-app/src/lib/theme-config.ts +10 -0
  138. package/assets/templates/next-tenant-app/src/lib/theme.tsx +159 -0
  139. package/assets/templates/next-tenant-app/src/lib/utils.ts +6 -0
  140. package/assets/templates/next-tenant-app/template.json +27 -0
  141. package/assets/templates/next-tenant-app/template.schema.json +160 -0
  142. package/assets/templates/next-tenant-app/test/server-only-stub.ts +1 -0
  143. package/assets/templates/next-tenant-app/tools/design-system/build-token-manifest.mjs +3 -0
  144. package/assets/templates/next-tenant-app/tools/design-system/check-imports.mjs +9 -0
  145. package/assets/templates/next-tenant-app/tools/design-system/check-stories.mjs +9 -0
  146. package/assets/templates/next-tenant-app/tools/design-system/check-values.mjs +9 -0
  147. package/assets/templates/next-tenant-app/tools/design-system/checks.mjs +238 -0
  148. package/assets/templates/next-tenant-app/tools/design-system/eslint-plugin-design-system.mjs +184 -0
  149. package/assets/templates/next-tenant-app/tools/design-system/playwright.config.mjs +34 -0
  150. package/assets/templates/next-tenant-app/tools/design-system/run-checks.mjs +22 -0
  151. package/assets/templates/next-tenant-app/tools/design-system/shared.mjs +166 -0
  152. package/assets/templates/next-tenant-app/tools/design-system/visual.spec.ts +41 -0
  153. package/assets/templates/next-tenant-app/tools/template/validate-route-contract.mjs +39 -0
  154. package/assets/templates/next-tenant-app/tools/template/validate-template.mjs +45 -0
  155. package/assets/templates/next-tenant-app/tsconfig.json +42 -0
  156. package/assets/templates/next-tenant-app/vitest.config.ts +25 -0
  157. package/bin/minutework.js +40 -0
  158. package/dist/auth.d.ts +59 -0
  159. package/dist/auth.js +338 -0
  160. package/dist/auth.js.map +1 -0
  161. package/dist/browser.d.ts +1 -0
  162. package/dist/browser.js +26 -0
  163. package/dist/browser.js.map +1 -0
  164. package/dist/cli.d.ts +2 -0
  165. package/dist/cli.js +5 -0
  166. package/dist/cli.js.map +1 -0
  167. package/dist/compile.d.ts +20 -0
  168. package/dist/compile.js +121 -0
  169. package/dist/compile.js.map +1 -0
  170. package/dist/config.d.ts +25 -0
  171. package/dist/config.js +102 -0
  172. package/dist/config.js.map +1 -0
  173. package/dist/deploy-state.d.ts +35 -0
  174. package/dist/deploy-state.js +30 -0
  175. package/dist/deploy-state.js.map +1 -0
  176. package/dist/deploy.d.ts +22 -0
  177. package/dist/deploy.js +308 -0
  178. package/dist/deploy.js.map +1 -0
  179. package/dist/developer-client.d.ts +88 -0
  180. package/dist/developer-client.js +78 -0
  181. package/dist/developer-client.js.map +1 -0
  182. package/dist/index.d.ts +27 -0
  183. package/dist/index.js +290 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/init.d.ts +22 -0
  186. package/dist/init.js +421 -0
  187. package/dist/init.js.map +1 -0
  188. package/dist/launcher.d.ts +1 -0
  189. package/dist/launcher.js +50 -0
  190. package/dist/launcher.js.map +1 -0
  191. package/dist/paths.d.ts +12 -0
  192. package/dist/paths.js +33 -0
  193. package/dist/paths.js.map +1 -0
  194. package/dist/sandbox.d.ts +30 -0
  195. package/dist/sandbox.js +852 -0
  196. package/dist/sandbox.js.map +1 -0
  197. package/dist/state.d.ts +46 -0
  198. package/dist/state.js +82 -0
  199. package/dist/state.js.map +1 -0
  200. package/dist/tokens.d.ts +14 -0
  201. package/dist/tokens.js +293 -0
  202. package/dist/tokens.js.map +1 -0
  203. package/package.json +43 -0
@@ -0,0 +1,184 @@
1
+ import {
2
+ arbitraryVisualUtilityPattern,
3
+ isFeatureSurfaceFile,
4
+ matchesPattern,
5
+ rawColorPattern,
6
+ } from "./shared.mjs";
7
+
8
+ function filenameOf(context) {
9
+ return context.filename ?? context.getFilename();
10
+ }
11
+
12
+ function stringValueFromNode(node) {
13
+ if (!node) {
14
+ return null;
15
+ }
16
+
17
+ if (node.type === "Literal" && typeof node.value === "string") {
18
+ return node.value;
19
+ }
20
+
21
+ if (node.type === "TemplateElement") {
22
+ return node.value.raw;
23
+ }
24
+
25
+ return null;
26
+ }
27
+
28
+ function reportIfPatternMatches(context, node, pattern, message) {
29
+ const value = stringValueFromNode(node);
30
+ if (!value || !matchesPattern(pattern, value)) {
31
+ return;
32
+ }
33
+
34
+ context.report({
35
+ node,
36
+ message,
37
+ });
38
+ }
39
+
40
+ const noDesignSystemBypassImports = {
41
+ meta: {
42
+ type: "problem",
43
+ schema: [],
44
+ },
45
+ create(context) {
46
+ return {
47
+ ImportDeclaration(node) {
48
+ if (!isFeatureSurfaceFile(filenameOf(context))) {
49
+ return;
50
+ }
51
+
52
+ const source = node.source.value;
53
+ if (typeof source !== "string") {
54
+ return;
55
+ }
56
+
57
+ if (source.startsWith("@/components/ui/")) {
58
+ context.report({
59
+ node,
60
+ message:
61
+ "Use @/design-system/primitives/* instead of importing from @/components/ui/* in feature code.",
62
+ });
63
+ }
64
+
65
+ if (source.startsWith("@radix-ui/")) {
66
+ context.report({
67
+ node,
68
+ message:
69
+ "Radix imports are only allowed inside src/design-system; feature code must import public design-system primitives.",
70
+ });
71
+ }
72
+
73
+ if (
74
+ source === "@/design-system/tokens" ||
75
+ (source.startsWith("@/design-system/tokens/") &&
76
+ source !== "@/design-system/tokens/manifest")
77
+ ) {
78
+ context.report({
79
+ node,
80
+ message:
81
+ "Feature code cannot import raw token modules directly; use semantic classes or @/design-system/tokens/manifest.",
82
+ });
83
+ }
84
+ },
85
+ };
86
+ },
87
+ };
88
+
89
+ const noCvaOutsideDesignSystem = {
90
+ meta: {
91
+ type: "problem",
92
+ schema: [],
93
+ },
94
+ create(context) {
95
+ return {
96
+ ImportDeclaration(node) {
97
+ if (!isFeatureSurfaceFile(filenameOf(context))) {
98
+ return;
99
+ }
100
+
101
+ if (node.source.value === "class-variance-authority") {
102
+ context.report({
103
+ node,
104
+ message:
105
+ "Feature code cannot import class-variance-authority directly; use recipes from src/design-system/recipes.",
106
+ });
107
+ }
108
+ },
109
+ };
110
+ },
111
+ };
112
+
113
+ const noRawDesignValues = {
114
+ meta: {
115
+ type: "problem",
116
+ schema: [],
117
+ },
118
+ create(context) {
119
+ return {
120
+ Literal(node) {
121
+ reportIfPatternMatches(
122
+ context,
123
+ node,
124
+ rawColorPattern,
125
+ "Raw color literals are forbidden outside src/design-system/tokens.",
126
+ );
127
+ },
128
+ TemplateElement(node) {
129
+ reportIfPatternMatches(
130
+ context,
131
+ node,
132
+ rawColorPattern,
133
+ "Raw color literals are forbidden outside src/design-system/tokens.",
134
+ );
135
+ },
136
+ };
137
+ },
138
+ };
139
+
140
+ const noBespokeVisualStacksInFeatures = {
141
+ meta: {
142
+ type: "problem",
143
+ schema: [],
144
+ },
145
+ create(context) {
146
+ return {
147
+ Literal(node) {
148
+ if (!isFeatureSurfaceFile(filenameOf(context))) {
149
+ return;
150
+ }
151
+
152
+ reportIfPatternMatches(
153
+ context,
154
+ node,
155
+ arbitraryVisualUtilityPattern,
156
+ "Feature code cannot use arbitrary visual Tailwind utilities; use tokens or design-system recipes instead.",
157
+ );
158
+ },
159
+ TemplateElement(node) {
160
+ if (!isFeatureSurfaceFile(filenameOf(context))) {
161
+ return;
162
+ }
163
+
164
+ reportIfPatternMatches(
165
+ context,
166
+ node,
167
+ arbitraryVisualUtilityPattern,
168
+ "Feature code cannot use arbitrary visual Tailwind utilities; use tokens or design-system recipes instead.",
169
+ );
170
+ },
171
+ };
172
+ },
173
+ };
174
+
175
+ const designSystemPlugin = {
176
+ rules: {
177
+ "no-design-system-bypass-imports": noDesignSystemBypassImports,
178
+ "no-cva-outside-design-system": noCvaOutsideDesignSystem,
179
+ "no-raw-design-values": noRawDesignValues,
180
+ "no-bespoke-visual-stacks-in-features": noBespokeVisualStacksInFeatures,
181
+ },
182
+ };
183
+
184
+ export default designSystemPlugin;
@@ -0,0 +1,34 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ import { defineConfig, devices } from "@playwright/test";
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const repoRoot = path.resolve(__dirname, "../..");
8
+
9
+ export default defineConfig({
10
+ testDir: __dirname,
11
+ testMatch: "visual.spec.ts",
12
+ fullyParallel: true,
13
+ retries: 0,
14
+ reporter: "line",
15
+ snapshotPathTemplate: "{testDir}/__screenshots__/{testFilePath}/{arg}{ext}",
16
+ use: {
17
+ baseURL: "http://127.0.0.1:6007",
18
+ trace: "on-first-retry",
19
+ },
20
+ webServer: {
21
+ command: "pnpm exec http-server ./storybook-static -p 6007 -s",
22
+ cwd: repoRoot,
23
+ url: "http://127.0.0.1:6007",
24
+ reuseExistingServer: !process.env.CI,
25
+ },
26
+ projects: [
27
+ {
28
+ name: "chromium",
29
+ use: {
30
+ ...devices["Desktop Chrome"],
31
+ },
32
+ },
33
+ ],
34
+ });
@@ -0,0 +1,22 @@
1
+ import {
2
+ buildTokenManifest,
3
+ checkImports,
4
+ checkStories,
5
+ checkValues,
6
+ } from "./checks.mjs";
7
+ import { parseCliArgs, resolveTargetFiles } from "./shared.mjs";
8
+
9
+ const options = parseCliArgs();
10
+ const targetFiles = resolveTargetFiles(options);
11
+
12
+ buildTokenManifest();
13
+
14
+ const violations = [
15
+ ...checkImports(targetFiles),
16
+ ...checkValues(targetFiles),
17
+ ...checkStories(targetFiles, options),
18
+ ];
19
+
20
+ if (violations.length > 0) {
21
+ process.exit(1);
22
+ }
@@ -0,0 +1,166 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, readdirSync, statSync } from "node:fs";
3
+ import { extname, join, relative, resolve, sep } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ export const repoRoot = resolve(fileURLToPath(new URL("../..", import.meta.url)));
7
+
8
+ const SOURCE_EXTENSIONS = new Set([
9
+ ".ts",
10
+ ".tsx",
11
+ ".js",
12
+ ".jsx",
13
+ ".mjs",
14
+ ".cjs",
15
+ ".css",
16
+ ".mdx",
17
+ ]);
18
+
19
+ const DEFAULT_ROOTS = ["src", "tools/design-system", ".storybook"];
20
+
21
+ export const rawColorPattern = /#(?:[\da-fA-F]{3,8})\b|(?:rgb|hsl|oklch)a?\([^)]*\)/g;
22
+ export const arbitraryVisualUtilityPattern =
23
+ /\b(?:bg|text|border|ring|shadow|from|to|via|fill|stroke)-\[[^\]]+\]/g;
24
+ export const literalStyleVisualPattern =
25
+ /style=\{\{[^}]*\b(?:color|background(?:Color)?|border(?:Color)?|boxShadow|fill|stroke)\s*:\s*['"`](?!var\(--)[^'"`]+['"`]/gs;
26
+
27
+ function toPosix(filePath) {
28
+ return filePath.split(sep).join("/");
29
+ }
30
+
31
+ export function normalizePath(filePath) {
32
+ return toPosix(relative(repoRoot, resolve(repoRoot, filePath)));
33
+ }
34
+
35
+ export function absolutePath(filePath) {
36
+ return resolve(repoRoot, filePath);
37
+ }
38
+
39
+ function walkDirectory(directoryPath) {
40
+ const entries = readdirSync(directoryPath, { withFileTypes: true });
41
+ const files = [];
42
+
43
+ for (const entry of entries) {
44
+ const absoluteEntryPath = join(directoryPath, entry.name);
45
+
46
+ if (entry.isDirectory()) {
47
+ files.push(...walkDirectory(absoluteEntryPath));
48
+ continue;
49
+ }
50
+
51
+ if (SOURCE_EXTENSIONS.has(extname(entry.name))) {
52
+ files.push(normalizePath(absoluteEntryPath));
53
+ }
54
+ }
55
+
56
+ return files;
57
+ }
58
+
59
+ export function getSourceFiles() {
60
+ const files = new Set();
61
+
62
+ for (const root of DEFAULT_ROOTS) {
63
+ const absoluteRoot = absolutePath(root);
64
+ if (!existsSync(absoluteRoot) || !statSync(absoluteRoot).isDirectory()) {
65
+ continue;
66
+ }
67
+
68
+ for (const filePath of walkDirectory(absoluteRoot)) {
69
+ files.add(filePath);
70
+ }
71
+ }
72
+
73
+ return [...files].sort();
74
+ }
75
+
76
+ export function getStagedFiles() {
77
+ try {
78
+ const output = execFileSync(
79
+ "git",
80
+ ["diff", "--name-only", "--cached", "--diff-filter=ACMR"],
81
+ { cwd: repoRoot, encoding: "utf8" },
82
+ );
83
+
84
+ return output
85
+ .split("\n")
86
+ .map((line) => line.trim())
87
+ .filter(Boolean)
88
+ .map(normalizePath);
89
+ } catch {
90
+ return [];
91
+ }
92
+ }
93
+
94
+ export function parseCliArgs(argv = process.argv.slice(2)) {
95
+ const options = {
96
+ staged: false,
97
+ files: [],
98
+ };
99
+
100
+ for (let index = 0; index < argv.length; index += 1) {
101
+ const arg = argv[index];
102
+ if (arg === "--staged") {
103
+ options.staged = true;
104
+ continue;
105
+ }
106
+
107
+ if (arg === "--files") {
108
+ options.files.push(...argv.slice(index + 1));
109
+ break;
110
+ }
111
+
112
+ options.files.push(arg);
113
+ }
114
+
115
+ return options;
116
+ }
117
+
118
+ export function resolveTargetFiles({ staged = false, files = [] } = {}) {
119
+ const candidates =
120
+ files.length > 0 ? files : staged ? getStagedFiles() : getSourceFiles();
121
+ const normalizedCandidates = candidates
122
+ .map(normalizePath)
123
+ .filter((candidate) => SOURCE_EXTENSIONS.has(extname(candidate)));
124
+
125
+ if (normalizedCandidates.length > 0) {
126
+ return [...new Set(normalizedCandidates)].sort();
127
+ }
128
+
129
+ return getSourceFiles();
130
+ }
131
+
132
+ export function isTokenFile(filePath) {
133
+ const normalizedPath = normalizePath(filePath);
134
+ return (
135
+ normalizedPath.startsWith("src/design-system/tokens/") &&
136
+ (normalizedPath.endsWith(".css") ||
137
+ normalizedPath.endsWith("/manifest.ts") ||
138
+ normalizedPath.endsWith("/manifest.json"))
139
+ );
140
+ }
141
+
142
+ export function isFeatureSurfaceFile(filePath) {
143
+ const normalizedPath = normalizePath(filePath);
144
+ return (
145
+ normalizedPath.startsWith("src/app/") ||
146
+ normalizedPath.startsWith("src/features/")
147
+ );
148
+ }
149
+
150
+ export function isGovernedVisualFile(filePath) {
151
+ const normalizedPath = normalizePath(filePath);
152
+ return (
153
+ normalizedPath.startsWith("src/app/") ||
154
+ normalizedPath.startsWith("src/features/") ||
155
+ normalizedPath.startsWith("src/design-system/")
156
+ );
157
+ }
158
+
159
+ export function isLegacyUiImport(specifier) {
160
+ return specifier.startsWith("@/components/ui/");
161
+ }
162
+
163
+ export function matchesPattern(pattern, value) {
164
+ pattern.lastIndex = 0;
165
+ return pattern.test(value);
166
+ }
@@ -0,0 +1,41 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ const storyCases = [
4
+ {
5
+ id: "design-system-primitives-button--primary",
6
+ name: "button-primary.png",
7
+ viewport: { width: 480, height: 220 },
8
+ },
9
+ {
10
+ id: "design-system-primitives-input--default",
11
+ name: "input-default.png",
12
+ viewport: { width: 520, height: 220 },
13
+ },
14
+ {
15
+ id: "design-system-patterns-panel-frame--raised",
16
+ name: "panel-frame-raised.png",
17
+ viewport: { width: 560, height: 340 },
18
+ },
19
+ {
20
+ id: "design-system-patterns-status-badge--all-tones",
21
+ name: "status-badge-tones.png",
22
+ viewport: { width: 520, height: 220 },
23
+ },
24
+ {
25
+ id: "design-system-patterns-theme-mode-toggle--default",
26
+ name: "theme-mode-toggle-default.png",
27
+ viewport: { width: 420, height: 260 },
28
+ },
29
+ ];
30
+
31
+ for (const storyCase of storyCases) {
32
+ test(storyCase.id, async ({ page }) => {
33
+ await page.setViewportSize(storyCase.viewport);
34
+ await page.goto(`/iframe.html?id=${storyCase.id}&viewMode=story`);
35
+ await page.addStyleTag({
36
+ content:
37
+ "*{animation:none !important;transition:none !important;caret-color:transparent !important;}",
38
+ });
39
+ await expect(page.locator("#storybook-root")).toHaveScreenshot(storyCase.name);
40
+ });
41
+ }
@@ -0,0 +1,39 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const templateRoot = path.resolve(__dirname, "..", "..");
7
+
8
+ const requiredPaths = [
9
+ "src/app/page.tsx",
10
+ "src/app/pricing/page.tsx",
11
+ "src/app/docs/page.tsx",
12
+ "src/app/docs/[...slug]/page.tsx",
13
+ "src/app/blog/page.tsx",
14
+ "src/app/blog/[slug]/page.tsx",
15
+ "src/app/login/page.tsx",
16
+ "src/app/app/layout.tsx",
17
+ "src/app/app/page.tsx",
18
+ "src/app/app/examples/runtime-commands/page.tsx",
19
+ "src/app/robots.ts",
20
+ "src/app/sitemap.ts",
21
+ "src/lib/content/contracts.ts",
22
+ "src/lib/content/custom-adapter.ts",
23
+ "src/lib/content/empty-state.ts",
24
+ "src/lib/content/adapter.server.ts",
25
+ ];
26
+
27
+ const missingPaths = requiredPaths.filter(
28
+ (relativePath) => !existsSync(path.join(templateRoot, relativePath)),
29
+ );
30
+
31
+ if (missingPaths.length > 0) {
32
+ throw new Error(
33
+ `Combined route contract is incomplete.\n${missingPaths
34
+ .map((relativePath) => `- ${relativePath}`)
35
+ .join("\n")}`,
36
+ );
37
+ }
38
+
39
+ console.log("combined route contract is valid");
@@ -0,0 +1,45 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import Ajv2020 from "ajv/dist/2020.js";
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const templateRoot = path.resolve(__dirname, "..", "..");
9
+ const bundledSchemaPath = path.join(templateRoot, "template.schema.json");
10
+ const sharedSchemaPath = path.resolve(templateRoot, "..", "template.schema.json");
11
+ const manifestPath = path.join(templateRoot, "template.json");
12
+
13
+ const bundledSchemaSource = readFileSync(bundledSchemaPath, "utf8");
14
+ const sharedSchemaSource = existsSync(sharedSchemaPath)
15
+ ? readFileSync(sharedSchemaPath, "utf8")
16
+ : null;
17
+
18
+ function normalizeJson(source) {
19
+ return JSON.stringify(JSON.parse(source));
20
+ }
21
+
22
+ if (
23
+ sharedSchemaSource &&
24
+ normalizeJson(sharedSchemaSource) !== normalizeJson(bundledSchemaSource)
25
+ ) {
26
+ throw new Error(
27
+ `Bundled template schema is out of sync with the shared schema at ${sharedSchemaPath}`,
28
+ );
29
+ }
30
+
31
+ const schema = JSON.parse(sharedSchemaSource ?? bundledSchemaSource);
32
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
33
+
34
+ const ajv = new Ajv2020({ allErrors: true, strict: false });
35
+ const validate = ajv.compile(schema);
36
+
37
+ if (!validate(manifest)) {
38
+ const details = (validate.errors ?? [])
39
+ .map((error) => `${error.instancePath || "/"} ${error.message ?? "invalid"}`)
40
+ .join("\n");
41
+
42
+ throw new Error(`Template manifest validation failed.\n${details}`);
43
+ }
44
+
45
+ console.log("template.json is valid");
@@ -0,0 +1,42 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "es2022"
8
+ ],
9
+ "allowJs": false,
10
+ "skipLibCheck": true,
11
+ "strict": true,
12
+ "noEmit": true,
13
+ "esModuleInterop": true,
14
+ "baseUrl": ".",
15
+ "module": "esnext",
16
+ "moduleResolution": "bundler",
17
+ "resolveJsonModule": true,
18
+ "isolatedModules": true,
19
+ "jsx": "react-jsx",
20
+ "incremental": true,
21
+ "paths": {
22
+ "@/*": [
23
+ "./src/*"
24
+ ]
25
+ },
26
+ "plugins": [
27
+ {
28
+ "name": "next"
29
+ }
30
+ ]
31
+ },
32
+ "include": [
33
+ "next-env.d.ts",
34
+ "**/*.ts",
35
+ "**/*.tsx",
36
+ ".next/types/**/*.ts",
37
+ ".next/dev/types/**/*.ts"
38
+ ],
39
+ "exclude": [
40
+ "node_modules"
41
+ ]
42
+ }
@@ -0,0 +1,25 @@
1
+ import path from "node:path";
2
+
3
+ import { defineConfig } from "vitest/config";
4
+
5
+ export default defineConfig({
6
+ resolve: {
7
+ alias: {
8
+ "@": path.resolve(__dirname, "src"),
9
+ "server-only": path.resolve(__dirname, "test/server-only-stub.ts"),
10
+ },
11
+ },
12
+ test: {
13
+ env: {
14
+ MW_PLATFORM_BASE_URL: "http://127.0.0.1:8000",
15
+ MW_CONTENT_API_TOKEN: "test-content-token",
16
+ MW_PUBLIC_BASE_URL: "http://127.0.0.1:3000",
17
+ MW_PUBLIC_SITE_PROPERTY_KEY: "main-site",
18
+ MW_PUBLIC_SITE_ENV: "preview",
19
+ },
20
+ environment: "node",
21
+ globals: true,
22
+ include: ["src/**/*.test.ts"],
23
+ restoreMocks: true,
24
+ },
25
+ });
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+
7
+ import { createJiti } from "jiti";
8
+
9
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
10
+ const srcLauncherPath = path.join(packageRoot, "src", "launcher.ts");
11
+ const distLauncherPath = path.join(packageRoot, "dist", "launcher.js");
12
+
13
+ const runCliFromPackageRoot = existsSync(srcLauncherPath)
14
+ ? await loadSourceLauncher(srcLauncherPath)
15
+ : await loadDistLauncher(distLauncherPath);
16
+
17
+ const exitCode = await runCliFromPackageRoot(import.meta.url, process.argv.slice(2));
18
+ process.exit(exitCode);
19
+
20
+ async function loadSourceLauncher(srcLauncherPath) {
21
+ const jiti = createJiti(import.meta.url, {
22
+ fsCache: false,
23
+ interopDefault: false,
24
+ moduleCache: false,
25
+ });
26
+ const loaded = await jiti.import(srcLauncherPath);
27
+ return pickRunCliFromPackageRoot(loaded, srcLauncherPath);
28
+ }
29
+
30
+ async function loadDistLauncher(distLauncherPath) {
31
+ const loaded = await import(pathToFileURL(distLauncherPath).href);
32
+ return pickRunCliFromPackageRoot(loaded, distLauncherPath);
33
+ }
34
+
35
+ function pickRunCliFromPackageRoot(loaded, sourcePath) {
36
+ if (typeof loaded?.runCliFromPackageRoot !== "function") {
37
+ throw new Error(`MinuteWork CLI launcher did not export runCliFromPackageRoot: ${sourcePath}`);
38
+ }
39
+ return loaded.runCliFromPackageRoot;
40
+ }
package/dist/auth.d.ts ADDED
@@ -0,0 +1,59 @@
1
+ import type { CliIo } from "./index.js";
2
+ import type { CliStatePaths, PlatformName } from "./paths.js";
3
+ import { type DeveloperClientDependencies } from "./developer-client.js";
4
+ import { type StoredAuthProfile } from "./state.js";
5
+ export type TokenExchangeResponse = {
6
+ plaintext_token: string;
7
+ token: {
8
+ environment: string;
9
+ expires_at: string;
10
+ id: string;
11
+ issued_for_user: {
12
+ email: string;
13
+ id: string;
14
+ username: string;
15
+ };
16
+ kind: string;
17
+ scope_template: string;
18
+ scopes: string[];
19
+ tenant_id: string;
20
+ tenant_name: string;
21
+ tenant_slug: string;
22
+ };
23
+ };
24
+ export type LoopbackReceiver = {
25
+ close: () => Promise<void>;
26
+ redirectUri: string;
27
+ waitForCode: (expectedState: string) => Promise<string>;
28
+ };
29
+ export type LoginDependencies = {
30
+ createLoopbackReceiver?: () => Promise<LoopbackReceiver>;
31
+ fetchImpl?: typeof fetch;
32
+ openExternalUrl?: (url: string, platform?: PlatformName) => Promise<void>;
33
+ };
34
+ export type LinkDependencies = DeveloperClientDependencies;
35
+ export declare function runLoginCommand(options: {
36
+ args: string[];
37
+ cwd: string;
38
+ dependencies?: LoginDependencies;
39
+ env: NodeJS.ProcessEnv;
40
+ io: CliIo;
41
+ paths: CliStatePaths;
42
+ platform: PlatformName;
43
+ }): Promise<number>;
44
+ export declare function requireCurrentAuthProfile(paths: CliStatePaths): Promise<StoredAuthProfile>;
45
+ export declare function runLinkCommand(options: {
46
+ args: string[];
47
+ cwd: string;
48
+ dependencies?: LinkDependencies;
49
+ env: NodeJS.ProcessEnv;
50
+ io: CliIo;
51
+ paths: CliStatePaths;
52
+ platform: PlatformName;
53
+ }): Promise<number>;
54
+ export declare function runEnvUseCommand(options: {
55
+ args: string[];
56
+ cwd: string;
57
+ io: CliIo;
58
+ platform: PlatformName;
59
+ }): Promise<number>;