@ofidj/generator-fidj 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +93 -58
  2. package/bin/create-fidj.cjs +54 -0
  3. package/generators/app/index.js +58 -189
  4. package/generators/app/templates/typescript/README.md +73 -0
  5. package/generators/app/templates/typescript/app.config.json +10 -0
  6. package/generators/app/templates/typescript/gitignore +5 -0
  7. package/generators/app/templates/typescript/package.json +25 -0
  8. package/generators/app/templates/typescript/public/fidj-logo.png +0 -0
  9. package/generators/app/templates/typescript/public/index.html +16 -0
  10. package/generators/app/templates/typescript/scripts/build.mjs +46 -0
  11. package/generators/app/templates/typescript/scripts/privacy-check.mjs +21 -0
  12. package/generators/app/templates/typescript/scripts/render-content.mjs +14 -0
  13. package/generators/app/templates/typescript/server/data-store.ts +123 -0
  14. package/generators/app/templates/typescript/server/index.ts +329 -0
  15. package/generators/app/templates/typescript/server/privacy-adapter.ts +21 -0
  16. package/generators/app/templates/typescript/src/content.ts +416 -0
  17. package/generators/app/templates/typescript/src/main.ts +243 -0
  18. package/generators/app/templates/typescript/src/style.css +639 -0
  19. package/generators/app/templates/typescript/test/server.test.mjs +287 -0
  20. package/generators/app/templates/typescript/tsconfig.json +15 -0
  21. package/lib/app-module.cjs +59 -0
  22. package/lib/scaffold.cjs +145 -0
  23. package/package.json +18 -12
  24. package/generators/app/templates/app2018/.travis.yml +0 -35
  25. package/generators/app/templates/app2018/LICENSE +0 -21
  26. package/generators/app/templates/app2018/Procfile +0 -1
  27. package/generators/app/templates/app2018/README.md +0 -49
  28. package/generators/app/templates/app2018/config.xml +0 -91
  29. package/generators/app/templates/app2018/gulpfile.js +0 -63
  30. package/generators/app/templates/app2018/ionic.config.json +0 -9
  31. package/generators/app/templates/app2018/package.json +0 -90
  32. package/generators/app/templates/app2018/platforms/README.md +0 -1
  33. package/generators/app/templates/app2018/resources/README.md +0 -6
  34. package/generators/app/templates/app2018/resources/icon.png +0 -0
  35. package/generators/app/templates/app2018/resources/marketing-01-ipad.png +0 -0
  36. package/generators/app/templates/app2018/resources/marketing-01-iphone.png +0 -0
  37. package/generators/app/templates/app2018/resources/marketing-02-ipad.png +0 -0
  38. package/generators/app/templates/app2018/resources/marketing-02-iphone.png +0 -0
  39. package/generators/app/templates/app2018/resources/marketing-03-ipad.png +0 -0
  40. package/generators/app/templates/app2018/resources/marketing-03-iphone.png +0 -0
  41. package/generators/app/templates/app2018/resources/marketing.json +0 -19
  42. package/generators/app/templates/app2018/resources/splash.png +0 -0
  43. package/generators/app/templates/app2018/src/app/app.component.ts +0 -88
  44. package/generators/app/templates/app2018/src/app/app.html +0 -19
  45. package/generators/app/templates/app2018/src/app/app.module.ts +0 -40
  46. package/generators/app/templates/app2018/src/app/app.scss +0 -16
  47. package/generators/app/templates/app2018/src/app/main.ts +0 -5
  48. package/generators/app/templates/app2018/src/app/shared/card.model.ts +0 -10
  49. package/generators/app/templates/app2018/src/app/shared/profile.service.ts +0 -46
  50. package/generators/app/templates/app2018/src/app/shared/shared.module.ts +0 -35
  51. package/generators/app/templates/app2018/src/app/shared/storage.service.ts +0 -11
  52. package/generators/app/templates/app2018/src/app/shared/version.const.ts +0 -5
  53. package/generators/app/templates/app2018/src/assets/icon/favicon.ico +0 -0
  54. package/generators/app/templates/app2018/src/assets/imgs/gg.png +0 -0
  55. package/generators/app/templates/app2018/src/assets/imgs/github.png +0 -0
  56. package/generators/app/templates/app2018/src/assets/imgs/login.svg +0 -5668
  57. package/generators/app/templates/app2018/src/assets/imgs/logo.png +0 -0
  58. package/generators/app/templates/app2018/src/index.html +0 -49
  59. package/generators/app/templates/app2018/src/manifest.json +0 -13
  60. package/generators/app/templates/app2018/src/pages/home/home.html +0 -56
  61. package/generators/app/templates/app2018/src/pages/home/home.scss +0 -3
  62. package/generators/app/templates/app2018/src/pages/home/home.ts +0 -126
  63. package/generators/app/templates/app2018/src/pages/login/login.html +0 -56
  64. package/generators/app/templates/app2018/src/pages/login/login.scss +0 -44
  65. package/generators/app/templates/app2018/src/pages/login/login.ts +0 -59
  66. package/generators/app/templates/app2018/src/pages/profile/profile.html +0 -33
  67. package/generators/app/templates/app2018/src/pages/profile/profile.scss +0 -3
  68. package/generators/app/templates/app2018/src/pages/profile/profile.ts +0 -36
  69. package/generators/app/templates/app2018/src/service-worker.js +0 -31
  70. package/generators/app/templates/app2018/src/theme/variables.scss +0 -88
  71. package/generators/app/templates/app2018/tests/karma.unit-tests.conf.js +0 -84
  72. package/generators/app/templates/app2018/tests/unit/app.test.js +0 -10
  73. package/generators/app/templates/app2018/tsconfig.json +0 -28
  74. package/generators/app/templates/app2018/tslint.json +0 -8
  75. package/generators/app/templates/app2018/web.js +0 -21
  76. package/generators/app/templates/app2021/.travis.yml +0 -32
  77. package/generators/app/templates/app2021/LICENSE +0 -21
  78. package/generators/app/templates/app2021/Procfile +0 -1
  79. package/generators/app/templates/app2021/README.md +0 -45
  80. package/generators/app/templates/app2021/angular.json +0 -188
  81. package/generators/app/templates/app2021/browserslist +0 -12
  82. package/generators/app/templates/app2021/ionic.config.json +0 -9
  83. package/generators/app/templates/app2021/karma.conf.js +0 -31
  84. package/generators/app/templates/app2021/package-lock.json +0 -5
  85. package/generators/app/templates/app2021/package.json +0 -77
  86. package/generators/app/templates/app2021/platforms/README.md +0 -1
  87. package/generators/app/templates/app2021/resources/README.md +0 -6
  88. package/generators/app/templates/app2021/resources/icon.png +0 -0
  89. package/generators/app/templates/app2021/resources/marketing-01-ipad.png +0 -0
  90. package/generators/app/templates/app2021/resources/marketing-01-iphone.png +0 -0
  91. package/generators/app/templates/app2021/resources/marketing-02-ipad.png +0 -0
  92. package/generators/app/templates/app2021/resources/marketing-02-iphone.png +0 -0
  93. package/generators/app/templates/app2021/resources/marketing-03-ipad.png +0 -0
  94. package/generators/app/templates/app2021/resources/marketing-03-iphone.png +0 -0
  95. package/generators/app/templates/app2021/resources/marketing.json +0 -19
  96. package/generators/app/templates/app2021/resources/splash.png +0 -0
  97. package/generators/app/templates/app2021/src/app/app-routing.module.ts +0 -25
  98. package/generators/app/templates/app2021/src/app/app.component.html +0 -3
  99. package/generators/app/templates/app2021/src/app/app.component.scss +0 -0
  100. package/generators/app/templates/app2021/src/app/app.component.spec.ts +0 -50
  101. package/generators/app/templates/app2021/src/app/app.component.ts +0 -28
  102. package/generators/app/templates/app2021/src/app/app.module.ts +0 -24
  103. package/generators/app/templates/app2021/src/app/content/content-routing.module.ts +0 -16
  104. package/generators/app/templates/app2021/src/app/content/content.module.ts +0 -20
  105. package/generators/app/templates/app2021/src/app/content/content.page.html +0 -31
  106. package/generators/app/templates/app2021/src/app/content/content.page.scss +0 -0
  107. package/generators/app/templates/app2021/src/app/content/content.page.ts +0 -26
  108. package/generators/app/templates/app2021/src/app/explore-container/explore-container.component.html +0 -4
  109. package/generators/app/templates/app2021/src/app/explore-container/explore-container.component.scss +0 -4
  110. package/generators/app/templates/app2021/src/app/explore-container/explore-container.component.spec.ts +0 -24
  111. package/generators/app/templates/app2021/src/app/explore-container/explore-container.component.ts +0 -16
  112. package/generators/app/templates/app2021/src/app/explore-container/explore-container.module.ts +0 -14
  113. package/generators/app/templates/app2021/src/app/my/my-routing.module.ts +0 -36
  114. package/generators/app/templates/app2021/src/app/my/my.module.ts +0 -19
  115. package/generators/app/templates/app2021/src/app/my/my.page.html +0 -15
  116. package/generators/app/templates/app2021/src/app/my/my.page.scss +0 -1
  117. package/generators/app/templates/app2021/src/app/my/my.page.spec.ts +0 -26
  118. package/generators/app/templates/app2021/src/app/my/my.page.ts +0 -25
  119. package/generators/app/templates/app2021/src/app/profile/profile-routing.module.ts +0 -16
  120. package/generators/app/templates/app2021/src/app/profile/profile.module.ts +0 -20
  121. package/generators/app/templates/app2021/src/app/profile/profile.page.html +0 -49
  122. package/generators/app/templates/app2021/src/app/profile/profile.page.scss +0 -0
  123. package/generators/app/templates/app2021/src/app/profile/profile.page.spec.ts +0 -25
  124. package/generators/app/templates/app2021/src/app/profile/profile.page.ts +0 -44
  125. package/generators/app/templates/app2021/src/app/shared/const.ts +0 -3
  126. package/generators/app/templates/app2021/src/app/signin/signin-routing.module.ts +0 -16
  127. package/generators/app/templates/app2021/src/app/signin/signin.module.ts +0 -18
  128. package/generators/app/templates/app2021/src/app/signin/signin.page.html +0 -53
  129. package/generators/app/templates/app2021/src/app/signin/signin.page.scss +0 -36
  130. package/generators/app/templates/app2021/src/app/signin/signin.page.ts +0 -69
  131. package/generators/app/templates/app2021/src/assets/icon/favicon.png +0 -0
  132. package/generators/app/templates/app2021/src/assets/shapes.svg +0 -1
  133. package/generators/app/templates/app2021/src/environments/environment.prod.ts +0 -3
  134. package/generators/app/templates/app2021/src/environments/environment.ts +0 -16
  135. package/generators/app/templates/app2021/src/global.scss +0 -26
  136. package/generators/app/templates/app2021/src/index.html +0 -26
  137. package/generators/app/templates/app2021/src/main.ts +0 -12
  138. package/generators/app/templates/app2021/src/polyfills.ts +0 -67
  139. package/generators/app/templates/app2021/src/test.ts +0 -20
  140. package/generators/app/templates/app2021/src/theme/variables.scss +0 -232
  141. package/generators/app/templates/app2021/src/zone-flags.ts +0 -5
  142. package/generators/app/templates/app2021/tests/protractor.conf.js +0 -36
  143. package/generators/app/templates/app2021/tests/src/app.e2e-spec.ts +0 -14
  144. package/generators/app/templates/app2021/tests/src/app.po.ts +0 -11
  145. package/generators/app/templates/app2021/tests/tsconfig.json +0 -13
  146. package/generators/app/templates/app2021/tsconfig.app.json +0 -18
  147. package/generators/app/templates/app2021/tsconfig.json +0 -23
  148. package/generators/app/templates/app2021/tsconfig.spec.json +0 -18
  149. package/generators/app/templates/app2021/tslint.json +0 -149
  150. package/generators/app/templates/app2021/web.js +0 -21
@@ -0,0 +1,287 @@
1
+ import {execFile} from "node:child_process";
2
+ import {promisify} from "node:util";
3
+ import { test } from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import { createServer } from "node:http";
6
+ import { mkdtemp, rm, writeFile, readdir } from "node:fs/promises";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { createHmac } from "node:crypto";
10
+ import { createRequire } from "node:module";
11
+ const { createApp, DataStore } = createRequire(import.meta.url)(
12
+ "../dist/server.cjs",
13
+ );
14
+ const listen = (server) =>
15
+ new Promise((resolve) =>
16
+ server.listen(0, "127.0.0.1", () =>
17
+ resolve(`http://127.0.0.1:${server.address().port}`),
18
+ ),
19
+ );
20
+ const close = (server) => new Promise((resolve) => server.close(resolve));
21
+ const token = (subject, appId = "test-app") =>
22
+ `header.${Buffer.from(JSON.stringify({ sub: subject, name: subject + "@test.local", aud: appId, roles: ["Owner"] })).toString("base64url")}.signature`;
23
+
24
+ test("enforces live app roles, identity isolation and scoped privacy over HTTP", async () => {
25
+ const directory = await mkdtemp(join(tmpdir(), "fidj-data-test-"));
26
+ const adapterKey = "test-only-privacy-adapter-secret-32-chars";
27
+ let appUrl;
28
+ const adapterCall = async (
29
+ operation,
30
+ subject,
31
+ requestId = "departure-maya",
32
+ overrides = {},
33
+ ) => {
34
+ const data = { operation, subject, appId: "test-app", requestId };
35
+ const timestamp = String(Date.now());
36
+ const signature = createHmac("sha256", adapterKey)
37
+ .update(timestamp + "." + JSON.stringify(data))
38
+ .digest("hex");
39
+ return fetch(appUrl + "/fidj/privacy", {
40
+ method: "POST",
41
+ headers: {
42
+ "Content-Type": "application/json",
43
+ "x-fidj-timestamp": timestamp,
44
+ "x-fidj-signature": signature,
45
+ ...overrides,
46
+ },
47
+ body: JSON.stringify(data),
48
+ });
49
+ };
50
+ const maya = token("maya");
51
+ const sam = token("sam");
52
+ const assignments = new Map([
53
+ [maya, ["Free"]],
54
+ [sam, ["Editor"]],
55
+ ]);
56
+ const consents = new Map();
57
+ let outage = false;
58
+ const issuer = createServer(async (req, res) => {
59
+ const bearer = req.headers.authorization?.slice(7);
60
+ const roles = assignments.get(bearer);
61
+ res.setHeader("Content-Type", "application/json");
62
+ if (outage) {
63
+ res.writeHead(503);
64
+ return res.end("{}");
65
+ }
66
+ if (!roles) {
67
+ res.writeHead(403);
68
+ return res.end("{}");
69
+ }
70
+ if (req.url === "/apps/test-app/me")
71
+ return res.end(
72
+ JSON.stringify({ roles: roles.map((type) => ({ type })) }),
73
+ );
74
+ if (req.url === "/me/apps/test-app/consents/history")
75
+ return res.end('{"history":[]}');
76
+ if (req.url === "/me/apps/test-app/consents") {
77
+ if (req.method === "PUT") {
78
+ let text = "";
79
+ for await (const chunk of req) text += chunk;
80
+ consents.set(bearer, JSON.parse(text));
81
+ }
82
+ return res.end(
83
+ JSON.stringify({
84
+ ...(consents.get(bearer) || { terms: true }),
85
+ appDataConnected: true,
86
+ }),
87
+ );
88
+ }
89
+ if (req.url === "/me/apps/test-app/export")
90
+ return res.end(JSON.stringify({ consent: consents.get(bearer) }));
91
+ if (req.url === "/me/apps/test-app" && req.method === "DELETE") {
92
+ const erased = await adapterCall(
93
+ "erase",
94
+ JSON.parse(Buffer.from(bearer.split(".")[1], "base64url")).sub,
95
+ );
96
+ if (!erased.ok) {
97
+ res.writeHead(202);
98
+ return res.end('{"status":"pending"}');
99
+ }
100
+ assignments.delete(bearer);
101
+ return res.end('{"status":"completed"}');
102
+ }
103
+ res.writeHead(404);
104
+ res.end("{}");
105
+ });
106
+ const apiEndpoint = await listen(issuer);
107
+ const settings = {
108
+ appId: "test-app",
109
+ apiEndpoint,
110
+ dashboardUrl: "https://fidj.ovh",
111
+ title: "Test app",
112
+ localDemo: false,
113
+ };
114
+ const persistence = new DataStore(directory, "test-app");
115
+ const erase = persistence.erase.bind(persistence);
116
+ let storageUnavailable = false;
117
+ persistence.erase = async (...args) => {
118
+ if (storageUnavailable) throw new Error("Simulated storage outage");
119
+ return erase(...args);
120
+ };
121
+ let app = createApp(settings, {
122
+ dataDir: directory,
123
+ adapterKey,
124
+ store: persistence,
125
+ });
126
+ let url = await listen(app);
127
+ appUrl = url;
128
+ const call = (path, bearer = maya, method = "GET", data) =>
129
+ fetch(url + "/api/" + path, {
130
+ method,
131
+ headers: {
132
+ Authorization: "Bearer " + bearer,
133
+ "Content-Type": "application/json",
134
+ },
135
+ body: data === undefined ? undefined : JSON.stringify(data),
136
+ });
137
+ try {
138
+ assert.equal((await fetch(url + "/api/notes")).status, 401);
139
+ assert.equal(
140
+ (await call("session", token("maya", "other-app"))).status,
141
+ 401,
142
+ );
143
+ assert.equal((await call("session", maya + "tampered")).status, 401);
144
+ assert.equal(
145
+ (await call("notes", maya, "POST", { title: "Hello", body: "Private" }))
146
+ .status,
147
+ 403,
148
+ );
149
+ assignments.set(maya, ["Editor"]);
150
+ assert.equal(
151
+ (await call("notes", maya, "POST", { title: "Hello", body: "Private" }))
152
+ .status,
153
+ 201,
154
+ );
155
+ assert.equal((await (await call("notes", sam)).json()).notes.length, 0);
156
+ assert.equal((await (await call("notes")).json()).notes.length, 1);
157
+ await call("notes", sam, "POST", { title: "Sam only", body: "Preserve" });
158
+ await close(app);
159
+ app = createApp(settings, {
160
+ dataDir: directory,
161
+ adapterKey,
162
+ store: persistence,
163
+ });
164
+ url = await listen(app);
165
+ appUrl = url;
166
+ assert.equal((await (await call("notes")).json()).notes[0].title, "Hello");
167
+ assert.equal(
168
+ (
169
+ await adapterCall("export", "maya", "export-test", {
170
+ "x-fidj-signature": "0".repeat(64),
171
+ })
172
+ ).status,
173
+ 401,
174
+ );
175
+ assert.equal(
176
+ (
177
+ await adapterCall("export", "maya", "export-test", {
178
+ "x-fidj-timestamp": "1",
179
+ })
180
+ ).status,
181
+ 401,
182
+ );
183
+ assert.equal(
184
+ (await (await adapterCall("export", "maya", "export-test")).json()).data
185
+ .notes.length,
186
+ 1,
187
+ );
188
+ assert.equal(
189
+ (await (await adapterCall("export", "sam", "export-sam")).json()).data
190
+ .notes.length,
191
+ 1,
192
+ );
193
+ assert.equal((await fetch(url + "/.fidj-data/data.json")).status, 404);
194
+ const readiness = await (await adapterCall("check", "readiness-check", "check-test")).json();
195
+ assert.equal(readiness.storage, "ready");
196
+ assert.deepEqual(readiness.capabilities, ["export", "erase"]);
197
+ const checked = await promisify(execFile)(process.execPath, ["scripts/privacy-check.mjs"], {env: {...process.env, FIDJ_APP_ID: "test-app", FIDJ_APP_URL: url, FIDJ_PRIVACY_ADAPTER_KEY: adapterKey}});
198
+ assert.match(checked.stdout, /Ready:/);
199
+ await assert.rejects(promisify(execFile)(process.execPath, ["scripts/privacy-check.mjs"], {env: {...process.env, FIDJ_APP_ID: "test-app", FIDJ_APP_URL: url, FIDJ_PRIVACY_ADAPTER_KEY: "wrong-key-that-is-long-enough-for-check"}}));
200
+ assert.equal(
201
+ (await (await fetch(url + "/api/config")).json()).adapterKey,
202
+ undefined,
203
+ );
204
+ assignments.set(maya, ["Free"]);
205
+ assert.equal(
206
+ (await call("notes", maya, "POST", { title: "Denied", body: "" })).status,
207
+ 403,
208
+ );
209
+ assert.equal(
210
+ (await call("privacy", maya, "PUT", { analytics: true })).status,
211
+ 200,
212
+ );
213
+ assert.equal(consents.get(maya).analytics, true);
214
+ assert.equal(consents.has(sam), false);
215
+ assert.equal(
216
+ (await (await call("privacy/export")).json()).app.notes.length,
217
+ 1,
218
+ );
219
+ outage = true;
220
+ assert.equal((await call("notes")).status, 503);
221
+ outage = false;
222
+ assert.equal(
223
+ (await call("privacy/leave", maya, "DELETE", { confirm: "other-app" }))
224
+ .status,
225
+ 400,
226
+ );
227
+ storageUnavailable = true;
228
+ assert.equal((await adapterCall("erase", "maya")).status, 500);
229
+ assert.equal((await (await call("notes")).json()).notes.length, 1);
230
+ storageUnavailable = false;
231
+ await writeFile(
232
+ join(directory, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa.tmp"),
233
+ "Interrupted private data copy",
234
+ );
235
+ assert.equal(
236
+ (await call("privacy/leave", maya, "DELETE", { confirm: "test-app" }))
237
+ .status,
238
+ 200,
239
+ );
240
+ assert.equal((await call("notes")).status, 401);
241
+ assert.equal(
242
+ (await readdir(directory)).filter((name) => name.endsWith(".tmp")).length,
243
+ 0,
244
+ );
245
+ assignments.set(maya, ["Free"]);
246
+ assert.equal((await (await call("notes")).json()).notes.length, 0);
247
+ assert.equal((await call("session", sam)).status, 200);
248
+ assert.equal(
249
+ (await (await call("notes", sam)).json()).notes[0].title,
250
+ "Sam only",
251
+ );
252
+ assignments.set(maya, ["Editor"]);
253
+ await call("notes", maya, "POST", {
254
+ title: "New membership",
255
+ body: "Keep this",
256
+ });
257
+ await close(app);
258
+ app = createApp(settings, {
259
+ dataDir: directory,
260
+ adapterKey,
261
+ store: persistence,
262
+ });
263
+ url = await listen(app);
264
+ appUrl = url;
265
+ assert.equal((await adapterCall("erase", "maya")).status, 200);
266
+ assert.equal(
267
+ (await (await call("notes")).json()).notes[0].title,
268
+ "New membership",
269
+ );
270
+ assert.equal((await adapterCall("erase", "sam")).status, 500);
271
+ const store = new DataStore(directory, "test-app");
272
+ await assert.rejects(
273
+ store.add(
274
+ "maya",
275
+ { id: "denied", title: "Race", body: "", createdAt: "" },
276
+ async () => {
277
+ throw new Error("Revoked during operation");
278
+ },
279
+ ),
280
+ );
281
+ assert.equal((await store.notes("maya")).length, 1);
282
+ } finally {
283
+ await close(app);
284
+ await close(issuer);
285
+ await rm(directory, { recursive: true, force: true });
286
+ }
287
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "lib": ["ES2022", "DOM"],
10
+ "types": ["node"],
11
+ "noEmit": true,
12
+ "resolveJsonModule": true
13
+ },
14
+ "include": ["src/**/*.ts", "server/**/*.ts"]
15
+ }
@@ -0,0 +1,59 @@
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+
4
+ function inspectModule(source, entry = "index.html", destination) {
5
+ const root = fs.realpathSync(path.resolve(source));
6
+ const output = path.resolve(destination);
7
+ if (!fs.statSync(root).isDirectory())
8
+ throw new Error("Module must be a built website directory.");
9
+ if (
10
+ output === root ||
11
+ output.startsWith(root + path.sep) ||
12
+ root.startsWith(output + path.sep)
13
+ )
14
+ throw new Error(
15
+ "Module source and generated destination must not contain each other.",
16
+ );
17
+ if (/^(?:[a-z]+:|\/\/)/i.test(entry))
18
+ throw new Error("Module entry must be relative.");
19
+ const url = new URL(entry, "https://module.invalid/module/");
20
+ if (
21
+ url.origin !== "https://module.invalid" ||
22
+ !url.pathname.startsWith("/module/") ||
23
+ url.search ||
24
+ entry.startsWith("/")
25
+ )
26
+ throw new Error(
27
+ "Module entry must be a relative HTML path inside the module.",
28
+ );
29
+ const file = decodeURIComponent(url.pathname.slice("/module/".length));
30
+ if (
31
+ !file.endsWith(".html") ||
32
+ file.split("/").some((part) => part === "..") ||
33
+ file.includes("\\")
34
+ )
35
+ throw new Error(
36
+ "Module entry must be a relative HTML path inside the module.",
37
+ );
38
+ if (!fs.statSync(path.join(root, file)).isFile())
39
+ throw new Error("Module entry does not exist.");
40
+ if (!fs.readFileSync(path.join(root, file), "utf8").includes("</head>"))
41
+ throw new Error("Module entry must have an HTML head.");
42
+ const visit = (directory) => {
43
+ for (const item of fs.readdirSync(directory, { withFileTypes: true })) {
44
+ if (item.isSymbolicLink())
45
+ throw new Error("Built modules must not contain symbolic links.");
46
+ if (
47
+ [".git", "node_modules", ".env"].includes(item.name) ||
48
+ item.name.startsWith(".env.")
49
+ )
50
+ throw new Error(
51
+ "Supply only the module's public build output, without environment or dependency files.",
52
+ );
53
+ if (item.isDirectory()) visit(path.join(directory, item.name));
54
+ }
55
+ };
56
+ visit(root);
57
+ return { root, file, entry: "./module/" + file + url.hash };
58
+ }
59
+ module.exports = { inspectModule };
@@ -0,0 +1,145 @@
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+ const { inspectModule } = require("./app-module.cjs");
4
+ const templateRoot = path.join(
5
+ __dirname,
6
+ "../generators/app/templates/typescript",
7
+ );
8
+
9
+ function scaffold(destination, options) {
10
+ const anonymous = options.anonymous ?? true;
11
+ if (![true, false, "true", "false"].includes(anonymous))
12
+ throw new Error("--anonymous must be true or false.");
13
+ const name = options.name || path.basename(path.resolve(destination));
14
+ if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(name))
15
+ throw new Error(
16
+ "Use a lowercase project name with letters, numbers and hyphens.",
17
+ );
18
+ if (!/^[a-zA-Z0-9_-]+$/.test(options.appId || ""))
19
+ throw new Error("Supply your app’s public fidjId with --app-id.");
20
+ const api = new URL(options.apiEndpoint || "https://api.sandbox.fidj.ovh/v3");
21
+ if (
22
+ api.username ||
23
+ api.password ||
24
+ api.search ||
25
+ api.hash ||
26
+ !["http:", "https:"].includes(api.protocol)
27
+ )
28
+ throw new Error(
29
+ "Use an API URL without credentials, a query or a fragment.",
30
+ );
31
+ if (
32
+ api.protocol === "http:" &&
33
+ !["localhost", "127.0.0.1", "[::1]"].includes(api.hostname)
34
+ )
35
+ throw new Error("Use HTTPS for a remote API.");
36
+ if (options.oidcIssuer) {
37
+ const issuer = new URL(options.oidcIssuer);
38
+ if (issuer.origin !== api.origin || issuer.pathname !== "/oidc" || issuer.hash || issuer.search || issuer.username || issuer.password) throw new Error("OIDC issuer must share the API origin and use /oidc.");
39
+ if (options.content === undefined && !options.module) throw new Error("OIDC currently requires a content app or console module.");
40
+ }
41
+ const title = options.title || name;
42
+ if (/[\r\n]/.test(title)) throw new Error("Title must be one line.");
43
+ if (
44
+ options.domain &&
45
+ !/^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i.test(options.domain)
46
+ )
47
+ throw new Error("Domain must be a hostname, without a scheme or path.");
48
+ const dir = path.resolve(destination);
49
+ const appModule = options.module
50
+ ? inspectModule(options.module, options.moduleEntry, dir)
51
+ : null;
52
+ if (options.moduleEntry && !appModule)
53
+ throw new Error("--module-entry requires --module.");
54
+ if (options.replace && fs.existsSync(dir)) {
55
+ if (!fs.existsSync(path.join(dir, ".fidj-generated")))
56
+ throw new Error(
57
+ "Replacement requires a .fidj-generated marker in the destination.",
58
+ );
59
+ fs.rmSync(dir, { recursive: true });
60
+ }
61
+ if (fs.existsSync(dir) && fs.readdirSync(dir).length)
62
+ throw new Error(
63
+ "Destination must be empty. Existing projects are never overwritten.",
64
+ );
65
+ fs.mkdirSync(dir, { recursive: true });
66
+ fs.cpSync(templateRoot, dir, { recursive: true });
67
+ const pkg = JSON.parse(
68
+ fs.readFileSync(path.join(dir, "package.json"), "utf8"),
69
+ );
70
+ pkg.name = name;
71
+ if (options.sdkPath) {
72
+ const sdkPath = path.resolve(options.sdkPath);
73
+ const sdk = JSON.parse(
74
+ fs.readFileSync(path.join(sdkPath, "package.json"), "utf8"),
75
+ );
76
+ if (sdk.name !== "@ofidj/node")
77
+ throw new Error("SDK path must contain a built @ofidj/node package.");
78
+ pkg.dependencies["@ofidj/node"] = "file:" + sdkPath;
79
+ }
80
+ fs.writeFileSync(
81
+ path.join(dir, "package.json"),
82
+ JSON.stringify(pkg, null, 2) + "\n",
83
+ );
84
+ fs.renameSync(path.join(dir, "gitignore"), path.join(dir, ".gitignore"));
85
+ const settings = `FIDJ_APP_ID=${options.appId}\nFIDJ_API_ENDPOINT=${api.href.replace(/\/$/, "")}\nFIDJ_DASHBOARD_URL=https://fidj.ovh\nAPP_TITLE=${title}\nPORT=8200\nHOST=127.0.0.1\nLOCAL_DEMO=false\nFIDJ_DATA_DIR=../.fidj-data/${name}\nFIDJ_PRIVACY_ADAPTER_KEY=\n`;
86
+ const localApi = options.local
87
+ ? "http://localhost:3201/v3"
88
+ : api.href.replace(/\/$/, "");
89
+ const configuration = {
90
+ appId: options.appId,
91
+ oidcIssuer: options.oidcIssuer || null,
92
+ apiEndpoint: localApi,
93
+ dashboardUrl: options.local ? "http://localhost:4200" : "https://fidj.ovh",
94
+ title,
95
+ localDemo: Boolean(options.local),
96
+ allowAnonymous: anonymous === true || anonymous === "true",
97
+ welcome: options.welcome || title,
98
+ description:
99
+ options.description ||
100
+ "A space to explore, with an account that puts you in control.",
101
+ content: appModule ? "" : options.content,
102
+ moduleEntry: appModule?.entry || "",
103
+ domain: options.domain,
104
+ };
105
+ if (appModule) {
106
+ fs.cpSync(appModule.root, path.join(dir, "public/module"), {
107
+ recursive: true,
108
+ });
109
+ const entryFile = path.join(dir, "public/module", appModule.file);
110
+ const relativeSignin =
111
+ path.posix.relative(
112
+ path.posix.dirname("module/" + appModule.file),
113
+ "index.html",
114
+ ) + "#/signin";
115
+ const html = fs.readFileSync(entryFile, "utf8");
116
+ if (!html.includes("</head>"))
117
+ throw new Error("Module entry must have an HTML head.");
118
+ fs.writeFileSync(
119
+ entryFile,
120
+ html.replace(
121
+ "</head>",
122
+ `<meta name="fidj-signin" content="${relativeSignin}"></head>`,
123
+ ),
124
+ );
125
+ }
126
+ fs.writeFileSync(
127
+ path.join(dir, "app.config.json"),
128
+ JSON.stringify(configuration, null, 2) + "\n",
129
+ );
130
+ const env = settings
131
+ .replace(api.href.replace(/\/$/, ""), localApi)
132
+ .replace(
133
+ "FIDJ_DASHBOARD_URL=https://fidj.ovh",
134
+ `FIDJ_DASHBOARD_URL=${configuration.dashboardUrl}`,
135
+ )
136
+ .replace("LOCAL_DEMO=false", `LOCAL_DEMO=${configuration.localDemo}`);
137
+ fs.writeFileSync(path.join(dir, ".env.example"), env);
138
+ fs.writeFileSync(path.join(dir, ".env"), env);
139
+ fs.writeFileSync(
140
+ path.join(dir, ".fidj-generated"),
141
+ "Disposable generator output.\n",
142
+ );
143
+ return dir;
144
+ }
145
+ module.exports = { scaffold };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ofidj/generator-fidj",
3
- "version": "1.0.0",
4
- "description": "build great mobile angular ionic apps based on fidj.ovh authentication",
3
+ "version": "1.0.1",
4
+ "description": "Generate a TypeScript app with Fidj sign-in, live server-side roles and per-app privacy.",
5
5
  "homepage": "https://fidj.ovh",
6
6
  "bugs": {
7
7
  "url": "https://github.com/ofidj/generator-fidj/issues"
@@ -10,9 +10,13 @@
10
10
  "access": "public"
11
11
  },
12
12
  "files": [
13
- "generators"
13
+ "bin",
14
+ "lib",
15
+ "generators",
16
+ "!generators/app/templates/app2018",
17
+ "!generators/app/templates/app2021"
14
18
  ],
15
- "main": "generators/index.js",
19
+ "main": "./lib/scaffold.cjs",
16
20
  "keywords": [
17
21
  "fidj",
18
22
  "angular",
@@ -20,13 +24,9 @@
20
24
  "yeoman-generator"
21
25
  ],
22
26
  "dependencies": {
23
- "chalk": "^3.0.0",
24
- "yeoman-generator": "^4.12.0",
25
- "yosay": "^2.0.2"
26
- },
27
- "devDependencies": {
28
- "tslint": "^6.1.3"
27
+ "yeoman-generator": "^4.12.0"
29
28
  },
29
+ "devDependencies": {},
30
30
  "eslintConfig": {
31
31
  "extends": "xo-space",
32
32
  "env": {
@@ -35,7 +35,13 @@
35
35
  },
36
36
  "repository": "https://github.com/ofidj/generator-fidj.git",
37
37
  "scripts": {
38
- "test": "echo nope"
38
+ "test": "node --test test/*.test.cjs"
39
+ },
40
+ "license": "MIT",
41
+ "bin": {
42
+ "create-fidj": "bin/create-fidj.cjs"
39
43
  },
40
- "license": "MIT"
44
+ "engines": {
45
+ "node": ">=22"
46
+ }
41
47
  }
@@ -1,35 +0,0 @@
1
- language:
2
- - node_js
3
- node_js:
4
- - 6.11.5
5
- branches:
6
- only:
7
- - master
8
- install:
9
- - git config remote.origin.fetch +refs/heads/*:refs/remotes/origin/*
10
- - git fetch --tags --depth=10000
11
- - npm install
12
- - npm version patch -m "[ci skip] version bumped %s"
13
- script:
14
- - npm test
15
- - npm run build
16
- after_success:
17
- - git remote add upstream "https://${GH_TOKEN}@github.com/${GH_REPO}.git"
18
- - git add .
19
- - git commit -m "[ci skip] version ok"
20
- - git push upstream HEAD:master
21
- - rm -r src
22
- - rm -r tests
23
- - rm -r coverage
24
- - rm -r node_modules
25
- - rm www/.gitignore
26
- deploy:
27
- provider: pages
28
- skip-cleanup: true
29
- github-token: $GH_TOKEN
30
- keep-history: true
31
- on:
32
- branch: master
33
- env:
34
- global:
35
- - GH_REPO: "<%= appUserName %>/<%= appName %>"
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2018 fidj.ovh team
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.
@@ -1 +0,0 @@
1
- web: node web.js
@@ -1,49 +0,0 @@
1
- # <%= appName %>
2
-
3
- > <%= appDescription %>
4
-
5
- ## Prerequisite
6
-
7
- Look at [mat's gist](https://gist.github.com/mlefree/2156f66dfb441f107bef157dde56a836),
8
- in order to check your node, cordova, ionic and yeoman installation.
9
-
10
- [![Fidj.ovh Status][fidj-image]][fidj-url]
11
- [![Build Status][travis-image]][travis-url]
12
-
13
- [![video][youtube-img]][youtube-video]
14
-
15
- ## Build it
16
-
17
- Clone and install the project
18
-
19
- ```bash
20
- npm install
21
- ```
22
-
23
- and then launch on your device
24
-
25
- ```bash
26
- npm run ios
27
- # then ready to launch your app (./platforms/ios/*.xcodeproj) in XCode
28
- ```
29
-
30
- ## Deploy
31
-
32
- Follow this https://ionicframework.com/docs/intro/deploying.
33
-
34
- ### For test on Github or Heroku
35
-
36
- It's up to you if you want to use your www generated folder as static page.You can also use the [fidj-sandbox](https://sss) before any production; 2 variables are available for that :
37
-
38
- ```bash
39
- FIDJ_APP_ID # force your fidj app Id with your sandbox's one
40
- FIDJ_APP_PROD # 'true' by default, 'false' to test it on sandbox
41
- ```
42
-
43
- [fidj-image]: https://fidj.ovh/_/<%= appName %>/badges/github.svg
44
- [fidj-url]: https://fidj.ovh/_/<%= appName %>
45
- [youtube-img]: http://img.youtube.com/vi/0FbnCWWg_NY/0.jpg
46
- [youtube-video]: https://www.youtube.com/embed/0FbnCWWg_NY?autoplay=true
47
- [travis-image]: https://travis-ci.org/<%=appUserName %>/<%= appName %>.svg?branch=master
48
- [travis-url]: https://travis-ci.org/<%=appUserName %>/<%= appName %>
49
-